/*
 * Copyright 2002-2003 Michael Günnewig
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 */

/* TODO:
 *   - some improvements possible
 *   - implement DecompressSetPalette? -- do we need it for anything?
 */

#include <assert.h>

#include "msrle_private.h"

#include "winnls.h"
#include "winuser.h"

#include "wine/debug.h"

WINE_DEFAULT_DEBUG_CHANNEL(msrle32);

static HINSTANCE MSRLE32_hModule = 0;

#define compare_fourcc(fcc1, fcc2) (((fcc1)^(fcc2))&~0x20202020)

#define ABS(a)                ((a) < 0 ? -(a) : (a))
#define SQR(a)                ((a) * (a))

#define QUALITY_to_DIST(q)    (ICQUALITY_HIGH - q)
static inline WORD ColorCmp(WORD clr1, WORD clr2)
{
  UINT a = clr1 - clr2;
  return SQR(a);
}
static inline WORD Intensity(RGBQUAD clr)
{
  return (30 * clr.rgbRed + 59 * clr.rgbGreen + 11 * clr.rgbBlue)/4;
}

#define GetRawPixel(lpbi,lp,x) \
  ((lpbi)->biBitCount == 1 ? ((lp)[(x)/8] >> (8 - (x)%8)) & 1 : \
   ((lpbi)->biBitCount == 4 ? ((lp)[(x)/2] >> (4 * (1 - (x)%2))) & 15 : lp[x]))

/*****************************************************************************/

/* utility functions */
static BOOL    isSupportedDIB(LPCBITMAPINFOHEADER lpbi);
static BOOL    isSupportedMRLE(LPCBITMAPINFOHEADER lpbi);
static BYTE    MSRLE32_GetNearestPaletteIndex(UINT count, const RGBQUAD *clrs, RGBQUAD clr);

/* compression functions */
static void    computeInternalFrame(CodecInfo *pi, LPCBITMAPINFOHEADER lpbiIn, const BYTE *lpIn);
static LONG    MSRLE32_GetMaxCompressedSize(LPCBITMAPINFOHEADER lpbi);
static LRESULT MSRLE32_CompressRLE4(const CodecInfo *pi, LPCBITMAPINFOHEADER lpbiIn,
                                    const BYTE *lpIn, LPBITMAPINFOHEADER lpbiOut,
                                    LPBYTE lpOut, BOOL isKey);
static LRESULT MSRLE32_CompressRLE8(const CodecInfo *pi, LPCBITMAPINFOHEADER lpbiIn,
                                    const BYTE *lpIn, LPBITMAPINFOHEADER lpbiOut,
                                    LPBYTE lpOut, BOOL isKey);

/* decompression functions */
static LRESULT MSRLE32_DecompressRLE4(const CodecInfo *pi, LPCBITMAPINFOHEADER lpbi,
				      const BYTE *lpIn, LPBYTE lpOut);
static LRESULT MSRLE32_DecompressRLE8(const CodecInfo *pi, LPCBITMAPINFOHEADER lpbi,
				      const BYTE *lpIn, LPBYTE lpOut);

/* API functions */
static LRESULT CompressGetFormat(CodecInfo *pi, LPCBITMAPINFOHEADER lpbiIn,
				 LPBITMAPINFOHEADER lpbiOut);
static LRESULT CompressGetSize(CodecInfo *pi, LPCBITMAPINFOHEADER lpbiIn,
			       LPCBITMAPINFOHEADER lpbiOut);
static LRESULT CompressQuery(const CodecInfo *pi, LPCBITMAPINFOHEADER lpbiIn,
			     LPCBITMAPINFOHEADER lpbiOut);
static LRESULT CompressBegin(CodecInfo *pi, LPCBITMAPINFOHEADER lpbiIn,
			     LPCBITMAPINFOHEADER lpbiOut);
static LRESULT Compress(CodecInfo *pi, ICCOMPRESS* lpic, DWORD dwSize);
static LRESULT CompressEnd(CodecInfo *pi);

static LRESULT DecompressGetFormat(CodecInfo *pi, LPCBITMAPINFOHEADER lpbiIn,
				   LPBITMAPINFOHEADER lpbiOut);
static LRESULT DecompressQuery(CodecInfo *pi, LPCBITMAPINFOHEADER lpbiIn,
			       LPCBITMAPINFOHEADER lpbiOut);
static LRESULT DecompressBegin(CodecInfo *pi, LPCBITMAPINFOHEADER lpbiIn,
			       LPCBITMAPINFOHEADER lpbiOut);
static LRESULT Decompress(CodecInfo *pi, ICDECOMPRESS *pic, DWORD dwSize);
static LRESULT DecompressEnd(CodecInfo *pi);
static LRESULT DecompressGetPalette(CodecInfo *pi, LPCBITMAPINFOHEADER lpbiIn,
				    LPBITMAPINFOHEADER lpbiOut);

/*****************************************************************************/

static BOOL isSupportedMRLE(LPCBITMAPINFOHEADER lpbi)
{
  /* pre-conditions */
  assert(lpbi != NULL);

  if (lpbi->biSize < sizeof(BITMAPINFOHEADER) ||
      lpbi->biPlanes != 1)
    return FALSE;

  if (lpbi->biCompression == BI_RLE4) {
    if (lpbi->biBitCount != 4 ||
	(lpbi->biWidth % 2) != 0)
      return FALSE;
  } else if (lpbi->biCompression == BI_RLE8) {
    if (lpbi->biBitCount != 8)
      return FALSE;
  } else
    return FALSE;

  return TRUE;
}

static BOOL  isSupportedDIB(LPCBITMAPINFOHEADER lpbi)
{
  /* pre-conditions */
  assert(lpbi != NULL);

  /* check structure version/planes/compression */
  if (lpbi->biSize < sizeof(BITMAPINFOHEADER) ||
      lpbi->biPlanes != 1)
    return FALSE;
  if (lpbi->biCompression != BI_RGB &&
      lpbi->biCompression != BI_BITFIELDS)
    return FALSE;

  /* check bit-depth */
  if (lpbi->biBitCount != 1 &&
      lpbi->biBitCount != 4 &&
      lpbi->biBitCount != 8 &&
      lpbi->biBitCount != 15 &&
      lpbi->biBitCount != 16 &&
      lpbi->biBitCount != 24 &&
      lpbi->biBitCount != 32)
    return FALSE;

  /* check for size(s) */
  if (!lpbi->biWidth || !lpbi->biHeight)
    return FALSE; /* image with zero size, makes no sense so error ! */
  if (DIBWIDTHBYTES(*lpbi) * (DWORD)lpbi->biHeight >= (1UL << 31) - 1)
    return FALSE; /* image too big ! */

  /* check for nonexistent colortable for hi- and true-color DIB's */
  if (lpbi->biBitCount >= 15 && lpbi->biClrUsed > 0)
    return FALSE;

  return TRUE;
}

static BYTE MSRLE32_GetNearestPaletteIndex(UINT count, const RGBQUAD *clrs, RGBQUAD clr)
{
  INT  diff = 0x00FFFFFF;
  UINT i;
  UINT idx = 0;

  /* pre-conditions */
  assert(clrs != NULL);

  for (i = 0; i < count; i++) {
    int r = ((int)clrs[i].rgbRed   - (int)clr.rgbRed);
    int g = ((int)clrs[i].rgbGreen - (int)clr.rgbGreen);
    int b = ((int)clrs[i].rgbBlue  - (int)clr.rgbBlue);

    r = r*r + g*g + b*b;

    if (r < diff) {
      idx  = i;
      diff = r;
      if (diff == 0)
	break;
    }
  }

  return idx;
}

/*****************************************************************************/

void computeInternalFrame(CodecInfo *pi, LPCBITMAPINFOHEADER lpbiIn, const BYTE *lpIn)
{
  WORD   wIntensityTbl[256];
  DWORD  lInLine, lOutLine;
  LPWORD lpOut;
  UINT   i;
  LONG   y;

  /* pre-conditions */
  assert(pi != NULL && lpbiIn != NULL && lpIn != NULL);
  assert(pi->pCurFrame != NULL);

  lInLine  = DIBWIDTHBYTES(*lpbiIn);
  lOutLine = WIDTHBYTES((WORD)lpbiIn->biWidth * 8u * sizeof(WORD)) / 2u;
  lpOut    = pi->pCurFrame;

  assert(lpbiIn->biClrUsed != 0);

  {
    const RGBQUAD *lp =
      (const RGBQUAD *)((const BYTE*)lpbiIn + lpbiIn->biSize);

    for (i = 0; i < lpbiIn->biClrUsed; i++)
      wIntensityTbl[i] = Intensity(lp[i]);
  }

  for (y = 0; y < lpbiIn->biHeight; y++) {
    LONG x;

    switch (lpbiIn->biBitCount) {
    case 1:
      for (x = 0; x < lpbiIn->biWidth / 8; x++) {
	for (i = 0; i < 7; i++)
	  lpOut[8 * x + i] = wIntensityTbl[(lpIn[x] >> (7 - i)) & 1];
      }
      break;
    case 4:
      for (x = 0; x < lpbiIn->biWidth / 2; x++) {
	lpOut[2 * x + 0] = wIntensityTbl[(lpIn[x] >> 4)];
	lpOut[2 * x + 1] = wIntensityTbl[(lpIn[x] & 0x0F)];
      }
      break;
    case 8:
      for (x = 0; x < lpbiIn->biWidth; x++)
	lpOut[x] = wIntensityTbl[lpIn[x]];
      break;
    }

    lpIn  += lInLine;
    lpOut += lOutLine;
  }
}

