/*============================================================================= PcxImage.cpp : PCX image file format implementation. Copyright (c) 2001 Crytek Studios. All Rights Reserved. Revision history: * Created by Khonich Andrey =============================================================================*/ #include "RenderPCH.h" #include "CImage.h" #define HIST_R_BITS 5 #define HIST_G_BITS 6 #define HIST_B_BITS 5 // Amount to shift left R value to get max (hist_r, hist_g, hist_b) #define HIST_SHIFT_R 1 #define HIST_SHIFT_G 0 #define HIST_SHIFT_B 1 #define HIST_R_MAX (1 << HIST_R_BITS) #define HIST_G_MAX (1 << HIST_G_BITS) #define HIST_B_MAX (1 << HIST_B_BITS) #ifdef SH_LITTLE_ENDIAN # define R_BIT 0 # define G_BIT 8 # define B_BIT 16 #else # define R_BIT 24 # define G_BIT 16 # define B_BIT 8 #endif // Compute masks for effectively separating R,G and B components from a unsigned long. // For a little-endian machine they are respectively // 0x000000f8, 0x0000fc00 and 0x00f80000 // For a big-endian machine they are respectively // 0xf8000000, 0x00fc0000 and 0x0000f800 #define R_MASK ((HIST_R_MAX - 1) << (R_BIT + 8 - HIST_R_BITS)) #define G_MASK ((HIST_G_MAX - 1) << (G_BIT + 8 - HIST_G_BITS)) #define B_MASK ((HIST_B_MAX - 1) << (B_BIT + 8 - HIST_B_BITS)) // The following macro extract the respective color components from a unsigned long // and transform them into a index in the histogram. #define INDEX_R(l) ((l & R_MASK) >> (R_BIT + 8 - HIST_R_BITS)) #define INDEX_G(l) ((l & G_MASK) >> (G_BIT + 8 - HIST_G_BITS - HIST_R_BITS)) #define INDEX_B(l) ((l & B_MASK) >> (B_BIT + 8 - HIST_B_BITS - HIST_G_BITS - HIST_R_BITS)) // Calculate index into histogram for given R,G,B components #define INDEX(r,g,b) (r + (g << HIST_R_BITS) + (b << (HIST_R_BITS + HIST_G_BITS))) // The storage for color usage histogram static ushort *hist = NULL; // Total number of colors that were used to create the histogram static unsigned hist_pixels; /* * A box in color space. * Both minimal and maximal component bounds are inclusive, that is, the bounds * Rm = 0, Rx = 255 means the box covers the entire R component range. *