static LONG MSRLE32_GetMaxCompressedSize(LPCBITMAPINFOHEADER lpbi)
{
  LONG a, b, size;

  /* pre-condition */
  assert(lpbi != NULL);

  a = lpbi->biWidth / 255;
  b = lpbi->biWidth % 255;
  if (lpbi->biBitCount <= 4) {
    a /= 2;
    b /= 2;
  }

  size = (2 + a * (2 + ((a + 2) & ~2)) + b * (2 + ((b + 2) & ~2)));
  return size * lpbi->biHeight;
}

/* lpP => current  pos in previous frame
 * lpA => previous pos in current  frame
 * lpB => current  pos in current  frame
 */
static INT countDiffRLE4(const WORD *lpP, const WORD *lpA, const WORD *lpB, INT pos, LONG lDist, LONG width)
{
  INT  count;
  WORD clr1, clr2;

  /* pre-conditions */
  assert(lpA && lpB && lDist >= 0 && width > 0);

  if (pos >= width)
    return 0;
  if (pos+1 == width)
    return 1;

  clr1 = lpB[pos++];
  clr2 = lpB[pos];

  count = 2;
  while (pos + 1 < width) {
    WORD clr3, clr4;

    clr3 = lpB[++pos];
    if (pos + 1 >= width)
      return count + 1;

    clr4 = lpB[++pos];
    if (ColorCmp(clr1, clr3) <= lDist &&
	ColorCmp(clr2, clr4) <= lDist) {
      /* diff at end? -- look-ahead for at least ?? more encodable pixels */
      if (pos + 2 < width && ColorCmp(clr1,lpB[pos+1]) <= lDist &&
	  ColorCmp(clr2,lpB[pos+2]) <= lDist) {
	if (pos + 4 < width && ColorCmp(lpB[pos+1],lpB[pos+3]) <= lDist &&
	    ColorCmp(lpB[pos+2],lpB[pos+4]) <= lDist)
	  return count - 3; /* followed by at least 4 encodable pixels */
	return count - 2;
      }
    } else if (lpP != NULL && ColorCmp(lpP[pos], lpB[pos]) <= lDist) {
      /* 'compare' with previous frame for end of diff */
      INT count2 = 0;

      /* FIXME */

      if (count2 >= 8)
	return count;

      pos -= count2;
    }

    count += 2;
    clr1 = clr3;
    clr2 = clr4;
  }

  return count;
}

/* lpP => current  pos in previous frame
 * lpA => previous pos in current  frame
 * lpB => current  pos in current  frame
 */
static INT countDiffRLE8(const WORD *lpP, const WORD *lpA, const WORD *lpB, INT pos, LONG lDist, LONG width)
{
  INT count;

  for (count = 0; pos < width; pos++, count++) {
    if (ColorCmp(lpA[pos], lpB[pos]) <= lDist) {
      /* diff at end? -- look-ahead for some more encodable pixel */
      if (pos + 1 < width && ColorCmp(lpB[pos], lpB[pos+1]) <= lDist)
	return count - 1;
      if (pos + 2 < width && ColorCmp(lpB[pos+1], lpB[pos+2]) <= lDist)
	return count - 1;
    } else if (lpP != NULL && ColorCmp(lpP[pos], lpB[pos]) <= lDist) {
      /* 'compare' with previous frame for end of diff */
      INT count2 = 0;

      for (count2 = 0, pos++; pos < width && count2 <= 5; pos++, count2++) {
	if (ColorCmp(lpP[pos], lpB[pos]) > lDist)
	  break;
      }
      if (count2 > 4)
	return count;

      pos -= count2;
    }
  }

  return count;
}

static INT MSRLE32_CompressRLE4Line(const CodecInfo *pi, const WORD *lpP,
                                    const WORD *lpC, LPCBITMAPINFOHEADER lpbi,
                                    const BYTE *lpIn, LONG lDist,
                                    INT x, LPBYTE *ppOut,
                                    DWORD *lpSizeImage)
{
  LPBYTE lpOut = *ppOut;
  INT    count, pos;
  WORD   clr1, clr2;

  /* try to encode as many pixel as possible */
  count = 1;
  pos   = x;
  clr1  = lpC[pos++];
  if (pos < lpbi->biWidth) {
    clr2 = lpC[pos];
    for (++count; pos + 1 < lpbi->biWidth; ) {
      ++pos;
      if (ColorCmp(clr1, lpC[pos]) > lDist)
	break;
      count++;
      if (pos + 1 >= lpbi->biWidth)
	break;
      ++pos;
      if (ColorCmp(clr2, lpC[pos]) > lDist)
	break;
      count++;
    }
  }

  if (count < 4) {
    /* add some pixel for absoluting if possible */
    count += countDiffRLE4(lpP, lpC - 1, lpC, pos-1, lDist, lpbi->biWidth);

    assert(count > 0);

    /* check for near end of line */
    if (x + count > lpbi->biWidth)
      count = lpbi->biWidth - x;

    /* absolute pixel(s) in groups of at least 3 and at most 254 pixels */
    while (count > 2) {
      INT  i;
      INT  size       = min(count, 254);
      int  bytes      = ((size + 1) & (~1)) / 2;
      int  extra_byte = bytes & 0x01;

      *lpSizeImage += 2 + bytes + extra_byte;
      assert(((*lpSizeImage) % 2) == 0);
      count -= size;
      *lpOut++ = 0;
      *lpOut++ = size;
      for (i = 0; i < size; i += 2) {
	clr1 = pi->palette_map[GetRawPixel(lpbi,lpIn,x)];
	x++;
	if (i + 1 < size) {
	  clr2 = pi->palette_map[GetRawPixel(lpbi,lpIn,x)];
	  x++;
	} else
	  clr2 = 0;

	*lpOut++ = (clr1 << 4) | clr2;
      }
      if (extra_byte)
	*lpOut++ = 0;
    }

    if (count > 0) {
      /* too little for absoluting so we must encode them */
      assert(count <= 2);

      *lpSizeImage += 2;
      clr1 = pi->palette_map[GetRawPixel(lpbi,lpIn,x)];
      x++;
      if (count == 2) {
	clr2 = pi->palette_map[GetRawPixel(lpbi,lpIn,x)];
	x++;
      } else
	clr2 = 0;
      *lpOut++ = count;
      *lpOut++ = (clr1 << 4) | clr2;
    }
  } else {
    /* encode count pixel(s) */
    clr1 = ((pi->palette_map[GetRawPixel(lpbi,lpIn,x)] << 4) |
	    pi->palette_map[GetRawPixel(lpbi,lpIn,x + 1)]);

    x += count;
    while (count > 0) {
      INT size = min(count, 254);

      *lpSizeImage += 2;
      count    -= size;
      *lpOut++  = size;
      *lpOut++  = clr1;
    }
  }

  *ppOut = lpOut;

  return x;
}

static INT MSRLE32_CompressRLE8Line(const CodecInfo *pi, const WORD *lpP,
                                    const WORD *lpC, LPCBITMAPINFOHEADER lpbi,
                                    const BYTE *lpIn, LONG lDist,
                                    INT x, LPBYTE *ppOut,
                                    DWORD *lpSizeImage)
{
  LPBYTE lpOut = *ppOut;
  INT    count, pos;
  WORD   clr;

  assert(lpbi->biBitCount <= 8);
  assert(lpbi->biCompression == BI_RGB);

  /* try to encode as much as possible */
  pos = x;
  clr = lpC[pos++];
  for (count = 1; pos < lpbi->biWidth; count++) {
    if (ColorCmp(clr, lpC[pos++]) > lDist)
      break;
  }

  if (count < 2) {
    /* add some more pixels for absoluting if possible */
    count += countDiffRLE8(lpP, lpC - 1, lpC, pos-1, lDist, lpbi->biWidth);

    assert(count > 0);

    /* check for over end of line */
    if (x + count > lpbi->biWidth)
      count = lpbi->biWidth - x;

    /* absolute pixel(s) in groups of at least 3 and at most 255 pixels */
    while (count > 2) {
      INT  i;
      INT  size       = min(count, 255);
      int  extra_byte = size % 2;

      *lpSizeImage += 2 + size + extra_byte;
      count -= size;
      *lpOut++ = 0;
      *lpOut++ = size;
      for (i = 0; i < size; i++) {
	*lpOut++ = pi->palette_map[GetRawPixel(lpbi,lpIn,x)];
	x++;
      }
      if (extra_byte)
	*lpOut++ = 0;
    }
    if (count > 0) {
      /* too little for absoluting so we must encode them even if it's expensive! */
      assert(count <= 2);

      *lpSizeImage += 2 * count;
      *lpOut++ = 1;
      *lpOut++ = pi->palette_map[GetRawPixel(lpbi,lpIn,x)];
      x++;

      if (count == 2) {
	*lpOut++ = 1;
	*lpOut++ = pi->palette_map[GetRawPixel(lpbi,lpIn,x)];
	x++;
      }
    }
  } else {
    /* encode count pixel(s) */
    clr = pi->palette_map[GetRawPixel(lpbi,lpIn,x)];

    /* optimize end of line */
    if (x + count + 1 == lpbi->biWidth)
      count++;

    x += count;
    while (count > 0) {
      INT size = min(count, 255);

      *lpSizeImage += 2;
      count    -= size;
      *lpOut++  = size;
      *lpOut++  = clr;
    }
  }

  *ppOut = lpOut;

  return x;
}

LRESULT MSRLE32_CompressRLE4(const CodecInfo *pi, LPCBITMAPINFOHEADER lpbiIn,
                             const BYTE *lpIn, LPBITMAPINFOHEADER lpbiOut,
                             LPBYTE lpOut, BOOL isKey)
{
  LPWORD lpC;
  LONG   lLine, lInLine, lDist;
  LPBYTE lpOutStart = lpOut;

  /* pre-conditions */
  assert(pi != NULL && lpbiOut != NULL);
  assert(lpIn != NULL && lpOut != NULL);
  assert(pi->pCurFrame != NULL);

  lpC      = pi->pCurFrame;
  lDist    = QUALITY_to_DIST(pi->dwQuality);
  lInLine  = DIBWIDTHBYTES(*lpbiIn);
  lLine    = WIDTHBYTES(lpbiOut->biWidth * 16) / 2;

  lpbiOut->biSizeImage = 0;
  if (isKey) {
    /* keyframe -- convert internal frame to output format */
    INT x, y;

    for (y = 0; y < lpbiOut->biHeight; y++) {
      x = 0;

      do {
	x = MSRLE32_CompressRLE4Line(pi, NULL, lpC, lpbiIn, lpIn, lDist, x,
				     &lpOut, &lpbiOut->biSizeImage);
      } while (x < lpbiOut->biWidth);

      lpC   += lLine;
      lpIn  += lInLine;

      /* add EOL -- end of line */
      lpbiOut->biSizeImage += 2;
      *(LPWORD)lpOut = 0;
      lpOut += sizeof(WORD);
      assert(lpOut == (lpOutStart + lpbiOut->biSizeImage));
    }
  } else {
    /* delta-frame -- compute delta between last and this internal frame */
    LPWORD lpP;
    INT    x, y;
    INT    jumpx, jumpy;

    assert(pi->pPrevFrame != NULL);

    lpP   = pi->pPrevFrame;
    jumpy = 0;
    jumpx = -1;

    for (y = 0; y < lpbiOut->biHeight; y++) {
      x = 0;

      do {
	INT count, pos;

	if (jumpx == -1)
	  jumpx = x;
	for (count = 0, pos = x; pos < lpbiOut->biWidth; pos++, count++) {
	  if (ColorCmp(lpP[pos], lpC[pos]) > lDist)
	    break;
	}

	if (pos == lpbiOut->biWidth && count > 8) {
	  /* (count > 8) secures that we will save space */
	  jumpy++;
	  break;
	} else if (jumpy || jumpx != pos) {
	  /* time to jump */
	  assert(jumpx != -1);

	  if (pos < jumpx) {
	    /* can only jump in positive direction -- jump until EOL, EOL */
	    INT w = lpbiOut->biWidth - jumpx;

	    assert(jumpy > 0);
	    assert(w >= 4);

	    jumpx = 0;
	    jumpy--;
	    /* if (w % 255 == 2) then equal costs
	     * else if (w % 255 < 4 && we could encode all) then 2 bytes too expensive
	     * else it will be cheaper
	     */
	    while (w > 0) {
	      lpbiOut->biSizeImage += 4;
	      *lpOut++ = 0;
	      *lpOut++ = 2;
	      *lpOut   = min(w, 255);
	      w       -= *lpOut++;
	      *lpOut++ = 0;
	    }
	    /* add EOL -- end of line */
	    lpbiOut->biSizeImage += 2;
	    *((LPWORD)lpOut) = 0;
	    lpOut += sizeof(WORD);
	  }

	  /* FIXME: if (jumpy == 0 && could encode all) then jump too expensive */

	  /* write out real jump(s) */
	  while (jumpy || pos != jumpx) {
	    lpbiOut->biSizeImage += 4;
	    *lpOut++ = 0;
	    *lpOut++ = 2;
	    *lpOut   = min(pos - jumpx, 255);
	    x       += *lpOut;
	    jumpx   += *lpOut++;
	    *lpOut   = min(jumpy, 255);
	    jumpy   -= *lpOut++;
	  }

	  jumpy = 0;
	}

	jumpx = -1;

	if (x < lpbiOut->biWidth) {
	  /* skipped the 'same' things corresponding to previous frame */
	  x = MSRLE32_CompressRLE4Line(pi, lpP, lpC, lpbiIn, lpIn, lDist, x,
			       &lpOut, &lpbiOut->biSizeImage);
	}
      } while (x < lpbiOut->biWidth);

      lpP   += lLine;
      lpC   += lLine;
      lpIn  += lInLine;

      if (jumpy == 0) {
	assert(jumpx == -1);

	/* add EOL -- end of line */
	lpbiOut->biSizeImage += 2;
	*((LPWORD)lpOut) = 0;
        lpOut += sizeof(WORD);
	assert(lpOut == lpOutStart + lpbiOut->biSizeImage);
      }
    }

    /* add EOL -- will be changed to EOI */
    lpbiOut->biSizeImage += 2;
    *((LPWORD)lpOut) = 0;
    lpOut += sizeof(WORD);
  }

  /* change EOL to EOI -- end of image */
  lpOut[-1] = 1;
  assert(lpOut == (lpOutStart + lpbiOut->biSizeImage));

  return ICERR_OK;
}

LRESULT MSRLE32_CompressRLE8(const CodecInfo *pi, LPCBITMAPINFOHEADER lpbiIn,
                             const BYTE *lpIn, LPBITMAPINFOHEADER lpbiOut,
                             LPBYTE lpOut, BOOL isKey)
{
  LPWORD lpC;
  LONG   lDist, lInLine, lLine;
  LPBYTE lpOutStart = lpOut;

  assert(pi != NULL && lpbiOut != NULL);
  assert(lpIn != NULL && lpOut != NULL);
  assert(pi->pCurFrame != NULL);

  lpC     = pi->pCurFrame;
  lDist   = QUALITY_to_DIST(pi->dwQuality);
  lInLine = DIBWIDTHBYTES(*lpbiIn);
  lLine   = WIDTHBYTES(lpbiOut->biWidth * 16) / 2;

  lpbiOut->biSizeImage = 0;
  if (isKey) {
    /* keyframe -- convert internal frame to output format */
    INT x, y;

    for (y = 0; y < lpbiOut->biHeight; y++) {
      x = 0;

      do {
	x = MSRLE32_CompressRLE8Line(pi, NULL, lpC, lpbiIn, lpIn, lDist, x,
			     &lpOut, &lpbiOut->biSizeImage);
	assert(lpOut == (lpOutStart + lpbiOut->biSizeImage));
      } while (x < lpbiOut->biWidth);

      lpC  += lLine;
      lpIn += lInLine;

      /* add EOL -- end of line */
      lpbiOut->biSizeImage += 2;
      *((LPWORD)lpOut) = 0;
      lpOut += sizeof(WORD);
      assert(lpOut == (lpOutStart + lpbiOut->biSizeImage));
    }
  } else {
    /* delta-frame -- compute delta between last and this internal frame */
    LPWORD lpP;
    INT    x, y;
    INT    jumpx, jumpy;

    assert(pi->pPrevFrame != NULL);

    lpP   = pi->pPrevFrame;
    jumpx = -1;
    jumpy = 0;

    for (y = 0; y < lpbiOut->biHeight; y++) {
      x = 0;

      do {
	INT count, pos;

	if (jumpx == -1)
	  jumpx = x;
	for (count = 0, pos = x; pos < lpbiOut->biWidth; pos++, count++) {
	  if (ColorCmp(lpP[pos], lpC[pos]) > lDist)
	    break;
	}

	if (pos == lpbiOut->biWidth && count > 4) {
	  /* (count > 4) secures that we will save space */
	  jumpy++;
	  break;
	} else if (jumpy || jumpx != pos) {
	  /* time to jump */
	  assert(jumpx != -1);

	  if (pos < jumpx) {
	    /* can only jump in positive direction -- do an EOL then jump */
	    assert(jumpy > 0);

	    jumpx = 0;
	    jumpy--;

	    /* add EOL -- end of line */
	    lpbiOut->biSizeImage += 2;
	    *((LPWORD)lpOut) = 0;
	    lpOut += sizeof(WORD);
	    assert(lpOut == (lpOutStart + lpbiOut->biSizeImage));
	  }

	  /* FIXME: if (jumpy == 0 && could encode all) then jump too expensive */

	  /* write out real jump(s) */
	  while (jumpy || pos != jumpx) {
	    lpbiOut->biSizeImage += 4;
	    *lpOut++ = 0;
	    *lpOut++ = 2;
	    *lpOut   = min(pos - jumpx, 255);
	    jumpx   += *lpOut++;
	    *lpOut   = min(jumpy, 255);
	    jumpy   -= *lpOut++;
	  }
	  x = pos;

	  jumpy = 0;
	}

	jumpx = -1;

	if (x < lpbiOut->biWidth) {
	  /* skip the 'same' things corresponding to previous frame */
	  x = MSRLE32_CompressRLE8Line(pi, lpP, lpC, lpbiIn, lpIn, lDist, x,
			       &lpOut, &lpbiOut->biSizeImage);
	  assert(lpOut == (lpOutStart + lpbiOut->biSizeImage));
	}
      } while (x < lpbiOut->biWidth);

      lpP  += lLine;
      lpC  += lLine;
      lpIn += lInLine;

      if (jumpy == 0) {
	/* add EOL -- end of line */
	lpbiOut->biSizeImage += 2;
	*((LPWORD)lpOut) = 0;
	lpOut += sizeof(WORD);
	assert(lpOut == (lpOutStart + lpbiOut->biSizeImage));
      }
    }

    /* add EOL -- will be changed to EOI */
    lpbiOut->biSizeImage += 2;
    *((LPWORD)lpOut) = 0;
    lpOut += sizeof(WORD);
  }

  /* change EOL to EOI -- end of image */
  lpOut[-1] = 1;
  assert(lpOut == (lpOutStart + lpbiOut->biSizeImage));

  return ICERR_OK;
}