* This structure is meant to be highly fast, thus only atomic operations * are implemented for it. After some operations the box may be left in a * invalid state, thus take care. */ struct shColorBox { // The minimal and maximal R byte Rm,Rx; // The minimal and maximal G byte Gm,Gx; // The minimal and maximal B byte Bm,Bx; // Color box volume unsigned Volume; // Number of pixels in this box unsigned PixelCount; // Number of non-zero different color values in this box unsigned ColorCount; // Useful function static inline unsigned Sqr (int x) { return x * x; } // Set box to given bounds void Set (byte rm, byte rx, byte gm, byte gx, byte bm, byte bx) { Rm = rm; Rx = rx; Gm = gm; Gx = gx; Bm = bm; Bx = bx; } // Compute the volume of box void ComputeVolume () { // We compute the length of the diagonal of the box rather than the // proper volume. This also has the side effect that a long narrow // box looks more "voluminous" thus its more probably that it will // be split rather than a relatively cubic one. Volume = Sqr (Rx - Rm) * (R_COEF_SQ << HIST_SHIFT_R) + Sqr (Gx - Gm) * (G_COEF_SQ << HIST_SHIFT_G) + Sqr (Bx - Bm) * (B_COEF_SQ << HIST_SHIFT_B); } // Count number of non-zero colors within this box void CountPixels () { PixelCount = ColorCount = 0; for (int b = Bm; b <= Bx; b++) for (int g = Gm; g <= Gx; g++) { ushort *hp = &hist [INDEX (Rm, g, b)]; for (int r = Rx - Rm; r >= 0; r--, hp++) if (*hp) { PixelCount += *hp; ColorCount++; } /* endif */ } /* endfor */ } // Move Rm up until we find pixels that contain this value bool ShrinkRm () { byte iRm = Rm; for (; Rm <= Rx; Rm++) for (byte b = Bm; b <= Bx; b++) { ushort *hp = &hist [INDEX (Rm, Gm, b)]; for (int g = Gx - Gm; g >= 0; g--, hp += HIST_R_MAX) if (*hp) return (Rm != iRm); } return (Rm != iRm); } // Move Rx down until we find pixels that contain this value bool ShrinkRx () { byte iRx = Rx; for (; Rx >= Rm; Rx--) for (byte b = Bm; b <= Bx; b++) { ushort *hp = &hist [INDEX (Rx, Gm, b)]; for (int g = Gx - Gm; g >= 0; g--, hp += HIST_R_MAX) if (*hp) return (Rx != iRx); } return (Rx != iRx); } // Move Gm up until we find pixels that contain this value bool ShrinkGm () { byte iGm = Gm; for (; Gm <= Gx; Gm++) for (byte b = Bm; b <= Bx; b++) { ushort *hp = &hist [INDEX (Rm, Gm, b)]; for (int r = Rx - Rm; r >= 0; r--, hp++) if (*hp) return (Gm != iGm); } return (Gm != iGm); } // Move Gx down until we find pixels that contain this value bool ShrinkGx () { byte iGx = Gx; for (; Gx >= Gm; Gx--) for (byte b = Bm; b <= Bx; b++) { ushort *hp = &hist [INDEX (Rm, Gx, b)]; for (int r = Rx - Rm; r >= 0; r--, hp++) if (*hp) return (Gx != iGx); } return (Gx != iGx); } // Move Bm up until we find pixels that contain this value bool ShrinkBm () { byte iBm = Bm; for (; Bm <= Bx; Bm++) for (byte g = Gm; g <= Gx; g++) { ushort *hp = &hist [INDEX (Rm, g, Bm)]; for (int r = Rx - Rm; r >= 0; r--, hp++) if (*hp) return (Bm != iBm); } return (Bm != iBm); } // Move Bx down until we find pixels that contain this value bool ShrinkBx () { byte iBx = Bx; for (; Bx >= Bm; Bx--) for (byte g = Gm; g <= Gx; g++) { ushort *hp = &hist [INDEX (Rm, g, Bx)]; for (int r = Rx - Rm; r >= 0; r--, hp++) if (*hp) return (Bx != iBx); } return (Bx != iBx); } // Shrink box: move min/max bounds until we hit an existing color void Shrink () { ShrinkRm (); ShrinkRx (); ShrinkGm (); ShrinkGx (); ShrinkBm (); ShrinkBx (); } /** * Compute the mean color for this box. * The computation is performed by taking into account each color's * weight, i.e. number of pixels with this color. Thus resulting palette * is biased towards most often used colors. */ void GetMeanColor (SRGBPixel &color) { unsigned rs = 0, gs = 0, bs = 0; unsigned count = 0; for (int b = Bm; b <= Bx; b++) for (int g = Gm; g <= Gx; g++) { ushort *hp = &hist [INDEX (Rm, g, b)]; for (int r = Rm; r <= Rx; r++, hp++) if (*hp) { unsigned pixc = *hp; count += pixc; rs += pixc * r; gs += pixc * g; bs += pixc * b; } /* endif */ } /* endfor */ // In some extreme cases (textures with zero pixels or // single-color textures with 1 transparent color) // we can end here with count == 0; avoid division by zero if (!count) { color = SRGBPixel (0, 0, 0); return; } color.red = ((rs + count / 2) << (8 - HIST_R_BITS)) / count; color.green = ((gs + count / 2) << (8 - HIST_G_BITS)) / count; color.blue = ((bs + count / 2) << (8 - HIST_B_BITS)) / count; } void FillInverseCMap (byte *icmap, byte index) { int Rcount = Rx - Rm + 1; for (int b = Bm; b <= Bx; b++) for (int g = Gm; g <= Gx; g++) memset (&icmap [INDEX (Rm, g, b)], index, Rcount); } }; // The storage for color space boxes static shColorBox *box = NULL; // Number of valid color boxes static int boxcount; // The storage for color indices static byte *color_index = NULL; static int __cdecl compare_boxes (const void *i1, const void *i2) { int count1 = box [*(byte *)i1].PixelCount; int count2 = box [*(byte *)i2].PixelCount; return (count1 > count2) ? -1 : (count1 == count2) ? 0 : +1; } //------------------------------------------------------------- The API ------// // The state of quantization variables static enum { // Uninitialized: initial state qsNone, // Counting color frequencies qsCount, // Remapping input images to output qsRemap } qState = qsNone; void shQuantizeBegin () { // Clean up, if previous quantization sequence was not finished shQuantizeEnd (); // First, allocate the histogram hist = new ushort [HIST_R_MAX * HIST_G_MAX * HIST_B_MAX]; memset (hist, 0, HIST_R_MAX * HIST_G_MAX * HIST_B_MAX * sizeof (ushort)); hist_pixels = 0; qState = qsCount; } void shQuantizeEnd () { delete [] color_index; color_index = NULL; delete [] box; box = NULL; delete [] hist; hist = NULL; } void shQuantizeCount (SRGBPixel *image, int pixels, SRGBPixel *transp) { // Sanity check if (!pixels || qState != qsCount) return; hist_pixels += pixels; // Now, count all colors in image unsigned long *src = (unsigned long *)image; if (transp) { unsigned long tc = (*(unsigned long *)transp) & RGB_MASK; while (pixels--) { unsigned long pix = *src++; if (tc != (pix & RGB_MASK)) { ushort &pa = hist [INDEX_R (pix) + INDEX_G (pix) + INDEX_B (pix)]; // do not permit overflow here; stick to MAX_ushort if (!++pa) --pa; } } } else while (pixels--) { unsigned long pix = *src++; ushort &pa = hist [INDEX_R (pix) + INDEX_G (pix) + INDEX_B (pix)]; // do not permit overflow here; stick to MAX_ushort if (!++pa) --pa; } } void shQuantizeBias (SRGBPixel *colors, int count, int weight) { // Sanity check if (!count || qState != qsCount) return; unsigned delta; if (hist_pixels < (0xffffffff / 100)) delta = ((hist_pixels + 1) * weight / (100 * count)); else delta = ((hist_pixels / count + 1) * weight) / 100; if (delta > 0xffff) delta = 0xffff; else if (!delta) return; // Now, count all colors in image unsigned long *src = (unsigned long *)colors; while (count--) { unsigned long pix = *src++; ushort &pa = hist [INDEX_R (pix) + INDEX_G (pix) + INDEX_B (pix)]; // do not permit overflow here; stick to MAX_ushort if (unsigned (pa) + delta > 0xffff) pa = 0xffff; else pa += delta; } } void shQuantizePalette (SRGBPixel *&outpalette, int &maxcolors, SRGBPixel *transp) { // Sanity check if (qState != qsCount || !maxcolors) return; // Good. Now we create the array of color space boxes. box = new shColorBox [maxcolors]; box [0].Set (0, HIST_R_MAX - 1, 0, HIST_G_MAX - 1, 0, HIST_B_MAX - 1); box [0].Shrink (); box [0].ComputeVolume (); box [0].CountPixels (); boxcount = 1; if (transp) maxcolors--; // Loop until we have enough boxes (or we're out of pixels) while (boxcount < maxcolors) { // Find the box that should be split // We're making this decision the following way: // - first half of palette we prefer to split boxes that are // most populated with different colors. // - the rest of palette we prefer to split largest boxes. int bi, bestbox = -1; unsigned bestrating = 0; if (boxcount < maxcolors / 2) { for (bi = 0; bi < boxcount; bi++) if (bestrating < box [bi].ColorCount) { bestrating = box [bi].ColorCount; bestbox = bi; } } else { for (bi = 0; bi < boxcount; bi++) if (bestrating < box [bi].Volume) { bestrating = box [bi].Volume; bestbox = bi; } } // Out of splittable boxes? if (bestrating <= 1) break; shColorBox &srcbox = box [bestbox]; shColorBox &dstbox = box [boxcount++]; dstbox = srcbox; // Decide along which of R/G/B axis to split the box int rlen = (dstbox.Rx - dstbox.Rm) * (R_COEF << HIST_SHIFT_R); int glen = (dstbox.Gx - dstbox.Gm) * (G_COEF << HIST_SHIFT_G); int blen = (dstbox.Bx - dstbox.Bm) * (B_COEF << HIST_SHIFT_B); enum { axisR, axisG, axisB } axis = (glen < rlen) ? ((rlen < blen) ? axisB : axisR) : ((glen < blen) ? axisB : axisG); // // We split each box into two by the plane that goes through given color // component (one of R,G,B as choosen above). Any of resulting split boxes // possibly can become smaller if we move one of the five its faces (the // sixth face sure can't move because it was checked before - the one that // is opposed to the just-created new face, in the place of split). // Here goes some ASCII art: // // C G K The initial color box ABCD-IJKL was split by a // *-------*-------* plane and two boxes ABCD-EFGH and EFGH-IJKL // /| /| /| were created. The boxes cannot be shrinked // B/ | F/ | J/ | by moving faces ABCD and IJKL (because the // *-------*-------* | boxes were previously adjusted and any surface // | *----|--*----|--* passes through at least one used color). // | /D | /H | /L Now we also see that if the left box // |/ |/ |/ can be shrinked by moving face, say, ABFE // *-------*-------* towards DCGH, it is impossible for the right // A E I box to be shrinked by moving EFJI towards HGKL, // because the previous whole face ABJI is known // to pass through at least one used color (and if it is not in the ABFE // are, then it is surely in the EFJI area). We can say the same about // the DCGH/HGKL, BCGF/FGKJ and ADHE/EHLI pairs. // switch (axis) { case axisR: srcbox.Rx = (srcbox.Rm + srcbox.Rx) / 2; dstbox.Rm = srcbox.Rx + 1; srcbox.ShrinkRx (); dstbox.ShrinkRm (); if (!srcbox.ShrinkGm ()) dstbox.ShrinkGm (); if (!srcbox.ShrinkGx ()) dstbox.ShrinkGx (); if (!srcbox.ShrinkBm ()) dstbox.ShrinkBm (); if (!srcbox.ShrinkBx ()) dstbox.ShrinkBx (); break; case axisG: srcbox.Gx = (srcbox.Gm + srcbox.Gx) / 2; dstbox.Gm = srcbox.Gx + 1; srcbox.ShrinkGx (); dstbox.ShrinkGm (); if (!srcbox.ShrinkRm ()) dstbox.ShrinkRm (); if (!srcbox.ShrinkRx ()) dstbox.ShrinkRx (); if (!srcbox.ShrinkBm ()) dstbox.ShrinkBm (); if (!srcbox.ShrinkBx ()) dstbox.ShrinkBx (); break; case axisB: srcbox.Bx = (srcbox.Bm + srcbox.Bx) / 2; dstbox.Bm = srcbox.Bx + 1; srcbox.ShrinkBx (); dstbox.ShrinkBm (); if (!srcbox.ShrinkRm ()) dstbox.ShrinkRm (); if (!srcbox.ShrinkRx ()) dstbox.ShrinkRx (); if (!srcbox.ShrinkGm ()) dstbox.ShrinkGm (); if (!srcbox.ShrinkGx ()) dstbox.ShrinkGx (); break; } /* endswitch */ dstbox.CountPixels (); srcbox.PixelCount -= dstbox.PixelCount; srcbox.ColorCount -= dstbox.ColorCount; srcbox.ComputeVolume (); dstbox.ComputeVolume (); } /* endwhile */ // Either we're out of splittable boxes, or we have palsize boxes. // Assign successive palette indices to all boxes int count, delta = transp ? 1 : 0; color_index = new byte [boxcount + delta]; for (count = 0; count < boxcount; count++) color_index [count] = count; // Sort palette indices by usage (a side bonus to quantization) qsort (color_index, boxcount, sizeof (byte), compare_boxes); // Allocate the palette, if not already allocated if (!outpalette) outpalette = new SRGBPixel [maxcolors + delta]; // Fill the unused colormap entries with zeros memset (&outpalette [boxcount + delta], 0, (maxcolors - boxcount) * sizeof (SRGBPixel)); // Now compute the mean color for each box for (count = 0; count < boxcount; count++) box [color_index [count]].GetMeanColor (outpalette [count + delta]); // If we have a transparent color, set colormap entry 0 to it if (delta) { for (count = boxcount; count; count--) color_index [count] = color_index [count - 1] + 1; color_index [0] = 0; outpalette [0] = SRGBPixel (0, 0, 0); } maxcolors = boxcount + delta; } void shQuantizeRemap (SRGBPixel *image, int pixels, byte *&outimage, SRGBPixel *transp) { // Sanity check if (qState != qsCount && qState != qsRemap) return; int count; // We will re-use the histogram memory for a inverse colormap. However, we // will need just a byte per element, so we'll assign the address of // histogram memory block to a pointer of suitable type, and the second // half of histogram storage remains unused. byte *icmap = (byte *)hist; int delta = transp ? 1 : 0; if (qState == qsCount) { // Now, fill inverse colormap with color indices for (count = 0; count < boxcount; count++) box [color_index [count + delta] - delta].FillInverseCMap (icmap, count + delta); qState = qsRemap; } // Allocate the picture and the palette if (!outimage) outimage = new byte [pixels]; unsigned long *src = (unsigned long *)image; byte *dst = outimage; count = pixels; if (transp) { unsigned long tc = (*(unsigned long *)transp) & RGB_MASK; while (count--) { unsigned long pix = *src++; if (tc == (pix & RGB_MASK)) *dst++ = 0; else *dst++ = icmap [INDEX_R (pix) + INDEX_G (pix) + INDEX_B (pix)]; } } else while (count--) { unsigned long pix = *src++; *dst++ = icmap [INDEX_R (pix) + INDEX_G (pix) + INDEX_B (pix)]; } } void shQuantizeRemapDither (SRGBPixel *image, int pixels, int pixperline, SRGBPixel *palette, int colors, byte *&outimage, SRGBPixel *transp) { // Sanity check if (qState != qsCount && qState != qsRemap) return; int count; // We will re-use the histogram memory for a inverse colormap. However, we // will need just a byte per element, so we'll assign the address of // histogram memory block to a pointer of suitable type, and the second // half of histogram storage remains unused. byte *icmap = (byte *)hist; int delta = transp ? 1 : 0; if (qState == qsCount) { // Build an inverse colormap (since during dithering we can get color // indices that did not existed in the original image) shInverseColormap (colors - delta, palette + delta, HIST_R_BITS, HIST_G_BITS, HIST_B_BITS, icmap); if (transp) for (int i = 0; i < HIST_R_MAX * HIST_G_MAX * HIST_B_MAX; i++) icmap [i]++; qState = qsRemap; } // Allocate the picture and the palette if (!outimage) outimage = new byte [pixels]; SRGBPixel *src = image; byte *dst = outimage; count = pixels; int *fserr = (int *)malloc (2 * 3 * (pixperline + 2) * sizeof (int)); memset (fserr, 0, 3 * (pixperline + 2) * sizeof (int)); // odd/even row unsigned char odd = 0; while (count > 0) { // The alogorithm implements the widely-known and used Floyd-Steinberg // error distribution - based dithering. The errors are distributed with // the following weights to the surrounding pixels: // // (here) 7/16 // 3/16 5/16 1/16 // // Even lines are traversed left to right, odd lines backwards. SRGBPixel *cursrc; byte *curdst; int *curerr, *nexterr; int dir; if (odd) { cursrc = src + pixperline - 1; curdst = dst + pixperline - 1; curerr = fserr + 2 * 3 * (pixperline + 2) - 6; nexterr = fserr + 3 * (pixperline + 2) - 3; dir = -1; } else { cursrc = src; curdst = dst; curerr = fserr + 3; nexterr = fserr + 3 * (pixperline + 2); dir = 1; } int dir3 = dir * 3; // We will keep the errors for pixels (x+1, y) in the variable "err10", // the error for the pixel right below us (x, y + 1) in "err01", and // the error at (x + 1, y + 1) in "err11". The error for the pixel at // (x - 1, y + 1) will be flushed into the errors array. This way, we // will have just one memory read and one memory write per pixel. // Well, in fact we have much more (x86 is terribly lacking registers) // but anyway they go through the cache. int err10r = 0, err01r = 0, err11r = 0; int err10g = 0, err01g = 0, err11g = 0; int err10b = 0, err01b = 0, err11b = 0; for (int fspix = pixperline; fspix; fspix--, cursrc += dir, curdst += dir, curerr += dir3, nexterr += dir3) { SRGBPixel srcpix = *cursrc; if (transp && transp->eq (srcpix)) { *curdst = 0; err10r = err10g = err10b = 0; nexterr [0] = err01r; nexterr [1] = err01g; nexterr [2] = err01b; err01r = err11r; err01g = err11g; err01b = err11b; err11r = err11g = err11b = 0; continue; } int r = srcpix.red + ((err10r + curerr [0]) / 16); if (r < 0) r = 0; if (r > 255) r = 255; int g = srcpix.green + ((err10g + curerr [1]) / 16); if (g < 0) g = 0; if (g > 255) g = 255; int b = srcpix.blue + ((err10b + curerr [2]) / 16); if (b < 0) b = 0; if (b > 255) b = 255; byte pix = icmap [((r >> (8 - HIST_R_BITS)) << (HIST_G_BITS + HIST_B_BITS)) | ((g >> (8 - HIST_G_BITS)) << HIST_B_BITS) | ((b >> (8 - HIST_B_BITS)))]; *curdst = pix; SRGBPixel realcolor = palette [pix]; err10r = r - realcolor.red; nexterr [0] = err01r + err10r * 3; // * 3 err01r = err11r + err10r * 5; // * 5 err11r = err10r; // * 1 err10r *= 7; // * 7 err10g = g - realcolor.green; nexterr [1] = err01g + err10g * 3; // * 3 err01g = err11g + err10g * 5; // * 5 err11g = err10g; // * 1 err10g *= 7; // * 7 err10b = b - realcolor.blue; nexterr [2] = err01b + err10b * 3; // * 3 err01b = err11b + err10b * 5; // * 5 err11b = err10b; // * 1 err10b *= 7; // * 7 } // flush cached errors into error array nexterr [0] = err01r; nexterr [1] = err01g; nexterr [2] = err01b; src += pixperline; dst += pixperline; odd ^= 1; count -= pixperline; } free(fserr); } void shQuantizeRGB (SRGBPixel *image, int pixels, int pixperline, byte *&outimage, SRGBPixel *&outpalette, int &maxcolors, bool dither) { shQuantizeBegin (); shQuantizeCount (image, pixels); shQuantizePalette (outpalette, maxcolors); if (dither) shQuantizeRemapDither (image, pixels, pixperline, outpalette, maxcolors, outimage); else shQuantizeRemap (image, pixels, outimage); shQuantizeEnd (); }