/*****************************************************************************/

static LRESULT MSRLE32_DecompressRLE4(const CodecInfo *pi, LPCBITMAPINFOHEADER lpbi,
				      const BYTE *lpIn, LPBYTE lpOut)
{
  int  bytes_per_pixel;
  int  line_size;
  int  pixel_ptr  = 0;
  int  i;
  BOOL bEndFlag   = FALSE;

  assert(pi != NULL);
  assert(lpbi != NULL && lpbi->biCompression == BI_RGB);
  assert(lpIn != NULL && lpOut != NULL);

  bytes_per_pixel = (lpbi->biBitCount + 1) / 8;
  line_size       = DIBWIDTHBYTES(*lpbi);

  do {
    BYTE code0, code1;

    code0 = *lpIn++;
    code1 = *lpIn++;

    if (code0 == 0) {
      int  extra_byte;

      switch (code1) {
      case  0: /* EOL - end of line  */
	pixel_ptr = 0;
	lpOut += line_size;
	break;
      case  1: /* EOI - end of image */
	bEndFlag = TRUE;
	break;
      case  2: /* skip */
	pixel_ptr += *lpIn++ * bytes_per_pixel;
	lpOut     += *lpIn++ * line_size;
	if (pixel_ptr >= lpbi->biWidth * bytes_per_pixel) {
	  pixel_ptr = 0;
	  lpOut    += line_size;
	}
	break;
      default: /* absolute mode */
	extra_byte = (((code1 + 1) & (~1)) / 2) & 0x01;

	if (pixel_ptr/bytes_per_pixel + code1 > lpbi->biWidth)
	  return ICERR_ERROR;

	code0 = code1;
	for (i = 0; i < code0 / 2; i++) {
	  if (bytes_per_pixel == 1) {
	    code1 = lpIn[i];
	    lpOut[pixel_ptr++] = pi->palette_map[(code1 >> 4)];
	    if (2 * i + 1 <= code0)
	      lpOut[pixel_ptr++] = pi->palette_map[(code1 & 0x0F)];
	  } else if (bytes_per_pixel == 2) {
	    code1 = lpIn[i] >> 4;
	    lpOut[pixel_ptr++] = pi->palette_map[code1 * 2 + 0];
	    lpOut[pixel_ptr++] = pi->palette_map[code1 * 2 + 1];

	    if (2 * i + 1 <= code0) {
	      code1 = lpIn[i] & 0x0F;
	      lpOut[pixel_ptr++] = pi->palette_map[code1 * 2 + 0];
	      lpOut[pixel_ptr++] = pi->palette_map[code1 * 2 + 1];
	    }
	  } else {
	    code1 = lpIn[i] >> 4;
	    lpOut[pixel_ptr + 0] = pi->palette_map[code1 * 4 + 0];
	    lpOut[pixel_ptr + 1] = pi->palette_map[code1 * 4 + 1];
	    lpOut[pixel_ptr + 2] = pi->palette_map[code1 * 4 + 2];
	    pixel_ptr += bytes_per_pixel;

	    if (2 * i + 1 <= code0) {
	      code1 = lpIn[i] & 0x0F;
	      lpOut[pixel_ptr + 0] = pi->palette_map[code1 * 4 + 0];
	      lpOut[pixel_ptr + 1] = pi->palette_map[code1 * 4 + 1];
	      lpOut[pixel_ptr + 2] = pi->palette_map[code1 * 4 + 2];
	      pixel_ptr += bytes_per_pixel;
	    }
	  }
	}
	if (code0 & 0x01) {
	  if (bytes_per_pixel == 1) {
	    code1 = lpIn[i];
	    lpOut[pixel_ptr++] = pi->palette_map[(code1 >> 4)];
	  } else if (bytes_per_pixel == 2) {
	    code1 = lpIn[i] >> 4;
	    lpOut[pixel_ptr++] = pi->palette_map[code1 * 2 + 0];
	    lpOut[pixel_ptr++] = pi->palette_map[code1 * 2 + 1];
	  } else {
	    code1 = lpIn[i] >> 4;
	    lpOut[pixel_ptr + 0] = pi->palette_map[code1 * 4 + 0];
	    lpOut[pixel_ptr + 1] = pi->palette_map[code1 * 4 + 1];
	    lpOut[pixel_ptr + 2] = pi->palette_map[code1 * 4 + 2];
	    pixel_ptr += bytes_per_pixel;
	  }
	  lpIn++;
	}
	lpIn += code0 / 2;

	/* if the RLE code is odd, skip a byte in the stream */
	if (extra_byte)
	  lpIn++;
      };
    } else {
      /* coded mode */
      if (pixel_ptr/bytes_per_pixel + code0 > lpbi->biWidth)
	return ICERR_ERROR;

      if (bytes_per_pixel == 1) {
	BYTE c1 = pi->palette_map[(code1 >> 4)];
	BYTE c2 = pi->palette_map[(code1 & 0x0F)];

	for (i = 0; i < code0; i++) {
	  if ((i & 1) == 0)
	    lpOut[pixel_ptr++] = c1;
	  else
	    lpOut[pixel_ptr++] = c2;
	}
      } else if (bytes_per_pixel == 2) {
	BYTE hi1 = pi->palette_map[(code1 >> 4) * 2 + 0];
	BYTE lo1 = pi->palette_map[(code1 >> 4) * 2 + 1];

	BYTE hi2 = pi->palette_map[(code1 & 0x0F) * 2 + 0];
	BYTE lo2 = pi->palette_map[(code1 & 0x0F) * 2 + 1];

	for (i = 0; i < code0; i++) {
	  if ((i & 1) == 0) {
	    lpOut[pixel_ptr++] = hi1;
	    lpOut[pixel_ptr++] = lo1;
	  } else {
	    lpOut[pixel_ptr++] = hi2;
	    lpOut[pixel_ptr++] = lo2;
	  }
	}
      } else {
	BYTE b1 = pi->palette_map[(code1 >> 4) * 4 + 0];
	BYTE g1 = pi->palette_map[(code1 >> 4) * 4 + 1];
	BYTE r1 = pi->palette_map[(code1 >> 4) * 4 + 2];

	BYTE b2 = pi->palette_map[(code1 & 0x0F) * 4 + 0];
	BYTE g2 = pi->palette_map[(code1 & 0x0F) * 4 + 1];
	BYTE r2 = pi->palette_map[(code1 & 0x0F) * 4 + 2];

	for (i = 0; i < code0; i++) {
	  if ((i & 1) == 0) {
	    lpOut[pixel_ptr + 0] = b1;
	    lpOut[pixel_ptr + 1] = g1;
	    lpOut[pixel_ptr + 2] = r1;
	  } else {
	    lpOut[pixel_ptr + 0] = b2;
	    lpOut[pixel_ptr + 1] = g2;
	    lpOut[pixel_ptr + 2] = r2;
	  }
	  pixel_ptr += bytes_per_pixel;
	}
      }
    }
  } while (! bEndFlag);

  return ICERR_OK;
}

static LRESULT MSRLE32_DecompressRLE8(const CodecInfo *pi, LPCBITMAPINFOHEADER lpbi,
				      const BYTE *lpIn, LPBYTE lpOut)
{
  int  bytes_per_pixel;
  int  line_size;
  int  pixel_ptr  = 0;
  BOOL bEndFlag   = FALSE;

  assert(pi != NULL);
  assert(lpbi != NULL && lpbi->biCompression == BI_RGB);
  assert(lpIn != NULL && lpOut != NULL);

  bytes_per_pixel = (lpbi->biBitCount + 1) / 8;
  line_size       = DIBWIDTHBYTES(*lpbi);

  do {
    BYTE code0, code1;

    code0 = *lpIn++;
    code1 = *lpIn++;

    if (code0 == 0) {
      int  extra_byte;

      switch (code1) {
      case  0: /* EOL - end of line  */
	pixel_ptr = 0;
	lpOut += line_size;
	break;
      case  1: /* EOI - end of image */
	bEndFlag = TRUE;
	break;
      case  2: /* skip */
	pixel_ptr += *lpIn++ * bytes_per_pixel;
	lpOut     += *lpIn++ * line_size;
	if (pixel_ptr >= lpbi->biWidth * bytes_per_pixel) {
	  pixel_ptr = 0;
	  lpOut    += line_size;
	}
	break;
      default: /* absolute mode */
	if (pixel_ptr/bytes_per_pixel + code1 > lpbi->biWidth) {
          WARN("aborted absolute: (%d=%d/%d+%d) > %d\n",pixel_ptr/bytes_per_pixel + code1,pixel_ptr,bytes_per_pixel,code1,lpbi->biWidth);
	  return ICERR_ERROR;
	}
	extra_byte = code1 & 0x01;

	code0 = code1;
	while (code0--) {
	  code1 = *lpIn++;
	  if (bytes_per_pixel == 1) {
	    lpOut[pixel_ptr] = pi->palette_map[code1];
	  } else if (bytes_per_pixel == 2) {
	    lpOut[pixel_ptr + 0] = pi->palette_map[code1 * 2 + 0];
	    lpOut[pixel_ptr + 1] = pi->palette_map[code1 * 2 + 1];
	  } else {
	    lpOut[pixel_ptr + 0] = pi->palette_map[code1 * 4 + 0];
	    lpOut[pixel_ptr + 1] = pi->palette_map[code1 * 4 + 1];
	    lpOut[pixel_ptr + 2] = pi->palette_map[code1 * 4 + 2];
	  }
	  pixel_ptr += bytes_per_pixel;
	}

	/* if the RLE code is odd, skip a byte in the stream */
	if (extra_byte)
	  lpIn++;
      };
    } else {
      /* coded mode */
      if (pixel_ptr/bytes_per_pixel + code0 > lpbi->biWidth) {
	WARN("aborted coded: (%d=%d/%d+%d) > %d\n",pixel_ptr/bytes_per_pixel + code1,pixel_ptr,bytes_per_pixel,code1,lpbi->biWidth);
	return ICERR_ERROR;
      }

      if (bytes_per_pixel == 1) {
	code1 = pi->palette_map[code1];
	while (code0--)
	  lpOut[pixel_ptr++] = code1;
      } else if (bytes_per_pixel == 2) {
	BYTE hi = pi->palette_map[code1 * 2 + 0];
	BYTE lo = pi->palette_map[code1 * 2 + 1];

	while (code0--) {
	  lpOut[pixel_ptr + 0] = hi;
	  lpOut[pixel_ptr + 1] = lo;
	  pixel_ptr += bytes_per_pixel;
	}
      } else {
	BYTE r = pi->palette_map[code1 * 4 + 2];
	BYTE g = pi->palette_map[code1 * 4 + 1];
	BYTE b = pi->palette_map[code1 * 4 + 0];

	while (code0--) {
	  lpOut[pixel_ptr + 0] = b;
	  lpOut[pixel_ptr + 1] = g;
	  lpOut[pixel_ptr + 2] = r;
	  pixel_ptr += bytes_per_pixel;
	}
      }
    }
  } while (! bEndFlag);

  return ICERR_OK;
}

/*****************************************************************************/

static CodecInfo* Open(LPICOPEN icinfo)
{
  CodecInfo* pi = NULL;

  if (icinfo == NULL) {
    TRACE("(NULL)\n");
    return (LPVOID)0xFFFF0000;
  }

  if (compare_fourcc(icinfo->fccType, ICTYPE_VIDEO)) return NULL;

  TRACE("(%p = {%u,0x%08X(%4.4s),0x%08X(%4.4s),0x%X,0x%X,...})\n", icinfo,
	icinfo->dwSize,	icinfo->fccType, (char*)&icinfo->fccType,
	icinfo->fccHandler, (char*)&icinfo->fccHandler,
	icinfo->dwVersion,icinfo->dwFlags);

  switch (icinfo->fccHandler) {
  case FOURCC_RLE:
  case FOURCC_RLE4:
  case FOURCC_RLE8:
  case FOURCC_MRLE:
    break;
  case mmioFOURCC('m','r','l','e'):
    icinfo->fccHandler = FOURCC_MRLE;
    break;
  default:
    WARN("unknown FOURCC = 0x%08X(%4.4s) !\n",
	 icinfo->fccHandler,(char*)&icinfo->fccHandler);
    return NULL;
  }

  pi = LocalAlloc(LPTR, sizeof(CodecInfo));

  if (pi != NULL) {
    pi->fccHandler  = icinfo->fccHandler;

    pi->bCompress   = FALSE;
    pi->dwQuality   = MSRLE32_DEFAULTQUALITY;
    pi->nPrevFrame  = -1;
    pi->pPrevFrame  = pi->pCurFrame = NULL;

    pi->bDecompress = FALSE;
    pi->palette_map = NULL;
  }

  icinfo->dwError = (pi != NULL ? ICERR_OK : ICERR_MEMORY);

  return pi;
}

static LRESULT Close(CodecInfo *pi)
{
  TRACE("(%p)\n", pi);

  /* pre-condition */
  assert(pi != NULL);

  if (pi->pPrevFrame != NULL || pi->pCurFrame != NULL)
    CompressEnd(pi);

  LocalFree(pi);
  return 1;
}

static LRESULT GetInfo(const CodecInfo *pi, ICINFO *icinfo, DWORD dwSize)
{
  /* pre-condition */
  assert(pi != NULL);

  /* check parameters */
  if (icinfo == NULL)
    return sizeof(ICINFO);
  if (dwSize < sizeof(ICINFO))
    return 0;

  icinfo->dwSize       = sizeof(ICINFO);
  icinfo->fccType      = ICTYPE_VIDEO;
  icinfo->fccHandler   = (pi != NULL ? pi->fccHandler : FOURCC_MRLE);
  icinfo->dwFlags      = VIDCF_QUALITY | VIDCF_TEMPORAL | VIDCF_CRUNCH | VIDCF_FASTTEMPORALC;
  icinfo->dwVersion    = ICVERSION;
  icinfo->dwVersionICM = ICVERSION;

  LoadStringW(MSRLE32_hModule, IDS_NAME, icinfo->szName, sizeof(icinfo->szName)/sizeof(WCHAR));
  LoadStringW(MSRLE32_hModule, IDS_DESCRIPTION, icinfo->szDescription, sizeof(icinfo->szDescription)/sizeof(WCHAR));

  return sizeof(ICINFO);
}

static LRESULT SetQuality(CodecInfo *pi, LONG lQuality)
{
  /* pre-condition */
  assert(pi != NULL);

  if (lQuality == -1)
    lQuality = MSRLE32_DEFAULTQUALITY;
  else if (ICQUALITY_LOW > lQuality || lQuality > ICQUALITY_HIGH)
    return ICERR_BADPARAM;

  pi->dwQuality = (DWORD)lQuality;

  return ICERR_OK;
}

static LRESULT Configure(const CodecInfo *pi, HWND hWnd)
{
  /* pre-condition */
  assert(pi != NULL);

  /* FIXME */
  return ICERR_OK;
}

static LRESULT About(CodecInfo *pi, HWND hWnd)
{
  WCHAR szTitle[20];
  WCHAR szAbout[128];

  /* pre-condition */
  assert(MSRLE32_hModule != 0);

  LoadStringW(MSRLE32_hModule, IDS_NAME, szTitle, sizeof(szTitle)/sizeof(szTitle[0]));
  LoadStringW(MSRLE32_hModule, IDS_ABOUT, szAbout, sizeof(szAbout)/sizeof(szAbout[0]));

  MessageBoxW(hWnd, szAbout, szTitle, MB_OK|MB_ICONINFORMATION);

  return ICERR_OK;
}

static LRESULT CompressGetFormat(CodecInfo *pi, LPCBITMAPINFOHEADER lpbiIn,
				 LPBITMAPINFOHEADER lpbiOut)
{
  LRESULT size;

  TRACE("(%p,%p,%p)\n",pi,lpbiIn,lpbiOut);

  /* pre-condition */
  assert(pi != NULL);

  /* check parameters -- need at least input format */
  if (lpbiIn == NULL) {
    if (lpbiOut != NULL)
      return ICERR_BADPARAM;
    return 0;
  }

  /* handle unsupported input format */
  if (CompressQuery(pi, lpbiIn, NULL) != ICERR_OK)
    return (lpbiOut == NULL ? ICERR_BADFORMAT : 0);

  assert(0 < lpbiIn->biBitCount && lpbiIn->biBitCount <= 8);

  switch (pi->fccHandler) {
  case FOURCC_RLE4:
    size = 1 << 4;
    break;
  case FOURCC_RLE8:
    size = 1 << 8;
    break;
  case FOURCC_RLE:
  case FOURCC_MRLE:
    size = (lpbiIn->biBitCount <= 4 ? 1 << 4 : 1 << 8);
    break;
  default:
    return ICERR_ERROR;
  }

  if (lpbiIn->biClrUsed != 0)
    size = lpbiIn->biClrUsed;

  size = sizeof(BITMAPINFOHEADER) + size * sizeof(RGBQUAD);

  if (lpbiOut != NULL) {
    lpbiOut->biSize          = sizeof(BITMAPINFOHEADER);
    lpbiOut->biWidth         = lpbiIn->biWidth;
    lpbiOut->biHeight        = lpbiIn->biHeight;
    lpbiOut->biPlanes        = 1;
    if (pi->fccHandler == FOURCC_RLE4 ||
	lpbiIn->biBitCount <= 4) {
      lpbiOut->biCompression = BI_RLE4;
      lpbiOut->biBitCount    = 4;
    } else {
      lpbiOut->biCompression = BI_RLE8;
      lpbiOut->biBitCount    = 8;
    }
    lpbiOut->biSizeImage     = MSRLE32_GetMaxCompressedSize(lpbiOut);
    lpbiOut->biXPelsPerMeter = lpbiIn->biXPelsPerMeter;
    lpbiOut->biYPelsPerMeter = lpbiIn->biYPelsPerMeter;
    if (lpbiIn->biClrUsed == 0)
      size = 1<<lpbiIn->biBitCount;
    else
      size = lpbiIn->biClrUsed;
    lpbiOut->biClrUsed       = min(size, 1 << lpbiOut->biBitCount);
    lpbiOut->biClrImportant  = 0;

    memcpy((LPBYTE)lpbiOut + lpbiOut->biSize,
	   (const BYTE*)lpbiIn + lpbiIn->biSize, lpbiOut->biClrUsed * sizeof(RGBQUAD));

    return ICERR_OK;
  } else
    return size;
}

static LRESULT CompressGetSize(CodecInfo *pi, LPCBITMAPINFOHEADER lpbiIn,
			       LPCBITMAPINFOHEADER lpbiOut)
{
  /* pre-condition */
  assert(pi != NULL);

  TRACE("(%p,%p,%p)\n",pi,lpbiIn,lpbiOut);

  /* check parameter -- need at least one format */
  if (lpbiIn == NULL && lpbiOut == NULL)
    return 0;
  /* check if the given format is supported */
  if (CompressQuery(pi, lpbiIn, lpbiOut) != ICERR_OK)
    return 0;

  /* the worst case is coding the complete image in absolute mode. */
  if (lpbiIn)
    return MSRLE32_GetMaxCompressedSize(lpbiIn);
  else
    return MSRLE32_GetMaxCompressedSize(lpbiOut);
}

static LRESULT CompressQuery(const CodecInfo *pi, LPCBITMAPINFOHEADER lpbiIn,
			     LPCBITMAPINFOHEADER lpbiOut)
{
  /* pre-condition */
  assert(pi != NULL);

  /* need at least one format */
  if (lpbiIn == NULL && lpbiOut == NULL)
    return ICERR_BADPARAM;

  /* check input format if given */
  if (lpbiIn != NULL) {
    if (!isSupportedDIB(lpbiIn))
      return ICERR_BADFORMAT;

    /* for 4-bit need an even width */
    if (lpbiIn->biBitCount <= 4 && (lpbiIn->biWidth % 2))
      return ICERR_BADFORMAT;

    if (pi->fccHandler == FOURCC_RLE4 && lpbiIn->biBitCount > 4)
      return ICERR_UNSUPPORTED;
    else if (lpbiIn->biBitCount > 8)
      return ICERR_UNSUPPORTED;
  }

  /* check output format if given */
  if (lpbiOut != NULL) {
    if (!isSupportedMRLE(lpbiOut))
      return ICERR_BADFORMAT;

    if (lpbiIn != NULL) {
      if (lpbiIn->biWidth  != lpbiOut->biWidth)
	return ICERR_UNSUPPORTED;
      if (lpbiIn->biHeight != lpbiOut->biHeight)
	return ICERR_UNSUPPORTED;
      if (lpbiIn->biBitCount > lpbiOut->biBitCount)
	return ICERR_UNSUPPORTED;
    }
  }

  return ICERR_OK;
}

static LRESULT CompressBegin(CodecInfo *pi, LPCBITMAPINFOHEADER lpbiIn,
			     LPCBITMAPINFOHEADER lpbiOut)
{
  const RGBQUAD *rgbIn;
  const RGBQUAD *rgbOut;
  UINT   i;
  size_t size;

  TRACE("(%p,%p,%p)\n",pi,lpbiIn,lpbiOut);

  /* pre-condition */
  assert(pi != NULL);

  /* check parameters -- need both formats */
  if (lpbiIn == NULL || lpbiOut == NULL)
    return ICERR_BADPARAM;
  /* And both must be supported */
  if (CompressQuery(pi, lpbiIn, lpbiOut) != ICERR_OK)
    return ICERR_BADFORMAT;

  /* FIXME: cannot compress and decompress at same time! */
  if (pi->bDecompress) {
    FIXME("cannot compress and decompress at same time!\n");
    return ICERR_ERROR;
  }

  if (pi->bCompress)
    CompressEnd(pi);

  size = WIDTHBYTES(lpbiOut->biWidth * 16) / 2 * lpbiOut->biHeight;
  pi->pPrevFrame = GlobalLock(GlobalAlloc(GPTR, size * sizeof(WORD)));
  if (pi->pPrevFrame == NULL)
    return ICERR_MEMORY;
  pi->pCurFrame = GlobalLock(GlobalAlloc(GPTR, size * sizeof(WORD)));
  if (pi->pCurFrame == NULL) {
    CompressEnd(pi);
    return ICERR_MEMORY;
  }
  pi->nPrevFrame = -1;
  pi->bCompress  = TRUE;

  rgbIn  = (const RGBQUAD*)((const BYTE*)lpbiIn  + lpbiIn->biSize);
  rgbOut = (const RGBQUAD*)((const BYTE*)lpbiOut + lpbiOut->biSize);

  switch (lpbiOut->biBitCount) {
  case 4:
  case 8:
    pi->palette_map = LocalAlloc(LPTR, lpbiIn->biClrUsed);
    if (pi->palette_map == NULL) {
      CompressEnd(pi);
      return ICERR_MEMORY;
    }

    for (i = 0; i < lpbiIn->biClrUsed; i++) {
      pi->palette_map[i] = MSRLE32_GetNearestPaletteIndex(lpbiOut->biClrUsed, rgbOut, rgbIn[i]);
    }
    break;
  };

  return ICERR_OK;
}

static LRESULT Compress(CodecInfo *pi, ICCOMPRESS* lpic, DWORD dwSize)
{
  int i;

  TRACE("(%p,%p,%u)\n",pi,lpic,dwSize);

  /* pre-condition */
  assert(pi != NULL);

  /* check parameters */
  if (lpic == NULL || dwSize < sizeof(ICCOMPRESS))
    return ICERR_BADPARAM;
  if (!lpic->lpbiOutput || !lpic->lpOutput ||
      !lpic->lpbiInput  || !lpic->lpInput)
    return ICERR_BADPARAM;

  TRACE("lpic={0x%X,%p,%p,%p,%p,%p,%p,%d,%u,%u,%p,%p}\n",lpic->dwFlags,lpic->lpbiOutput,lpic->lpOutput,lpic->lpbiInput,lpic->lpInput,lpic->lpckid,lpic->lpdwFlags,lpic->lFrameNum,lpic->dwFrameSize,lpic->dwQuality,lpic->lpbiPrev,lpic->lpPrev);

  if (! pi->bCompress) {
    LRESULT hr = CompressBegin(pi, lpic->lpbiInput, lpic->lpbiOutput);
    if (hr != ICERR_OK)
      return hr;
  } else if (CompressQuery(pi, lpic->lpbiInput, lpic->lpbiOutput) != ICERR_OK)
    return ICERR_BADFORMAT;

  if (lpic->lFrameNum >= pi->nPrevFrame + 1) {
    /* we continue in the sequence so we need to initialize 
     * our internal framedata */

    computeInternalFrame(pi, lpic->lpbiInput, lpic->lpInput);
  } else if (lpic->lFrameNum == pi->nPrevFrame) {
    /* Oops, compress same frame again ? Okay, as you wish.
     * No need to recompute internal framedata, because we only swapped buffers */
    LPWORD pTmp = pi->pPrevFrame;

    pi->pPrevFrame = pi->pCurFrame;
    pi->pCurFrame  = pTmp;
  } else if ((lpic->dwFlags & ICCOMPRESS_KEYFRAME) == 0) {
    LPWORD pTmp;

    WARN(": prev=%d cur=%d gone back? -- untested\n",pi->nPrevFrame,lpic->lFrameNum);
    if (lpic->lpbiPrev == NULL || lpic->lpPrev == NULL)
      return ICERR_GOTOKEYFRAME; /* Need a keyframe if you go back */
    if (CompressQuery(pi, lpic->lpbiPrev, lpic->lpbiOutput) != ICERR_OK)
      return ICERR_BADFORMAT;

    WARN(": prev=%d cur=%d compute swapped -- untested\n",pi->nPrevFrame,lpic->lFrameNum);
    computeInternalFrame(pi, lpic->lpbiPrev, lpic->lpPrev);

    /* swap buffers for current and previous frame */
    /* Don't free and alloc new -- costs to much time and they are of equal size ! */
    pTmp = pi->pPrevFrame;
    pi->pPrevFrame = pi->pCurFrame;
    pi->pCurFrame  = pTmp;
    pi->nPrevFrame = lpic->lFrameNum;
  }

  for (i = 0; i < 3; i++) {
    SetQuality(pi, lpic->dwQuality);

    lpic->lpbiOutput->biSizeImage = 0;

    if (lpic->lpbiOutput->biBitCount == 4)
      MSRLE32_CompressRLE4(pi, lpic->lpbiInput, lpic->lpInput,
                   lpic->lpbiOutput, lpic->lpOutput, (lpic->dwFlags & ICCOMPRESS_KEYFRAME) != 0);
    else
      MSRLE32_CompressRLE8(pi, lpic->lpbiInput, lpic->lpInput,
                   lpic->lpbiOutput, lpic->lpOutput, (lpic->dwFlags & ICCOMPRESS_KEYFRAME) != 0);

    if (lpic->dwFrameSize == 0 ||
	lpic->lpbiOutput->biSizeImage < lpic->dwFrameSize)
      break;

    if ((*lpic->lpdwFlags & ICCOMPRESS_KEYFRAME) == 0) {
      if (lpic->lpbiOutput->biBitCount == 4)
        MSRLE32_CompressRLE4(pi, lpic->lpbiInput, lpic->lpInput,
                             lpic->lpbiOutput, lpic->lpOutput, TRUE);
      else
        MSRLE32_CompressRLE8(pi, lpic->lpbiInput, lpic->lpInput,
                             lpic->lpbiOutput, lpic->lpOutput, TRUE);

      if (lpic->dwFrameSize == 0 ||
	  lpic->lpbiOutput->biSizeImage < lpic->dwFrameSize) {
	WARN("switched to keyframe, was small enough!\n");
	*lpic->lpdwFlags |= ICCOMPRESS_KEYFRAME;
	*lpic->lpckid    = MAKEAVICKID(cktypeDIBbits,
				       StreamFromFOURCC(*lpic->lpckid));
	break;
      }
    }

    if (lpic->dwQuality < 1000)
      break;

    lpic->dwQuality -= 1000; /* reduce quality by 10% */
  }

  { /* swap buffer for current and previous frame */
    /* Don't free and alloc new -- costs to much time and they are of equal size ! */
    LPWORD pTmp = pi->pPrevFrame;

    pi->pPrevFrame = pi->pCurFrame;
    pi->pCurFrame  = pTmp;
    pi->nPrevFrame = lpic->lFrameNum;
  }

  return ICERR_OK;
}

static LRESULT CompressEnd(CodecInfo *pi)
{
  TRACE("(%p)\n",pi);

  if (pi != NULL) {
    if (pi->pPrevFrame != NULL)
    {
      GlobalUnlock(GlobalHandle(pi->pPrevFrame));
      GlobalFree(GlobalHandle(pi->pPrevFrame));
    }
    if (pi->pCurFrame != NULL)
    {
      GlobalUnlock(GlobalHandle(pi->pCurFrame));
      GlobalFree(GlobalHandle(pi->pCurFrame));
    }
    pi->pPrevFrame = NULL;
    pi->pCurFrame  = NULL;
    pi->nPrevFrame = -1;
    pi->bCompress  = FALSE;
  }

  return ICERR_OK;
}

static LRESULT DecompressGetFormat(CodecInfo *pi, LPCBITMAPINFOHEADER lpbiIn,
				   LPBITMAPINFOHEADER lpbiOut)
{
  DWORD size;

  TRACE("(%p,%p,%p)\n",pi,lpbiIn,lpbiOut);

  /* pre-condition */
  assert(pi != NULL);

  if (lpbiIn == NULL)
    return (lpbiOut != NULL ? ICERR_BADPARAM : 0);

  if (DecompressQuery(pi, lpbiIn, NULL) != ICERR_OK)
    return (lpbiOut != NULL ? ICERR_BADFORMAT : 0);

  size = lpbiIn->biSize;

  if (lpbiIn->biBitCount <= 8)
    size += lpbiIn->biClrUsed * sizeof(RGBQUAD);

  if (lpbiOut != NULL) {
    memcpy(lpbiOut, lpbiIn, size);
    lpbiOut->biCompression  = BI_RGB;
    lpbiOut->biSizeImage    = DIBWIDTHBYTES(*lpbiOut) * lpbiOut->biHeight;

    return ICERR_OK;
  } else
    return size;
}

static LRESULT DecompressQuery(CodecInfo *pi, LPCBITMAPINFOHEADER lpbiIn,
			       LPCBITMAPINFOHEADER lpbiOut)
{
  LRESULT hr = ICERR_OK;

  TRACE("(%p,%p,%p)\n",pi,lpbiIn,lpbiOut);

  /* pre-condition */
  assert(pi != NULL);

  /* need at least one format */
  if (lpbiIn == NULL && lpbiOut == NULL)
    return ICERR_BADPARAM;

  /* check input format if given */
  if (lpbiIn != NULL) {
    if (!isSupportedMRLE(lpbiIn))
      return ICERR_BADFORMAT;
  }

  /* check output format if given */
  if (lpbiOut != NULL) {
    if (!isSupportedDIB(lpbiOut))
      hr = ICERR_BADFORMAT;

    if (lpbiIn != NULL) {
      if (lpbiIn->biWidth  != lpbiOut->biWidth)
	hr = ICERR_UNSUPPORTED;
      if (lpbiIn->biHeight != lpbiOut->biHeight)
	hr = ICERR_UNSUPPORTED;
      if (lpbiIn->biBitCount > lpbiOut->biBitCount)
	hr = ICERR_UNSUPPORTED;
    }
  }

  return hr;
}

static LRESULT DecompressBegin(CodecInfo *pi, LPCBITMAPINFOHEADER lpbiIn,
			       LPCBITMAPINFOHEADER lpbiOut)
{
  const RGBQUAD *rgbIn;
  const RGBQUAD *rgbOut;
  UINT  i;

  TRACE("(%p,%p,%p)\n",pi,lpbiIn,lpbiOut);

  /* pre-condition */
  assert(pi != NULL);

  /* check parameters */
  if (lpbiIn == NULL || lpbiOut == NULL)
    return ICERR_BADPARAM;
  if (DecompressQuery(pi, lpbiIn, lpbiOut) != ICERR_OK)
    return ICERR_BADFORMAT;

  /* FIXME: cannot compress and decompress at a time! */
  if (pi->bCompress) {
    FIXME("cannot compress and decompress at same time!\n");
    return ICERR_ERROR;
  }

  if (pi->bDecompress)
    DecompressEnd(pi);

  rgbIn  = (const RGBQUAD*)((const BYTE*)lpbiIn  + lpbiIn->biSize);
  rgbOut = (const RGBQUAD*)((const BYTE*)lpbiOut + lpbiOut->biSize);

  switch (lpbiOut->biBitCount) {
  case 4:
  case 8:
    pi->palette_map = LocalAlloc(LPTR, lpbiIn->biClrUsed);
    if (pi->palette_map == NULL)
      return ICERR_MEMORY;

    for (i = 0; i < lpbiIn->biClrUsed; i++) {
      pi->palette_map[i] = MSRLE32_GetNearestPaletteIndex(lpbiOut->biClrUsed, rgbOut, rgbIn[i]);
    }
    break;
  case 15:
  case 16:
    pi->palette_map = LocalAlloc(LPTR, lpbiIn->biClrUsed * 2);
    if (pi->palette_map == NULL)
      return ICERR_MEMORY;

    for (i = 0; i < lpbiIn->biClrUsed; i++) {
      WORD color;

      if (lpbiOut->biBitCount == 15)
	color = ((rgbIn[i].rgbRed >> 3) << 10)
	  | ((rgbIn[i].rgbGreen >> 3) << 5) | (rgbIn[i].rgbBlue >> 3);
      else
	color = ((rgbIn[i].rgbRed >> 3) << 11)
	  | ((rgbIn[i].rgbGreen >> 3) << 5) | (rgbIn[i].rgbBlue >> 3);

      pi->palette_map[i * 2 + 1] = color >> 8;
      pi->palette_map[i * 2 + 0] = color & 0xFF;
    };
    break;
  case 24:
  case 32:
    pi->palette_map = LocalAlloc(LPTR, lpbiIn->biClrUsed * sizeof(RGBQUAD));
    if (pi->palette_map == NULL)
      return ICERR_MEMORY;
    memcpy(pi->palette_map, rgbIn, lpbiIn->biClrUsed * sizeof(RGBQUAD));
    break;
  };

  pi->bDecompress = TRUE;

  return ICERR_OK;
}

static LRESULT Decompress(CodecInfo *pi, ICDECOMPRESS *pic, DWORD dwSize)
{
  TRACE("(%p,%p,%u)\n",pi,pic,dwSize);

  /* pre-condition */
  assert(pi != NULL);

  /* check parameters */
  if (pic == NULL)
    return ICERR_BADPARAM;
  if (pic->lpbiInput == NULL || pic->lpInput == NULL ||
      pic->lpbiOutput == NULL || pic->lpOutput == NULL)
    return ICERR_BADPARAM;

  /* check formats */
  if (! pi->bDecompress) {
    LRESULT hr = DecompressBegin(pi, pic->lpbiInput, pic->lpbiOutput);
    if (hr != ICERR_OK)
      return hr;
  } else if (DecompressQuery(pi, pic->lpbiInput, pic->lpbiOutput) != ICERR_OK)
    return ICERR_BADFORMAT;

  assert(pic->lpbiInput->biWidth  == pic->lpbiOutput->biWidth);
  assert(pic->lpbiInput->biHeight == pic->lpbiOutput->biHeight);

  pic->lpbiOutput->biSizeImage = DIBWIDTHBYTES(*pic->lpbiOutput) * pic->lpbiOutput->biHeight;
  if (pic->lpbiInput->biBitCount == 4)
    return MSRLE32_DecompressRLE4(pi, pic->lpbiOutput, pic->lpInput, pic->lpOutput);
  else
    return MSRLE32_DecompressRLE8(pi, pic->lpbiOutput, pic->lpInput, pic->lpOutput);
}

static LRESULT DecompressEnd(CodecInfo *pi)
{
  TRACE("(%p)\n",pi);

  /* pre-condition */
  assert(pi != NULL);

  pi->bDecompress = FALSE;

  if (pi->palette_map != NULL) {
    LocalFree(pi->palette_map);
    pi->palette_map = NULL;
  }

  return ICERR_OK;
}

static LRESULT DecompressGetPalette(CodecInfo *pi, LPCBITMAPINFOHEADER lpbiIn,
				    LPBITMAPINFOHEADER lpbiOut)
{
  int size;

  TRACE("(%p,%p,%p)\n",pi,lpbiIn,lpbiOut);

  /* pre-condition */
  assert(pi != NULL);

  /* check parameters */
  if (lpbiIn == NULL || lpbiOut == NULL)
    return ICERR_BADPARAM;

  if (DecompressQuery(pi, lpbiIn, lpbiOut) != ICERR_OK)
    return ICERR_BADFORMAT;

  if (lpbiOut->biBitCount > 8)
    return ICERR_ERROR;

  if (lpbiIn->biBitCount <= 8) {
    if (lpbiIn->biClrUsed > 0)
      size = lpbiIn->biClrUsed;
    else
      size = (1 << lpbiIn->biBitCount);

    lpbiOut->biClrUsed = size;

    memcpy((LPBYTE)lpbiOut + lpbiOut->biSize, (const BYTE*)lpbiIn + lpbiIn->biSize, size * sizeof(RGBQUAD));
  } /* else could never occur ! */

  return ICERR_OK;
}

/* DriverProc - entry point for an installable driver */
LRESULT CALLBACK MSRLE32_DriverProc(DWORD_PTR dwDrvID, HDRVR hDrv, UINT uMsg,
				    LPARAM lParam1, LPARAM lParam2)
{
  CodecInfo *pi = (CodecInfo*)dwDrvID;

  TRACE("(%lx,%p,0x%04X,0x%08lX,0x%08lX)\n", dwDrvID, hDrv, uMsg, lParam1, lParam2);

  switch (uMsg) {
    /* standard driver messages */
  case DRV_LOAD:
    return DRVCNF_OK;
  case DRV_OPEN:
      return (LRESULT)Open((ICOPEN*)lParam2);
  case DRV_CLOSE:
    if (dwDrvID != 0xFFFF0000 && (LPVOID)dwDrvID != NULL)
      Close(pi);
    return DRVCNF_OK;
  case DRV_ENABLE:
  case DRV_DISABLE:
    return DRVCNF_OK;
  case DRV_FREE:
    return DRVCNF_OK;
  case DRV_QUERYCONFIGURE:
    return DRVCNF_CANCEL; /* FIXME */
  case DRV_CONFIGURE:
    return DRVCNF_OK;     /* FIXME */
  case DRV_INSTALL:
  case DRV_REMOVE:
    return DRVCNF_OK;

    /* installable compression manager messages */
  case ICM_CONFIGURE:
    FIXME("ICM_CONFIGURE (%ld)\n",lParam1);
    if (lParam1 == -1)
      return ICERR_UNSUPPORTED; /* FIXME */
    else
      return Configure(pi, (HWND)lParam1);
  case ICM_ABOUT:
    if (lParam1 == -1)
      return ICERR_OK;
    else
      return About(pi, (HWND)lParam1);
  case ICM_GETSTATE:
  case ICM_SETSTATE:
    return 0; /* no state */
  case ICM_GETINFO:
    return GetInfo(pi, (ICINFO*)lParam1, (DWORD)lParam2);
  case ICM_GETDEFAULTQUALITY:
    if ((LPVOID)lParam1 != NULL) {
      *((LPDWORD)lParam1) = MSRLE32_DEFAULTQUALITY;
      return ICERR_OK;
    }
    break;
  case ICM_GETQUALITY:
    if ((LPVOID)lParam1 != NULL) {
      *((LPDWORD)lParam1) = pi->dwQuality;
      return ICERR_OK;
    }
    break;
  case ICM_SETQUALITY:
    return SetQuality(pi, *(LPLONG)lParam1);
  case ICM_COMPRESS_GET_FORMAT:
    return CompressGetFormat(pi, (LPCBITMAPINFOHEADER)lParam1,
			     (LPBITMAPINFOHEADER)lParam2);
  case ICM_COMPRESS_GET_SIZE:
    return CompressGetSize(pi, (LPCBITMAPINFOHEADER)lParam1,
			   (LPCBITMAPINFOHEADER)lParam2);
  case ICM_COMPRESS_QUERY:
    return CompressQuery(pi, (LPCBITMAPINFOHEADER)lParam1,
			 (LPCBITMAPINFOHEADER)lParam2);
  case ICM_COMPRESS_BEGIN:
    return CompressBegin(pi, (LPCBITMAPINFOHEADER)lParam1,
			 (LPCBITMAPINFOHEADER)lParam2);
  case ICM_COMPRESS:
    return Compress(pi, (ICCOMPRESS*)lParam1, (DWORD)lParam2);
  case ICM_COMPRESS_END:
    return CompressEnd(pi);
  case ICM_DECOMPRESS_GET_FORMAT:
    return DecompressGetFormat(pi, (LPCBITMAPINFOHEADER)lParam1,
			       (LPBITMAPINFOHEADER)lParam2);
  case ICM_DECOMPRESS_QUERY:
    return DecompressQuery(pi, (LPCBITMAPINFOHEADER)lParam1,
			   (LPCBITMAPINFOHEADER)lParam2);
  case ICM_DECOMPRESS_BEGIN:
    return DecompressBegin(pi, (LPCBITMAPINFOHEADER)lParam1,
			   (LPCBITMAPINFOHEADER)lParam2);
  case ICM_DECOMPRESS:
    return Decompress(pi, (ICDECOMPRESS*)lParam1, (DWORD)lParam2);
  case ICM_DECOMPRESS_END:
    return DecompressEnd(pi);
  case ICM_DECOMPRESS_SET_PALETTE:
    FIXME("(...) -> SetPalette(%p,%p,%p): stub!\n", pi, (LPVOID)lParam1, (LPVOID)lParam2);
    return ICERR_UNSUPPORTED;
  case ICM_DECOMPRESS_GET_PALETTE:
    return DecompressGetPalette(pi, (LPBITMAPINFOHEADER)lParam1,
				(LPBITMAPINFOHEADER)lParam2);
  case ICM_GETDEFAULTKEYFRAMERATE:
    if ((LPVOID)lParam1 != NULL)
      *(LPDWORD)lParam1 = 15;
    return ICERR_OK;
  default:
    if (uMsg < DRV_USER)
      return DefDriverProc(dwDrvID, hDrv, uMsg, lParam1, lParam2);
    else
      FIXME("Unknown message uMsg=0x%08X lParam1=0x%08lX lParam2=0x%08lX\n",uMsg,lParam1,lParam2);
  };

  return ICERR_UNSUPPORTED;
}

/* DllMain - library initialization code */
BOOL WINAPI DllMain(HINSTANCE hModule, DWORD dwReason, LPVOID lpReserved)
{
  TRACE("(%p,%d,%p)\n",hModule,dwReason,lpReserved);

  switch (dwReason) {
  case DLL_PROCESS_ATTACH:
    DisableThreadLibraryCalls(hModule);
    MSRLE32_hModule = hModule;
    break;
  }

  return TRUE;
}
