/*====================================================================* - Copyright (C) 2001 Leptonica. All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions - are met: - 1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - 2. Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following - disclaimer in the documentation and/or other materials - provided with the distribution. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *====================================================================*/ /*! * \file colorquant1.c *
* * Octcube color quantization * * There are several different octcube/octree based quantizations. * These can be classified, in the order in which they appear in this * file, as follows: * * ----------------------------------------------------------------- * (1) General adaptive octree * (2) Adaptive octree by population at fixed level * (3) Adaptive octree using population and with specified number * of output colors * (4) Octcube with colormap representation of mixed color/gray * (5) 256 fixed octcubes covering color space * (6) Octcubes at fixed level for ncolors <= 256 * (7) Octcubes at fixed level with RGB output * (8) Quantizing an rgb image using a specified colormap * ----------------------------------------------------------------- * * (1) Two-pass adaptive octree color quantization * PIX *pixOctreeColorQuant() * PIX *pixOctreeColorQuantGeneral() * * which calls * static CQCELL ***octreeGenerateAndPrune() * static PIX *pixOctreeQuantizePixels() * * which calls * static l_int32 octreeFindColorCell() * * Helper cqcell functions * static CQCELL ***cqcellTreeCreate() * static void cqcellTreeDestroy() * * Helper index functions * l_int32 makeRGBToIndexTables() * void getOctcubeIndexFromRGB() * static void getRGBFromOctcube() * static l_int32 getOctcubeIndices() * static l_int32 octcubeGetCount() * * (2) Adaptive octree quantization based on population at a fixed level * PIX *pixOctreeQuantByPopulation() * static l_int32 pixDitherOctindexWithCmap() * * (3) Adaptive octree quantization to 4 and 8 bpp with specified * number of output colors in colormap * PIX *pixOctreeQuantNumColors() * * (4) Mixed color/gray quantization with specified number of colors * PIX *pixOctcubeQuantMixedWithGray() * * (5) Fixed partition octcube quantization with 256 cells * PIX *pixFixedOctcubeQuant256() * * (6) Fixed partition quantization for images with few colors * PIX *pixFewColorsOctcubeQuant1() * PIX *pixFewColorsOctcubeQuant2() * PIX *pixFewColorsOctcubeQuantMixed() * * (7) Fixed partition octcube quantization at specified level * with quantized output to RGB * PIX *pixFixedOctcubeQuantGenRGB() * * (8) Color quantize RGB image using existing colormap * PIX *pixQuantFromCmap() [high-level wrapper] * PIX *pixOctcubeQuantFromCmap() * static PIX *pixOctcubeQuantFromCmapLUT() * * Generation of octcube histogram * NUMA *pixOctcubeHistogram() * * Get filled octcube table from colormap * l_int32 *pixcmapToOctcubeLUT() * * Strip out unused elements in colormap * l_int32 pixRemoveUnusedColors() * * Find number of occupied octcubes at the specified level * l_int32 pixNumberOccupiedOctcubes() * * Notes: * Leptonica also provides color quantization using a modified * form of median cut. See colorquant2.c for details. **/ #include
* This data structure is used for pixOctreeColorQuant(), * a color octree that adjusts to the color distribution * in the image that is being quantized. The best settings * are with CqNLevels = 6 and DITHERING set on. * * Notes: * (1) the CTE (color table entry) index is sequentially * assigned as the tree is pruned back * (2) if 'bleaf' == 1, all pixels in that cube have been * assigned to one or more CTEs. But note that if * all 8 subcubes have 'bleaf' == 1, it will have no * pixels left for assignment and will not be a CTE. * (3) 'nleaves', the number of leaves contained at the next * lower level is some number between 0 and 8, inclusive. * If it is zero, it means that all colors within this cube * are part of a single growing cluster that has not yet * been set aside as a leaf. If 'nleaves' > 0, 'bleaf' * will be set to 1 and all pixels not assigned to leaves * at lower levels will be assigned to a CTE here. * (However, as described above, if all pixels are already * assigned, we set 'bleaf' = 1 but do not create a CTE * at this level.) * (4) To keep the maximum color error to a minimum, we * prune the tree back to level 2, and require that * all 64 level 2 cells are CTEs. * (5) We reserve an extra set of colors to prevent running out * of colors during the assignment of the final 64 level 2 cells. * This is more likely to happen with small images. * (6) When we run out of colors, the dithered image can be very * poor, so we additionally prevent dithering if the image * is small. * (7) The color content of the image is measured, and if there * is very little color, it is quantized in grayscale. **/ struct ColorQuantCell { l_int32 rc, gc, bc; /* center values */ l_int32 n; /* number of samples in this cell */ l_int32 index; /* CTE (color table entry) index */ l_int32 nleaves; /* # of leaves contained at next lower level */ l_int32 bleaf; /* boolean: 0 if not a leaf, 1 if so */ }; typedef struct ColorQuantCell CQCELL; /* Constants for pixOctreeColorQuant() */ static const l_int32 CqNLevels = 5; /* only 4, 5 and 6 are allowed */ static const l_int32 CqReservedColors = 64; /* to allow for level 2 */ /* remainder CTEs */ static const l_int32 ExtraReservedColors = 25; /* to avoid running out */ static const l_int32 TreeGenWidth = 350; /* big enough for good stats */ static const l_int32 MinDitherSize = 250; /* don't dither if smaller */ /* *
* This data structure is used for pixOctreeQuantNumColors(), * a color octree that adjusts in a simple way to the to the color * distribution in the image that is being quantized. It outputs * colormapped images, either 4 bpp or 8 bpp, depending on the * max number of colors and the compression desired. * * The number of samples is saved as a float in the first location, * because this is required to use it as the key that orders the * cells in the priority queue. ** */ struct OctcubeQuantCell { l_float32 n; /* number of samples in this cell */ l_int32 octindex; /* octcube index */ l_int32 rcum, gcum, bcum; /* cumulative values */ l_int32 rval, gval, bval; /* average values */ }; typedef struct OctcubeQuantCell OQCELL; /* *
* This data structure is using for heap sorting octcubes * by population. Sort order is decreasing. **/ struct L_OctcubePop { l_float32 npix; /* parameter on which to sort */ l_int32 index; /* octcube index at assigned level */ l_int32 rval; /* mean red value of pixels in octcube */ l_int32 gval; /* mean green value of pixels in octcube */ l_int32 bval; /* mean blue value of pixels in octcube */ }; typedef struct L_OctcubePop L_OCTCUBE_POP; /* *
* In pixDitherOctindexWithCmap(), we use these default values. To get the max value of 'dif' in the dithering color transfer, divide these "DIF_CAP" values by 8. However, a value of 0 means that there is no cap (infinite cap). A very small value is used for POP_DIF_CAP because dithering on the population generated colormap can be unstable without a tight cap. **/ static const l_int32 FIXED_DIF_CAP = 0; static const l_int32 POP_DIF_CAP = 40; /* Static octree helper function */ static l_int32 octreeFindColorCell(l_int32 octindex, CQCELL ***cqcaa, l_int32 *pindex, l_int32 *prval, l_int32 *pgval, l_int32 *pbval); /* Static cqcell functions */ static CQCELL ***octreeGenerateAndPrune(PIX *pixs, l_int32 colors, l_int32 reservedcolors, PIXCMAP **pcmap); static PIX *pixOctreeQuantizePixels(PIX *pixs, CQCELL ***cqcaa, l_int32 ditherflag); static CQCELL ***cqcellTreeCreate(void); static void cqcellTreeDestroy(CQCELL ****pcqcaa); /* Static helper octcube index functions */ static void getRGBFromOctcube(l_int32 cubeindex, l_int32 level, l_int32 *prval, l_int32 *pgval, l_int32 *pbval); static l_int32 getOctcubeIndices(l_int32 rgbindex, l_int32 level, l_int32 *pbindex, l_int32 *psindex); static l_int32 octcubeGetCount(l_int32 level, l_int32 *psize); /* Static function to perform octcube-indexed dithering */ static l_int32 pixDitherOctindexWithCmap(PIX *pixs, PIX *pixd, l_uint32 *rtab, l_uint32 *gtab, l_uint32 *btab, l_int32 *carray, l_int32 difcap); /* Static function to perform octcube-based quantizing from colormap */ static PIX *pixOctcubeQuantFromCmapLUT(PIX *pixs, PIXCMAP *cmap, l_int32 mindepth, l_int32 *cmaptab, l_uint32 *rtab, l_uint32 *gtab, l_uint32 *btab); #ifndef NO_CONSOLE_IO #define DEBUG_COLORQUANT 0 #define DEBUG_OCTINDEX 0 #define DEBUG_OCTCUBE_CMAP 0 #define DEBUG_POP 0 #define DEBUG_FEW_COLORS 0 #define PRINT_OCTCUBE_STATS 0 #endif /* ~NO_CONSOLE_IO */ /*-------------------------------------------------------------------------* * Two-pass adaptive octree color quantization * *-------------------------------------------------------------------------*/ /*! * \brief pixOctreeColorQuant() * * \param[in] pixs 32 bpp; 24-bit color * \param[in] colors in colormap; some number in range [128 ... 256]; * the actual number of colors used will be smaller * \param[in] ditherflag 1 to dither, 0 otherwise * \return pixd 8 bpp with colormap, or NULL on error * *
* I found one description in the literature of octree color * quantization, using progressive truncation of the octree, * by M. Gervautz and W. Purgathofer in Graphics Gems, pp. * 287-293, ed. A. Glassner, Academic Press, 1990. * Rather than setting up a fixed partitioning of the color * space ab initio, as we do here, they allow the octree to be * progressively truncated as new pixels are added. They * need to set up some data structures that are traversed * with the addition of each 24 bit pixel, in order to decide * either 1) in which cluster (sub-branch of the octree to put * the pixel, or 2 whether to truncate the octree further * to place the pixel in an existing cluster, or 3 which * two existing clusters should be merged so that the pixel * can be left to start a truncated leaf of the octree. Such dynamic * truncation is considerably more complicated, and Gervautz et * al. did not explain how they did it in anywhere near the * detail required to check their implementation. * * The simple method in pixFixedOctcubeQuant256 is very * fast, and with dithering the results are good, but you * can do better if the color clusters are selected adaptively * from the image. We want a method that makes much better * use of color samples in regions of color space with high * pixel density, while also fairly representing small numbers * of color pixels in low density regions. Such adaptation * requires two passes through the image: the first for generating * the pruned tree of color cubes and the second for computing the index * into the color table for each pixel. * * A relatively simple adaptive method is pixOctreeQuantByPopulation. * That function first determines if the image has very few colors, * and, if so, quantizes to those colors. If there are more than * 256 colors, it generates a histogram of octcube leaf occupancy * at level 4, chooses the 192 most populated such leaves as * the first 192 colors, and sets the remaining 64 colors to the * residual average pixel values in each of the 64 level 2 octcubes. * This is a bit faster than pixOctreeColorQuant, and does very * well without dithering, but for most images with dithering it * is clearly inferior. * * We now describe pixOctreeColorQuant. The first pass is done * on a subsampled image, because we do not need to use all the * pixels in the image to generate the tree. Subsampling * down to 0.25 1/16 of the pixels makes the program run * about 1.3 times faster. * * Instead of dividing the color space into 256 equal-sized * regions, we initially divide it into 2^12 or 2^15 or 2^18 * equal-sized octcubes. Suppose we choose to use 2^18 octcubes. * This gives us 6 octree levels. We then prune back, * starting from level 6. For every cube at level 6, there * are 8 cubes at level 5. Call the operation of putting a * cube aside as a color table entry CTE a "saving." * We use a in general level-dependent threshold, and save * those level 6 cubes that are above threshold. * The rest are combined into the containing level 5 cube. * If between 1 and 7 level 6 cubes within a level 5 * cube have been saved by thresholding, then the remaining * level 6 cubes in that level 5 cube are automatically * saved as well, without applying a threshold. This greatly * simplifies both the description of the CTEs and the later * classification of each pixel as belonging to a CTE. * This procedure is iterated through every cube, starting at * level 5, and then 4, 3, and 2, successively. The result is that * each CTE contains the entirety of a set of from 1 to 7 cubes * from a given level that all belong to a single cube at the * level above. We classify the CTEs in terms of the * condition in which they are made as either being "threshold" * or "residual." They are "threshold" CTEs if no subcubes * are CTEs that is, they contain every pixel within the cube * and the number of pixels exceeds the threshold for making * a CTE. They are "residual" CTEs if at least one but not more * than 7 of the subcubes have already been determined to be CTEs; * this happens automatically -- no threshold is applied. * If all 8 subcubes are determined to be CTEs, the cube is * marked as having all pixels accounted for 'bleaf' = 1 but * is not saved as a CTE. * * We stop the pruning at level 2, at which there are 64 * sub-cubes. Any pixels not already claimed in a CTE are * put in these cubes. * * As the cubes are saved as color samples in the color table, * the number of remaining pixels P and the number of * remaining colors in the color table N are recomputed, * along with the average number of pixels P/N ppc to go in * each of the remaining colors. This running average number is * used to set the threshold at the current level. * * Because we are going to very small cubes at levels 6 or 5, * and will dither the colors for errors, it is not necessary * to compute the color center of each cluster; we can simply * use the center of the cube. This gives us a minimax error * condition: the maximum error is half the width of the * level 2 cubes -- 32 color values out of 256 -- for each color * sample. In practice, most of the pixels will be very much * closer to the center of their cells. And with dithering, * the average pixel color in a small region will be closer still. * Thus with the octree quantizer, we are able to capture * regions of high color pdf probability density function in small * but accurate CTEs, and to have only a small number of pixels * that end up a significant distance with a guaranteed maximum * from their true color. * * How should the threshold factor vary? Threshold factors * are required for levels 2, 3, 4 and 5 in the pruning stage. * The threshold for level 5 is actually applied to cubes at * level 6, etc. From various experiments, it appears that * the results do not vary appreciably for threshold values near 1.0. * If you want more colors in smaller cubes, the threshold * factors can be set lower than 1.0 for cubes at levels 4 and 5. * However, if the factor is set much lower than 1.0 for * levels 2 and 3, we can easily run out of colors. * We put aside 64 colors in the calculation of the threshold * values, because we must have 64 color centers at level 2, * that will have very few pixels in most of them. * If we reduce the factor for level 5 to 0.4, this will * generate many level 6 CTEs, and consequently * many residual cells will be formed up from those leaves, * resulting in the possibility of running out of colors. * Remember, the residual CTEs are mandatory, and are formed * without using the threshold, regardless of the number of * pixels that are absorbed. * * The implementation logically has four parts: * * 1 accumulation into small, fixed cells * 2 pruning back into selected CTE cubes * 3 organizing the CTEs for fast search to find * the CTE to which any image pixel belongs * 4 doing a second scan to code the image pixels by CTE * * Step 1 is straightforward; we use 2^15 cells. * * We've already discussed how the pruning step 2 will be performed. * * Steps 3) and (4 are related, in that the organization * used by step 3 determines how the search actually * takes place for each pixel in step 4. * * There are many ways to do step 3. Let's explore a few. * * a The simplest is to order the cubes from highest occupancy * to lowest, and traverse the list looking for the deepest * match. To make this more efficient, so that we know when * to stop looking, any cube that has separate CTE subcubes * would be marked as such, so that we know when we hit a * true leaf. * * b Alternatively, we can order the cubes by highest * occupancy separately each level, and work upward, * starting at level 5, so that when we find a match we * know that it will be correct. * * c Another approach would be to order the cubes by * "address" and use a hash table to find the cube * corresponding to a pixel color. I don't know how to * do this with a variable length address, as each CTE * will have 3*n bits, where n is the level. * * d Another approach entirely is to put the CTE cubes into * a tree, in such a way that starting from the root, and * using 3 bits of address at a time, the correct branch of * each octree can be taken until a leaf is found. Because * a given cube can be both a leaf and also have branches * going to sub-cubes, the search stops only when no * marked subcubes have addresses that match the given pixel. * * In the tree method, we can start with a dense infrastructure, * and place the leaves corresponding to the N colors * in the tree, or we can grow from the root only those * branches that end directly on leaves. * * What we do here is to take approach d, and implement the tree * "virtually", as a set of arrays, one array for each level * of the tree. Initially we start at level 5, an array with * 2^15 cubes, each with 8 subcubes. We then build nodes at * levels closer to the root; at level 4 there are 2^12 nodes * each with 8 subcubes; etc. Using these arrays has * several advantages: * * ~ We don't need to keep track of links between cubes * and subcubes, because we can use the canonical * addressing on the cell arrays directly to determine * which nodes are parent cubes and which are sub-cubes. * * ~ We can prune directly on this tree * * ~ We can navigate the pruned tree quickly to classify * each pixel in the image. * * Canonical addressing guarantees that the i-th node at level k * has 8 subnodes given by the 8*i ... 8*i+7 nodes at level k+1. * * The pruning step works as follows. We go from the lowest * level up. At each level, the threshold is found from the * product of a factor near 1.0 and the ratio of unmarked pixels * to remaining colors minus the 64. We march through * the space, sequentially considering a cube and its 8 subcubes. * We first check those subcubes that are not already * marked as CTE to see if any are above threshold, and if so, * generate a CTE and mark them as such. * We then determine if any of the subcubes have been marked. * If so, and there are subcubes that are not marked, * we generate a CTE for the cube from the remaining unmarked * subcubes; this is mandatory and does not depend on how many * pixels are in the set of subcubes. If none of the subcubes * are marked, we aggregate their pixels into the cube * containing them, but do not mark it as a CTE; that * will be determined when iterating through the next level up. * * When all the pixels in a cube are accounted for in one or more * colors, we set the boolean 'bleaf' to true. This is the * flag used to mark the cubes in the pruning step. If a cube * is marked, and all 8 subcubes are marked, then it is not * itself given a CTE because all pixels have already been * accounted for. * * Note that the pruning of the tree and labelling of the CTEs * step 2 accomplishes step 3 implicitly, because the marked * and pruned tree is ready for use in labelling each pixel * in step 4. We now, for every pixel in the image, traverse * the tree from the root, looking for the lowest cube that is a leaf. * At each level we have a cube and subcube. If we reach a subcube * leaf that is marked 0, we know that the color is stored in the * cube above, and we've found the CTE. Otherwise, the subcube * leaf is marked 1. If we're at the last level, we've reached * the final leaf and must use it. Otherwise, continue the * process at the next level down. * * For robustness, efficiency and high quality output, we do the following: * * (1) Measure the color content of the image. If there is very little * color, quantize in grayscale. * (2) For efficiency, build the octree with a subsampled image if the * image is larger than some threshold size. * (3) Reserve an extra set of colors to prevent running out of colors * when pruning the octree; specifically, during the assignment * of those level 2 cells out of the 64 that have unassigned * pixels. The problem of running out is more likely to happen * with small images, because the estimation we use for the * number of pixels available is not accurate. * (4) In the unlikely event that we run out of colors, the dithered * image can be very poor. As this would only happen with very * small images, and dithering is not particularly noticeable with * such images, turn it off. **/ PIX * pixOctreeColorQuant(PIX *pixs, l_int32 colors, l_int32 ditherflag) { PROCNAME("pixOctreeColorQuant"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); if (pixGetDepth(pixs) != 32) return (PIX *)ERROR_PTR("pixs not 32 bpp", procName, NULL); if (colors < 128 || colors > 240) /* further restricted */ return (PIX *)ERROR_PTR("colors must be in [128, 240]", procName, NULL); return pixOctreeColorQuantGeneral(pixs, colors, ditherflag, 0.01, 0.01); } /*! * \brief pixOctreeColorQuantGeneral() * * \param[in] pixs 32 bpp; 24-bit color * \param[in] colors in colormap; some number in range [128 ... 240]; * the actual number of colors used will be smaller * \param[in] ditherflag 1 to dither, 0 otherwise * \param[in] validthresh minimum fraction of pixels neither near white * nor black, required for color quantization; * typically ~0.01, but smaller for images that have * color but are nearly all white * \param[in] colorthresh minimum fraction of pixels with color that are * not near white or black, that are required * for color quantization; typ. ~0.01, but smaller * for images that have color along with a * significant fraction of gray * \return pixd 8 bit with colormap, or NULL on error * *
* Notes: * (1) The parameters %validthresh and %colorthresh are used to * determine if color quantization should be used on an image, * or whether, instead, it should be quantized in grayscale. * If the image has very few non-white and non-black pixels, or * if those pixels that are non-white and non-black are all * very close to either white or black, it is usually better * to treat the color as accidental and to quantize the image * to gray only. These parameters are useful if you know * something a priori about the image. Perhaps you know that * there is only a very small fraction of color pixels, but they're * important to preserve; then you want to use a smaller value for * these parameters. To disable conversion to gray and force * color quantization, use %validthresh = 0.0 and %colorthresh = 0.0. * (2) See pixOctreeColorQuant() for algorithmic and implementation * details. This function has a more general interface. * (3) See pixColorFraction() for computing the fraction of pixels * that are neither white nor black, and the fraction of those * pixels that have little color. From the documentation there: * If pixfract is very small, there are few pixels that are * neither black nor white. If colorfract is very small, * the pixels that are neither black nor white have very * little color content. The product 'pixfract * colorfract' * gives the fraction of pixels with significant color content. * We test against the product %validthresh * %colorthresh * to find color in images that have either very few * intermediate gray pixels or that have many such gray pixels. **/ PIX * pixOctreeColorQuantGeneral(PIX *pixs, l_int32 colors, l_int32 ditherflag, l_float32 validthresh, l_float32 colorthresh) { l_int32 w, h, minside, factor, index, rval, gval, bval; l_float32 scalefactor; l_float32 pixfract; /* fraction neither near white nor black */ l_float32 colorfract; /* fraction with color of the pixfract population */ CQCELL ***cqcaa; PIX *pixd, *pixsub; PIXCMAP *cmap; PROCNAME("pixOctreeColorQuantGeneral"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); if (pixGetDepth(pixs) != 32) return (PIX *)ERROR_PTR("pixs not 32 bpp", procName, NULL); if (colors < 128 || colors > 240) return (PIX *)ERROR_PTR("colors must be in [128, 240]", procName, NULL); /* Determine if the image has sufficient color content for * octree quantization, based on the input thresholds. * If pixfract << 1, most pixels are close to black or white. * If colorfract << 1, the pixels that are not near * black or white have very little color. * If with insufficient color, quantize with a grayscale colormap. */ pixGetDimensions(pixs, &w, &h, NULL); if (validthresh > 0.0 && colorthresh > 0.0) { minside = L_MIN(w, h); factor = L_MAX(1, minside / 400); pixColorFraction(pixs, 20, 244, 20, factor, &pixfract, &colorfract); if (pixfract * colorfract < validthresh * colorthresh) { L_INFO("\n Pixel fraction neither white nor black = %6.3f" "\n Color fraction of those pixels = %6.3f" "\n Quantizing to 8 bpp gray\n", procName, pixfract, colorfract); return pixConvertTo8(pixs, 1); } } else { L_INFO("\n Process in color by default\n", procName); } /* Conditionally subsample to speed up the first pass */ if (w > TreeGenWidth) { scalefactor = (l_float32)TreeGenWidth / (l_float32)w; pixsub = pixScaleBySampling(pixs, scalefactor, scalefactor); } else { pixsub = pixClone(pixs); } /* Drop the number of requested colors if image is very small */ if (w < MinDitherSize && h < MinDitherSize) colors = L_MIN(colors, 220); /* Make the pruned octree */ cqcaa = octreeGenerateAndPrune(pixsub, colors, CqReservedColors, &cmap); if (!cqcaa) { pixDestroy(&pixsub); return (PIX *)ERROR_PTR("tree not made", procName, NULL); } #if DEBUG_COLORQUANT L_INFO(" Colors requested = %d\n", procName, colors); L_INFO(" Actual colors = %d\n", procName, cmap->n); #endif /* DEBUG_COLORQUANT */ /* Do not dither if image is very small */ if (w < MinDitherSize && h < MinDitherSize && ditherflag == 1) { L_INFO("Small image: dithering turned off\n", procName); ditherflag = 0; } /* Traverse tree from root, looking for lowest cube * that is a leaf, and set dest pix value to its * colortable index */ if ((pixd = pixOctreeQuantizePixels(pixs, cqcaa, ditherflag)) == NULL) { pixDestroy(&pixsub); cqcellTreeDestroy(&cqcaa); return (PIX *)ERROR_PTR("pixd not made", procName, NULL); } /* Attach colormap and copy res */ pixSetColormap(pixd, cmap); pixCopyResolution(pixd, pixs); pixCopyInputFormat(pixd, pixs); /* Force darkest color to black if each component <= 4 */ pixcmapGetRankIntensity(cmap, 0.0, &index); pixcmapGetColor(cmap, index, &rval, &gval, &bval); if (rval < 5 && gval < 5 && bval < 5) pixcmapResetColor(cmap, index, 0, 0, 0); /* Force lightest color to white if each component >= 252 */ pixcmapGetRankIntensity(cmap, 1.0, &index); pixcmapGetColor(cmap, index, &rval, &gval, &bval); if (rval > 251 && gval > 251 && bval > 251) pixcmapResetColor(cmap, index, 255, 255, 255); cqcellTreeDestroy(&cqcaa); pixDestroy(&pixsub); return pixd; } /*! * \brief octreeGenerateAndPrune() * * \param[in] pixs * \param[in] colors number of colors to use between 128 and 256 * \param[in] reservedcolors number of reserved colors * \param[out] pcmap colormap returned * \return octree, colormap and number of colors used, or NULL * on error * *
* Notes: * (1) The number of colors in the cmap may differ from the number * of colors requested, but it will not be larger than 256 **/ static CQCELL *** octreeGenerateAndPrune(PIX *pixs, l_int32 colors, l_int32 reservedcolors, PIXCMAP **pcmap) { l_int32 rval, gval, bval, cindex; l_int32 level, ncells, octindex; l_int32 w, h, wpls; l_int32 i, j, isub; l_int32 npix; /* number of remaining pixels to be assigned */ l_int32 ncolor; /* number of remaining color cells to be used */ l_int32 ppc; /* ave number of pixels left for each color cell */ l_int32 rv, gv, bv; l_float32 thresholdFactor[] = {0.01f, 0.01f, 1.0f, 1.0f, 1.0f, 1.0f}; l_float32 thresh; /* factor of ppc for this level */ l_uint32 *datas, *lines; l_uint32 *rtab, *gtab, *btab; CQCELL ***cqcaa; /* one array for each octree level */ CQCELL **cqca, **cqcasub; CQCELL *cqc, *cqcsub; PIXCMAP *cmap; NUMA *nat; /* accumulates levels for threshold cells */ NUMA *nar; /* accumulates levels for residual cells */ PROCNAME("octreeGenerateAndPrune"); if (!pixs) return (CQCELL ***)ERROR_PTR("pixs not defined", procName, NULL); if (pixGetDepth(pixs) != 32) return (CQCELL ***)ERROR_PTR("pixs must be 32 bpp", procName, NULL); if (colors < 128 || colors > 256) return (CQCELL ***)ERROR_PTR("colors not in [128,256]", procName, NULL); if (!pcmap) return (CQCELL ***)ERROR_PTR("&cmap not defined", procName, NULL); if ((cqcaa = cqcellTreeCreate()) == NULL) return (CQCELL ***)ERROR_PTR("cqcaa not made", procName, NULL); /* Make the canonical index tables */ rtab = gtab = btab = NULL; makeRGBToIndexTables(CqNLevels, &rtab, >ab, &btab); /* Generate an 8 bpp cmap (max size 256) */ cmap = pixcmapCreate(8); *pcmap = cmap; pixGetDimensions(pixs, &w, &h, NULL); npix = w * h; /* initialize to all pixels */ ncolor = colors - reservedcolors - ExtraReservedColors; ppc = npix / ncolor; datas = pixGetData(pixs); wpls = pixGetWpl(pixs); /* Accumulate the centers of each cluster at level CqNLevels */ ncells = 1 << (3 * CqNLevels); cqca = cqcaa[CqNLevels]; for (i = 0; i < h; i++) { lines = datas + i * wpls; for (j = 0; j < w; j++) { extractRGBValues(lines[j], &rval, &gval, &bval); octindex = rtab[rval] | gtab[gval] | btab[bval]; cqc = cqca[octindex]; cqc->n++; } } /* Arrays for storing statistics */ nat = numaCreate(0); nar = numaCreate(0); /* Prune back from the lowest level and generate the colormap */ for (level = CqNLevels - 1; level >= 2; level--) { thresh = thresholdFactor[level]; cqca = cqcaa[level]; cqcasub = cqcaa[level + 1]; ncells = 1 << (3 * level); for (i = 0; i < ncells; i++) { /* i is octindex at level */ cqc = cqca[i]; for (j = 0; j < 8; j++) { /* check all subnodes */ isub = 8 * i + j; /* isub is octindex at level+1 */ cqcsub = cqcasub[isub]; if (cqcsub->bleaf == 1) { /* already a leaf? */ cqc->nleaves++; /* count the subcube leaves */ continue; } if (cqcsub->n >= thresh * ppc) { /* make it a true leaf? */ cqcsub->bleaf = 1; if (cmap->n < 256) { cqcsub->index = cmap->n; /* assign the color index */ getRGBFromOctcube(isub, level + 1, &rv, &gv, &bv); pixcmapAddColor(cmap, rv, gv, bv); #if 1 /* save values */ cqcsub->rc = rv; cqcsub->gc = gv; cqcsub->bc = bv; #endif } else { /* This doesn't seem to happen. Do something. */ L_ERROR("assigning pixels to wrong color\n", procName); pixcmapGetNearestIndex(cmap, 128, 128, 128, &cindex); cqcsub->index = cindex; /* assign to the nearest */ pixcmapGetColor(cmap, cindex, &rval, &gval, &bval); cqcsub->rc = rval; cqcsub->gc = gval; cqcsub->bc = bval; } cqc->nleaves++; npix -= cqcsub->n; ncolor--; if (ncolor > 0) ppc = npix / ncolor; else if (ncolor + reservedcolors > 0) ppc = npix / (ncolor + reservedcolors); else ppc = 1000000; /* make it big */ numaAddNumber(nat, level + 1); #if DEBUG_OCTCUBE_CMAP fprintf(stderr, "Exceeds threshold: colors used = %d, colors remaining = %d\n", cmap->n, ncolor + reservedcolors); fprintf(stderr, " cell with %d pixels, npix = %d, ppc = %d\n", cqcsub->n, npix, ppc); fprintf(stderr, " index = %d, level = %d, subindex = %d\n", i, level, j); fprintf(stderr, " rv = %d, gv = %d, bv = %d\n", rv, gv, bv); #endif /* DEBUG_OCTCUBE_CMAP */ } } if (cqc->nleaves > 0 || level == 2) { /* make the cube a leaf now */ cqc->bleaf = 1; if (cqc->nleaves < 8) { /* residual CTE cube: acquire the * remaining pixels */ for (j = 0; j < 8; j++) { /* check all subnodes */ isub = 8 * i + j; cqcsub = cqcasub[isub]; if (cqcsub->bleaf == 0) /* absorb */ cqc->n += cqcsub->n; } if (cmap->n < 256) { cqc->index = cmap->n; /* assign the color index */ getRGBFromOctcube(i, level, &rv, &gv, &bv); pixcmapAddColor(cmap, rv, gv, bv); #if 1 /* save values */ cqc->rc = rv; cqc->gc = gv; cqc->bc = bv; #endif } else { L_WARNING("possibly assigned pixels to wrong color\n", procName); /* This is very bad. It will only cause trouble * with dithering, and we try to avoid it with * ExtraReservedColors. */ pixcmapGetNearestIndex(cmap, rv, gv, bv, &cindex); cqc->index = cindex; /* assign to the nearest */ pixcmapGetColor(cmap, cindex, &rval, &gval, &bval); cqc->rc = rval; cqc->gc = gval; cqc->bc = bval; } npix -= cqc->n; ncolor--; if (ncolor > 0) ppc = npix / ncolor; else if (ncolor + reservedcolors > 0) ppc = npix / (ncolor + reservedcolors); else ppc = 1000000; /* make it big */ numaAddNumber(nar, level); #if DEBUG_OCTCUBE_CMAP fprintf(stderr, "By remainder: colors used = %d, colors remaining = %d\n", cmap->n, ncolor + reservedcolors); fprintf(stderr, " cell with %d pixels, npix = %d, ppc = %d\n", cqc->n, npix, ppc); fprintf(stderr, " index = %d, level = %d\n", i, level); fprintf(stderr, " rv = %d, gv = %d, bv = %d\n", rv, gv, bv); #endif /* DEBUG_OCTCUBE_CMAP */ } } else { /* absorb all the subpixels but don't make it a leaf */ for (j = 0; j < 8; j++) { /* absorb from all subnodes */ isub = 8 * i + j; cqcsub = cqcasub[isub]; cqc->n += cqcsub->n; } } } } #if PRINT_OCTCUBE_STATS { l_int32 tc[] = {0, 0, 0, 0, 0, 0, 0}; l_int32 rc[] = {0, 0, 0, 0, 0, 0, 0}; l_int32 nt, nr, ival; nt = numaGetCount(nat); nr = numaGetCount(nar); for (i = 0; i < nt; i++) { numaGetIValue(nat, i, &ival); tc[ival]++; } for (i = 0; i < nr; i++) { numaGetIValue(nar, i, &ival); rc[ival]++; } fprintf(stderr, " Threshold cells formed: %d\n", nt); for (i = 1; i < CqNLevels + 1; i++) fprintf(stderr, " level %d: %d\n", i, tc[i]); fprintf(stderr, "\n Residual cells formed: %d\n", nr); for (i = 0; i < CqNLevels ; i++) fprintf(stderr, " level %d: %d\n", i, rc[i]); } #endif /* PRINT_OCTCUBE_STATS */ numaDestroy(&nat); numaDestroy(&nar); LEPT_FREE(rtab); LEPT_FREE(gtab); LEPT_FREE(btab); return cqcaa; } /*! * \brief pixOctreeQuantizePixels() * * \param[in] pixs 32 bpp * \param[in] cqcaa octree in array format * \param[in] ditherflag 1 for dithering, 0 for no dithering * \return pixd or NULL on error * *
* Notes: * (1) This routine doesn't need to use the CTEs (colormap * table entries) because the color indices are embedded * in the octree. Thus, the calling program must make * and attach the colormap to pixd after it is returned. * (2) Dithering is performed in integers, effectively rounding * to 1/8 sample increment. The data in the integer buffers is * 64 times the sample values. The 'dif' is 8 times the * sample values, and this spread, multiplied by 8, to the * integer buffers. Because the dif is truncated to an * integer, the dither is accurate to 1/8 of a sample increment, * or 1/2048 of the color range. **/ static PIX * pixOctreeQuantizePixels(PIX *pixs, CQCELL ***cqcaa, l_int32 ditherflag) { l_uint8 *bufu8r, *bufu8g, *bufu8b; l_int32 rval, gval, bval; l_int32 octindex, index; l_int32 val1, val2, val3, dif; l_int32 w, h, wpls, wpld, i, j, success; l_int32 rc, gc, bc; l_int32 *buf1r, *buf1g, *buf1b, *buf2r, *buf2g, *buf2b; l_uint32 *rtab, *gtab, *btab; l_uint32 *datas, *datad, *lines, *lined; PIX *pixd; PROCNAME("pixOctreeQuantizePixels"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); if (pixGetDepth(pixs) != 32) return (PIX *)ERROR_PTR("pixs must be 32 bpp", procName, NULL); if (!cqcaa) return (PIX *)ERROR_PTR("cqcaa not defined", procName, NULL); /* Make output 8 bpp palette image */ pixGetDimensions(pixs, &w, &h, NULL); datas = pixGetData(pixs); wpls = pixGetWpl(pixs); if ((pixd = pixCreate(w, h, 8)) == NULL) return (PIX *)ERROR_PTR("pixd not made", procName, NULL); pixCopyResolution(pixd, pixs); pixCopyInputFormat(pixd, pixs); datad = pixGetData(pixd); wpld = pixGetWpl(pixd); /* Make the canonical index tables */ rtab = gtab = btab = NULL; makeRGBToIndexTables(CqNLevels, &rtab, >ab, &btab); /* Traverse tree from root, looking for lowest cube * that is a leaf, and set dest pix to its * colortable index value. The results are far * better when dithering to get a more accurate * average color. */ if (ditherflag == 0) { /* no dithering */ for (i = 0; i < h; i++) { lines = datas + i * wpls; lined = datad + i * wpld; for (j = 0; j < w; j++) { extractRGBValues(lines[j], &rval, &gval, &bval); octindex = rtab[rval] | gtab[gval] | btab[bval]; octreeFindColorCell(octindex, cqcaa, &index, &rc, &gc, &bc); SET_DATA_BYTE(lined, j, index); } } } else { /* Dither */ success = TRUE; bufu8r = bufu8g = bufu8b = NULL; buf1r = buf1g = buf1b = buf2r = buf2g = buf2b = NULL; bufu8r = (l_uint8 *)LEPT_CALLOC(w, sizeof(l_uint8)); bufu8g = (l_uint8 *)LEPT_CALLOC(w, sizeof(l_uint8)); bufu8b = (l_uint8 *)LEPT_CALLOC(w, sizeof(l_uint8)); buf1r = (l_int32 *)LEPT_CALLOC(w, sizeof(l_int32)); buf1g = (l_int32 *)LEPT_CALLOC(w, sizeof(l_int32)); buf1b = (l_int32 *)LEPT_CALLOC(w, sizeof(l_int32)); buf2r = (l_int32 *)LEPT_CALLOC(w, sizeof(l_int32)); buf2g = (l_int32 *)LEPT_CALLOC(w, sizeof(l_int32)); buf2b = (l_int32 *)LEPT_CALLOC(w, sizeof(l_int32)); if (!bufu8r || !bufu8g || !bufu8b || !buf1r || !buf1g || !buf1b || !buf2r || !buf2g || !buf2b) { L_ERROR("buffer not made\n", procName); success = FALSE; goto buffer_cleanup; } /* Start by priming buf2; line 1 is above line 2 */ pixGetRGBLine(pixs, 0, bufu8r, bufu8g, bufu8b); for (j = 0; j < w; j++) { buf2r[j] = 64 * bufu8r[j]; buf2g[j] = 64 * bufu8g[j]; buf2b[j] = 64 * bufu8b[j]; } for (i = 0; i < h - 1; i++) { /* Swap data 2 --> 1, and read in new line 2 */ memcpy(buf1r, buf2r, 4 * w); memcpy(buf1g, buf2g, 4 * w); memcpy(buf1b, buf2b, 4 * w); pixGetRGBLine(pixs, i + 1, bufu8r, bufu8g, bufu8b); for (j = 0; j < w; j++) { buf2r[j] = 64 * bufu8r[j]; buf2g[j] = 64 * bufu8g[j]; buf2b[j] = 64 * bufu8b[j]; } /* Dither */ lined = datad + i * wpld; for (j = 0; j < w - 1; j++) { rval = buf1r[j] / 64; gval = buf1g[j] / 64; bval = buf1b[j] / 64; octindex = rtab[rval] | gtab[gval] | btab[bval]; octreeFindColorCell(octindex, cqcaa, &index, &rc, &gc, &bc); SET_DATA_BYTE(lined, j, index); dif = buf1r[j] / 8 - 8 * rc; if (dif != 0) { val1 = buf1r[j + 1] + 3 * dif; val2 = buf2r[j] + 3 * dif; val3 = buf2r[j + 1] + 2 * dif; if (dif > 0) { buf1r[j + 1] = L_MIN(16383, val1); buf2r[j] = L_MIN(16383, val2); buf2r[j + 1] = L_MIN(16383, val3); } else { buf1r[j + 1] = L_MAX(0, val1); buf2r[j] = L_MAX(0, val2); buf2r[j + 1] = L_MAX(0, val3); } } dif = buf1g[j] / 8 - 8 * gc; if (dif != 0) { val1 = buf1g[j + 1] + 3 * dif; val2 = buf2g[j] + 3 * dif; val3 = buf2g[j + 1] + 2 * dif; if (dif > 0) { buf1g[j + 1] = L_MIN(16383, val1); buf2g[j] = L_MIN(16383, val2); buf2g[j + 1] = L_MIN(16383, val3); } else { buf1g[j + 1] = L_MAX(0, val1); buf2g[j] = L_MAX(0, val2); buf2g[j + 1] = L_MAX(0, val3); } } dif = buf1b[j] / 8 - 8 * bc; if (dif != 0) { val1 = buf1b[j + 1] + 3 * dif; val2 = buf2b[j] + 3 * dif; val3 = buf2b[j + 1] + 2 * dif; if (dif > 0) { buf1b[j + 1] = L_MIN(16383, val1); buf2b[j] = L_MIN(16383, val2); buf2b[j + 1] = L_MIN(16383, val3); } else { buf1b[j + 1] = L_MAX(0, val1); buf2b[j] = L_MAX(0, val2); buf2b[j + 1] = L_MAX(0, val3); } } } /* Get last pixel in row; no downward propagation */ rval = buf1r[w - 1] / 64; gval = buf1g[w - 1] / 64; bval = buf1b[w - 1] / 64; octindex = rtab[rval] | gtab[gval] | btab[bval]; octreeFindColorCell(octindex, cqcaa, &index, &rc, &gc, &bc); SET_DATA_BYTE(lined, w - 1, index); } /* Get last row of pixels; no leftward propagation */ lined = datad + (h - 1) * wpld; for (j = 0; j < w; j++) { rval = buf2r[j] / 64; gval = buf2g[j] / 64; bval = buf2b[j] / 64; octindex = rtab[rval] | gtab[gval] | btab[bval]; octreeFindColorCell(octindex, cqcaa, &index, &rc, &gc, &bc); SET_DATA_BYTE(lined, j, index); } buffer_cleanup: LEPT_FREE(bufu8r); LEPT_FREE(bufu8g); LEPT_FREE(bufu8b); LEPT_FREE(buf1r); LEPT_FREE(buf1g); LEPT_FREE(buf1b); LEPT_FREE(buf2r); LEPT_FREE(buf2g); LEPT_FREE(buf2b); if (!success) pixDestroy(&pixd); } LEPT_FREE(rtab); LEPT_FREE(gtab); LEPT_FREE(btab); return pixd; } /*! * \brief octreeFindColorCell() * * \param[in] octindex * \param[in] cqcaa * \param[out] pindex index of CTE; returned to set pixel value * \param[out] prval of CTE * \param[out] pgval of CTE * \param[out] pbval of CTE * \return 0 if OK; 1 on error * *
* Notes: * (1) As this is in inner loop, we don't check input pointers! * (2) This traverses from the root (well, actually from level 2, * because the level 2 cubes are the largest CTE cubes), * and finds the index number of the cell and the color values, * which can be used either directly or in a (Floyd-Steinberg) * error-diffusion dithering algorithm. **/ static l_int32 octreeFindColorCell(l_int32 octindex, CQCELL ***cqcaa, l_int32 *pindex, l_int32 *prval, l_int32 *pgval, l_int32 *pbval) { l_int32 level; l_int32 baseindex, subindex; CQCELL *cqc, *cqcsub; /* Use rgb values stored in the cubes; a little faster */ for (level = 2; level < CqNLevels; level++) { getOctcubeIndices(octindex, level, &baseindex, &subindex); cqc = cqcaa[level][baseindex]; cqcsub = cqcaa[level + 1][subindex]; if (cqcsub->bleaf == 0) { /* use cell at level above */ *pindex = cqc->index; *prval = cqc->rc; *pgval = cqc->gc; *pbval = cqc->bc; break; } else if (level == CqNLevels - 1) { /* reached the bottom */ *pindex = cqcsub->index; *prval = cqcsub->rc; *pgval = cqcsub->gc; *pbval = cqcsub->bc; break; } } #if 0 /* Generate rgb values for each cube on the fly; slower */ for (level = 2; level < CqNLevels; level++) { l_int32 rv, gv, bv; getOctcubeIndices(octindex, level, &baseindex, &subindex); cqc = cqcaa[level][baseindex]; cqcsub = cqcaa[level + 1][subindex]; if (cqcsub->bleaf == 0) { /* use cell at level above */ getRGBFromOctcube(baseindex, level, &rv, &gv, &bv); *pindex = cqc->index; *prval = rv; *pgval = gv; *pbval = bv; break; } else if (level == CqNLevels - 1) { /* reached the bottom */ getRGBFromOctcube(subindex, level + 1, &rv, &gv, &bv); *pindex = cqcsub->index; *prval = rv; *pgval = gv; *pbval = bv; break; } } #endif return 0; } /*------------------------------------------------------------------* * Helper cqcell functions * *------------------------------------------------------------------*/ /*! * \brief cqcellTreeCreate() * * \return cqcell array tree */ static CQCELL *** cqcellTreeCreate(void) { l_int32 level, ncells, i; CQCELL ***cqcaa; CQCELL **cqca; /* one array for each octree level */ PROCNAME("cqcellTreeCreate"); /* Make array of accumulation cell arrays from levels 1 to 5 */ if ((cqcaa = (CQCELL ***)LEPT_CALLOC(CqNLevels + 1, sizeof(CQCELL **))) == NULL) return (CQCELL ***)ERROR_PTR("cqcaa not made", procName, NULL); for (level = 0; level <= CqNLevels; level++) { ncells = 1 << (3 * level); if ((cqca = (CQCELL **)LEPT_CALLOC(ncells, sizeof(CQCELL *))) == NULL) { cqcellTreeDestroy(&cqcaa); return (CQCELL ***)ERROR_PTR("cqca not made", procName, NULL); } cqcaa[level] = cqca; for (i = 0; i < ncells; i++) { if ((cqca[i] = (CQCELL *)LEPT_CALLOC(1, sizeof(CQCELL))) == NULL) { cqcellTreeDestroy(&cqcaa); return (CQCELL ***)ERROR_PTR("cqc not made", procName, NULL); } } } return cqcaa; } /*! * \brief cqcellTreeDestroy() * * \param[in,out] pcqcaa will be set to null before returning */ static void cqcellTreeDestroy(CQCELL ****pcqcaa) { l_int32 level, ncells, i; CQCELL ***cqcaa; CQCELL **cqca; PROCNAME("cqcellTreeDestroy"); if (pcqcaa == NULL) { L_WARNING("ptr address is NULL\n", procName); return; } if ((cqcaa = *pcqcaa) == NULL) return; for (level = 0; level <= CqNLevels; level++) { cqca = cqcaa[level]; ncells = 1 << (3 * level); for (i = 0; i < ncells; i++) LEPT_FREE(cqca[i]); LEPT_FREE(cqca); } LEPT_FREE(cqcaa); *pcqcaa = NULL; return; } /*------------------------------------------------------------------* * Helper index functions * *------------------------------------------------------------------*/ /*! * \brief makeRGBToIndexTables() * * \param[in] cqlevels can be 1, 2, 3, 4, 5 or 6 * \param[out] prtab, pgtab, pbtab tables * \return 0 if OK; 1 on error * *
* Set up tables. e.g., for cqlevels = 5, we need an integer 0 < i < 2^15: * rtab = 0 i7 0 0 i6 0 0 i5 0 0 i4 0 0 i3 0 0 * gtab = 0 0 i7 0 0 i6 0 0 i5 0 0 i4 0 0 i3 0 * btab = 0 0 0 i7 0 0 i6 0 0 i5 0 0 i4 0 0 i3 * * The tables are then used to map from rbg --> index as follows: * index = 0 r7 g7 b7 r6 g6 b6 r5 g5 b5 r4 g4 b4 r3 g3 b3 * * e.g., for cqlevels = 4, we map to * index = 0 0 0 0 r7 g7 b7 r6 g6 b6 r5 g5 b5 r4 g4 b4 * * This may look a bit strange. The notation 'r7' means the MSBit of * the r value which has 8 bits, going down from r7 to r0. * Keep in mind that r7 is actually the r component bit for level 1 of * the octtree. Level 1 is composed of 8 octcubes, represented by * the bits r7 g7 b7, which divide the entire color space into * 8 cubes. At level 2, each of these 8 octcubes is further divided into * 8 cubes, each labeled by the second most significant bits r6 g6 b6 * of the rgb color. **/ l_ok makeRGBToIndexTables(l_int32 cqlevels, l_uint32 **prtab, l_uint32 **pgtab, l_uint32 **pbtab) { l_int32 i; l_uint32 *rtab, *gtab, *btab; PROCNAME("makeRGBToIndexTables"); if (cqlevels < 1 || cqlevels > 6) return ERROR_INT("cqlevels must be in {1,...6}", procName, 1); if (!prtab || !pgtab || !pbtab) return ERROR_INT("not all &tabs defined", procName, 1); rtab = (l_uint32 *)LEPT_CALLOC(256, sizeof(l_uint32)); gtab = (l_uint32 *)LEPT_CALLOC(256, sizeof(l_uint32)); btab = (l_uint32 *)LEPT_CALLOC(256, sizeof(l_uint32)); if (!rtab || !gtab || !btab) return ERROR_INT("calloc fail for tab", procName, 1); *prtab = rtab; *pgtab = gtab; *pbtab = btab; switch (cqlevels) { case 1: for (i = 0; i < 256; i++) { rtab[i] = (i >> 5) & 0x0004; gtab[i] = (i >> 6) & 0x0002; btab[i] = (i >> 7); } break; case 2: for (i = 0; i < 256; i++) { rtab[i] = ((i >> 2) & 0x0020) | ((i >> 4) & 0x0004); gtab[i] = ((i >> 3) & 0x0010) | ((i >> 5) & 0x0002); btab[i] = ((i >> 4) & 0x0008) | ((i >> 6) & 0x0001); } break; case 3: for (i = 0; i < 256; i++) { rtab[i] = ((i << 1) & 0x0100) | ((i >> 1) & 0x0020) | ((i >> 3) & 0x0004); gtab[i] = (i & 0x0080) | ((i >> 2) & 0x0010) | ((i >> 4) & 0x0002); btab[i] = ((i >> 1) & 0x0040) | ((i >> 3) & 0x0008) | ((i >> 5) & 0x0001); } break; case 4: for (i = 0; i < 256; i++) { rtab[i] = ((i << 4) & 0x0800) | ((i << 2) & 0x0100) | (i & 0x0020) | ((i >> 2) & 0x0004); gtab[i] = ((i << 3) & 0x0400) | ((i << 1) & 0x0080) | ((i >> 1) & 0x0010) | ((i >> 3) & 0x0002); btab[i] = ((i << 2) & 0x0200) | (i & 0x0040) | ((i >> 2) & 0x0008) | ((i >> 4) & 0x0001); } break; case 5: for (i = 0; i < 256; i++) { rtab[i] = ((i << 7) & 0x4000) | ((i << 5) & 0x0800) | ((i << 3) & 0x0100) | ((i << 1) & 0x0020) | ((i >> 1) & 0x0004); gtab[i] = ((i << 6) & 0x2000) | ((i << 4) & 0x0400) | ((i << 2) & 0x0080) | (i & 0x0010) | ((i >> 2) & 0x0002); btab[i] = ((i << 5) & 0x1000) | ((i << 3) & 0x0200) | ((i << 1) & 0x0040) | ((i >> 1) & 0x0008) | ((i >> 3) & 0x0001); } break; case 6: for (i = 0; i < 256; i++) { rtab[i] = ((i << 10) & 0x20000) | ((i << 8) & 0x4000) | ((i << 6) & 0x0800) | ((i << 4) & 0x0100) | ((i << 2) & 0x0020) | (i & 0x0004); gtab[i] = ((i << 9) & 0x10000) | ((i << 7) & 0x2000) | ((i << 5) & 0x0400) | ((i << 3) & 0x0080) | ((i << 1) & 0x0010) | ((i >> 1) & 0x0002); btab[i] = ((i << 8) & 0x8000) | ((i << 6) & 0x1000) | ((i << 4) & 0x0200) | ((i << 2) & 0x0040) | (i & 0x0008) | ((i >> 2) & 0x0001); } break; default: ERROR_INT("cqlevels not in [1...6]", procName, 1); break; } return 0; } /*! * \brief getOctcubeIndexFromRGB() * * \param[in] rval, gval, bval * \param[in] rtab, gtab, btab generated with makeRGBToIndexTables() * \param[out] pindex found index * \return void * *
* Notes: * No error checking! **/ void getOctcubeIndexFromRGB(l_int32 rval, l_int32 gval, l_int32 bval, l_uint32 *rtab, l_uint32 *gtab, l_uint32 *btab, l_uint32 *pindex) { *pindex = rtab[rval] | gtab[gval] | btab[bval]; return; } /*! * \brief getRGBFromOctcube() * * \param[in] cubeindex octcube index * \param[in] level at which index is expressed * \param[out] prval r val of this cube * \param[out] pgval g val of this cube * \param[out] pbval b val of this cube * \return void * *
* Notes: * (1) We can consider all octcube indices to represent a * specific point in color space: namely, the location * of the 'upper-left' corner of the cube, where indices * increase down and to the right. The upper left corner * of the color space is then 00000.... * (2) The 'rgbindex' is a 24-bit representation of the location, * in octcube notation, at the center of the octcube. * To get to the center of an octcube, you choose the 111 * octcube at the next lower level. * (3) For example, if the octcube index = 110101 (binary), * which is a level 2 expression, then the rgbindex * is the 24-bit representation of 110101111 (at level 3); * namely, 000110101111000000000000. The number is padded * with 3 leading 0s (because the representation uses * only 21 bits) and 12 trailing 0s (the default for * levels 4-7, which are contained within each of the level3 * octcubes. Then the rgb values for the center of the * octcube are: rval = 11100000, gval = 10100000, bval = 01100000 **/ static void getRGBFromOctcube(l_int32 cubeindex, l_int32 level, l_int32 *prval, l_int32 *pgval, l_int32 *pbval) { l_int32 rgbindex; /* Bring to format in 21 bits: (r7 g7 b7 r6 g6 b6 ...) */ /* This is valid for levels from 0 to 6 */ rgbindex = cubeindex << (3 * (7 - level)); /* upper corner of cube */ rgbindex |= (0x7 << (3 * (6 - level))); /* index to center of cube */ /* Extract separate pieces */ *prval = ((rgbindex >> 13) & 0x80) | ((rgbindex >> 11) & 0x40) | ((rgbindex >> 9) & 0x20) | ((rgbindex >> 7) & 0x10) | ((rgbindex >> 5) & 0x08) | ((rgbindex >> 3) & 0x04) | ((rgbindex >> 1) & 0x02); *pgval = ((rgbindex >> 12) & 0x80) | ((rgbindex >> 10) & 0x40) | ((rgbindex >> 8) & 0x20) | ((rgbindex >> 6) & 0x10) | ((rgbindex >> 4) & 0x08) | ((rgbindex >> 2) & 0x04) | (rgbindex & 0x02); *pbval = ((rgbindex >> 11) & 0x80) | ((rgbindex >> 9) & 0x40) | ((rgbindex >> 7) & 0x20) | ((rgbindex >> 5) & 0x10) | ((rgbindex >> 3) & 0x08) | ((rgbindex >> 1) & 0x04) | ((rgbindex << 1) & 0x02); return; } /*! * \brief getOctcubeIndices() * * \param[in] rgbindex * \param[in] level octree level 0, 1, 2, 3, 4, 5 * \param[out] pbindex base index index at the octree level * \param[out] psindex sub index index at the next lower level * \return 0 if OK, 1 on error * *
* Notes: * for CqNLevels = 6, the full RGB index is in the form: * index = (0[13] 0 r7 g7 b7 r6 g6 b6 r5 g5 b5 r4 g4 b4 r3 g3 b3 r2 g2 b2) * for CqNLevels = 5, the full RGB index is in the form: * index = (0[16] 0 r7 g7 b7 r6 g6 b6 r5 g5 b5 r4 g4 b4 r3 g3 b3) * for CqNLevels = 4, the full RGB index is in the form: * index = (0[19] 0 r7 g7 b7 r6 g6 b6 r5 g5 b5 r4 g4 b4) * * The base index is the index of the octcube at the level given, * whereas the sub index is the index at the next level down. * * For level 0: base index = 0 * sub index is the 3 bit number (r7 g7 b7) * For level 1: base index = (r7 g7 b7) * sub index = (r7 g7 b7 r6 g6 b6) * For level 2: base index = (r7 g7 b7 r6 g6 b6) * sub index = (r7 g7 b7 r6 g6 b6 r5 g5 b5) * For level 3: base index = (r7 g7 b7 r6 g6 b6 r5 g5 b5) * sub index = (r7 g7 b7 r6 g6 b6 r5 g5 b5 r4 g4 b4) * For level 4: base index = (r7 g7 b7 r6 g6 b6 r5 g5 b5 r4 g4 b4) * sub index = (r7 g7 b7 r6 g6 b6 r5 g5 b5 r4 g4 b4 r3 g3 b3) * For level 5: base index = (r7 g7 b7 r6 g6 b6 r5 g5 b5 r4 g4 b4 r3 g3 b3) * sub index = (r7 g7 b7 r6 g6 b6 r5 g5 b5 r4 g4 b4 r3 g3 b3 * r2 g2 b2) **/ static l_int32 getOctcubeIndices(l_int32 rgbindex, l_int32 level, l_int32 *pbindex, l_int32 *psindex) { PROCNAME("getOctcubeIndex"); if (level < 0 || level > CqNLevels - 1) return ERROR_INT("level must be in e.g., [0 ... 5]", procName, 1); if (!pbindex) return ERROR_INT("&bindex not defined", procName, 1); if (!psindex) return ERROR_INT("&sindex not defined", procName, 1); *pbindex = rgbindex >> (3 * (CqNLevels - level)); *psindex = rgbindex >> (3 * (CqNLevels - 1 - level)); return 0; } /*! * \brief octcubeGetCount() * * \param[in] level valid values are in [1,...6]; there are 2^level * cubes along each side of the rgb cube * \param[out] psize 2^(3 * level) cubes in the entire rgb cube * \return 0 if OK, 1 on error. Caller must check! * *
* level: 1 2 3 4 5 6 * size: 8 64 512 4098 32784 262272 **/ static l_int32 octcubeGetCount(l_int32 level, l_int32 *psize) { PROCNAME("octcubeGetCount"); if (!psize) return ERROR_INT("&size not defined", procName, 1); if (level < 1 || level > 6) return ERROR_INT("invalid level", procName, 1); *psize = 1 << (3 * level); return 0; } /*---------------------------------------------------------------------------* * Adaptive octree quantization based on population at a fixed level * *---------------------------------------------------------------------------*/ /*! * \brief pixOctreeQuantByPopulation() * * \param[in] pixs 32 bpp rgb * \param[in] level significant bits for each of RGB; valid for {3,4}. * Use 0 for default (level 4; recommended * \param[in] ditherflag 1 to dither, 0 otherwise * \return pixd quantized to octcubes or NULL on error * *
* Notes: * (1) This color quantization method works very well without * dithering, using octcubes at two different levels: * (a) the input %level, which is either 3 or 4 * (b) level 2 (64 octcubes to cover the entire color space) * (2) For best results, using %level = 4 is recommended. * Why do we provide an option for using level 3? Because * there are 512 octcubes at level 3, and for many images * not more than 256 are filled. As a result, on some images * a very accurate quantized representation is possible using * %level = 3. * (3) This first breaks up the color space into octcubes at the * input %level, and computes, for each octcube, the average * value of the pixels that are in it. * (4) Then there are two possible situations: * (a) If there are not more than 256 populated octcubes, * it returns a cmapped pix with those values assigned. * (b) Otherwise, it selects 192 octcubes containing the largest * number of pixels and quantizes pixels within those octcubes * to their average. Then, to handle the residual pixels * that are not in those 192 octcubes, it generates a * level 2 octree consisting of 64 octcubes, and within * each octcube it quantizes the residual pixels to their * average within each of those level 2 octcubes. * (5) Unpopulated level 2 octcubes are represented in the colormap * by their centers. This, of course, has no effect unless * dithering is used for the output image. * (6) The depth of pixd is the minimum required to support the * number of colors found at %level; namely, 2, 4 or 8. * (7) This function works particularly well on images such as maps, * where there are a relatively small number of well-populated * colors, but due to antialiasing and compression artifacts * there may be a large number of different colors. This will * pull out and represent accurately the highly populated colors, * while still making a reasonable approximation for the others. * (8) The highest level of octcubes allowed is 4. Use of higher * levels typically results in having a small fraction of * pixels in the most populated 192 octcubes. As a result, * most of the pixels are represented at level 2, which is * not sufficiently accurate. * (9) Dithering shows artifacts on some images. If you plan to * dither, pixOctreeColorQuant() and pixFixedOctcubeQuant256() * usually give better results. **/ PIX * pixOctreeQuantByPopulation(PIX *pixs, l_int32 level, l_int32 ditherflag) { l_int32 w, h, wpls, wpld, i, j, depth, size, ncolors, index; l_int32 rval, gval, bval; l_int32 *rarray, *garray, *barray, *narray, *iarray; l_uint32 octindex, octindex2; l_uint32 *rtab, *gtab, *btab, *rtab2, *gtab2, *btab2; l_uint32 *lines, *lined, *datas, *datad; L_OCTCUBE_POP *opop; L_HEAP *lh; PIX *pixd; PIXCMAP *cmap; PROCNAME("pixOctreeQuantByPopulation"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); if (pixGetDepth(pixs) != 32) return (PIX *)ERROR_PTR("pixs not 32 bpp", procName, NULL); if (level == 0) level = 4; if (level < 3 || level > 4) return (PIX *)ERROR_PTR("level not in {3,4}", procName, NULL); /* Do not dither if image is very small */ pixGetDimensions(pixs, &w, &h, NULL); if (w < MinDitherSize && h < MinDitherSize && ditherflag == 1) { L_INFO("Small image: dithering turned off\n", procName); ditherflag = 0; } if (octcubeGetCount(level, &size)) /* array size = 2 ** (3 * level) */ return (PIX *)ERROR_PTR("size not returned", procName, NULL); rtab = gtab = btab = NULL; makeRGBToIndexTables(level, &rtab, >ab, &btab); pixd = NULL; narray = (l_int32 *)LEPT_CALLOC(size, sizeof(l_int32)); rarray = (l_int32 *)LEPT_CALLOC(size, sizeof(l_int32)); garray = (l_int32 *)LEPT_CALLOC(size, sizeof(l_int32)); barray = (l_int32 *)LEPT_CALLOC(size, sizeof(l_int32)); if (!narray || !rarray || !garray || !barray) goto array_cleanup; /* Place the pixels in octcube leaves. */ datas = pixGetData(pixs); wpls = pixGetWpl(pixs); for (i = 0; i < h; i++) { lines = datas + i * wpls; for (j = 0; j < w; j++) { extractRGBValues(lines[j], &rval, &gval, &bval); octindex = rtab[rval] | gtab[gval] | btab[bval]; narray[octindex]++; rarray[octindex] += rval; garray[octindex] += gval; barray[octindex] += bval; } } /* Find the number of different colors */ for (i = 0, ncolors = 0; i < size; i++) { if (narray[i] > 0) ncolors++; } if (ncolors <= 4) depth = 2; else if (ncolors <= 16) depth = 4; else depth = 8; pixd = pixCreate(w, h, depth); datad = pixGetData(pixd); wpld = pixGetWpl(pixd); pixCopyResolution(pixd, pixs); pixCopyInputFormat(pixd, pixs); cmap = pixcmapCreate(depth); pixSetColormap(pixd, cmap); /* Average the colors in each octcube leaf. */ for (i = 0; i < size; i++) { if (narray[i] > 0) { rarray[i] /= narray[i]; garray[i] /= narray[i]; barray[i] /= narray[i]; } } /* If ncolors <= 256, finish immediately. Do not dither. * Re-use narray to hold the colormap index + 1 */ if (ncolors <= 256) { for (i = 0, index = 0; i < size; i++) { if (narray[i] > 0) { pixcmapAddColor(cmap, rarray[i], garray[i], barray[i]); narray[i] = index + 1; /* to avoid storing 0 */ index++; } } /* Set the cmap indices for each pixel */ for (i = 0; i < h; i++) { lines = datas + i * wpls; lined = datad + i * wpld; for (j = 0; j < w; j++) { extractRGBValues(lines[j], &rval, &gval, &bval); octindex = rtab[rval] | gtab[gval] | btab[bval]; switch (depth) { case 8: SET_DATA_BYTE(lined, j, narray[octindex] - 1); break; case 4: SET_DATA_QBIT(lined, j, narray[octindex] - 1); break; case 2: SET_DATA_DIBIT(lined, j, narray[octindex] - 1); break; default: L_WARNING("shouldn't get here\n", procName); } } } goto array_cleanup; } /* More complicated. Sort by decreasing population */ lh = lheapCreate(500, L_SORT_DECREASING); for (i = 0; i < size; i++) { if (narray[i] > 0) { opop = (L_OCTCUBE_POP *)LEPT_CALLOC(1, sizeof(L_OCTCUBE_POP)); opop->npix = (l_float32)narray[i]; opop->index = i; opop->rval = rarray[i]; opop->gval = garray[i]; opop->bval = barray[i]; lheapAdd(lh, opop); } } /* Take the top 192. These will form the first 192 colors * in the cmap. iarray[i] holds the index into the cmap. */ iarray = (l_int32 *)LEPT_CALLOC(size, sizeof(l_int32)); for (i = 0; i < 192; i++) { opop = (L_OCTCUBE_POP*)lheapRemove(lh); if (!opop) break; pixcmapAddColor(cmap, opop->rval, opop->gval, opop->bval); iarray[opop->index] = i + 1; /* +1 to avoid storing 0 */ #if DEBUG_POP fprintf(stderr, "i = %d, n = %6.0f, (r,g,b) = (%d %d %d)\n", i, opop->npix, opop->rval, opop->gval, opop->bval); #endif /* DEBUG_POP */ LEPT_FREE(opop); } /* Make the octindex tables for level 2, and reuse rarray, etc. */ rtab2 = gtab2 = btab2 = NULL; makeRGBToIndexTables(2, &rtab2, >ab2, &btab2); for (i = 0; i < 64; i++) { narray[i] = 0; rarray[i] = 0; garray[i] = 0; barray[i] = 0; } /* Take the rest of the occupied octcubes, assigning the pixels * to these new colormap indices. iarray[] is addressed * by %level octcube indices, and it now holds the * colormap indices for all pixels in pixs. */ for (i = 192; i < size; i++) { opop = (L_OCTCUBE_POP*)lheapRemove(lh); if (!opop) break; rval = opop->rval; gval = opop->gval; bval = opop->bval; octindex2 = rtab2[rval] | gtab2[gval] | btab2[bval]; narray[octindex2] += (l_int32)opop->npix; rarray[octindex2] += (l_int32)opop->npix * rval; garray[octindex2] += (l_int32)opop->npix * gval; barray[octindex2] += (l_int32)opop->npix * bval; iarray[opop->index] = 192 + octindex2 + 1; /* +1 to avoid storing 0 */ LEPT_FREE(opop); } lheapDestroy(&lh, TRUE); /* To span the full color space, which is necessary for dithering, * set each iarray element whose value is still 0 at the input * level octcube leaves (because there were no pixels in those * octcubes) to the colormap index corresponding to its level 2 * octcube. */ if (ditherflag) { for (i = 0; i < size; i++) { if (iarray[i] == 0) { getRGBFromOctcube(i, level, &rval, &gval, &bval); octindex2 = rtab2[rval] | gtab2[gval] | btab2[bval]; iarray[i] = 192 + octindex2 + 1; } } } LEPT_FREE(rtab2); LEPT_FREE(gtab2); LEPT_FREE(btab2); /* Average the colors from the residuals in each level 2 octcube, * and add these 64 values to the colormap. */ for (i = 0; i < 64; i++) { if (narray[i] > 0) { rarray[i] /= narray[i]; garray[i] /= narray[i]; barray[i] /= narray[i]; } else { /* no pixels in this octcube; use center value */ getRGBFromOctcube(i, 2, &rarray[i], &garray[i], &barray[i]); } pixcmapAddColor(cmap, rarray[i], garray[i], barray[i]); } /* Set the cmap indices for each pixel. Subtract 1 from * the value in iarray[] because we added 1 earlier. */ if (ditherflag == 0) { for (i = 0; i < h; i++) { lines = datas + i * wpls; lined = datad + i * wpld; for (j = 0; j < w; j++) { extractRGBValues(lines[j], &rval, &gval, &bval); octindex = rtab[rval] | gtab[gval] | btab[bval]; SET_DATA_BYTE(lined, j, iarray[octindex] - 1); } } } else { /* dither */ pixDitherOctindexWithCmap(pixs, pixd, rtab, gtab, btab, iarray, POP_DIF_CAP); } #if DEBUG_POP for (i = 0; i < size / 16; i++) { l_int32 j; for (j = 0; j < 16; j++) fprintf(stderr, "%d ", iarray[16 * i + j]); fprintf(stderr, "\n"); } #endif /* DEBUG_POP */ LEPT_FREE(iarray); array_cleanup: LEPT_FREE(narray); LEPT_FREE(rarray); LEPT_FREE(garray); LEPT_FREE(barray); LEPT_FREE(rtab); LEPT_FREE(gtab); LEPT_FREE(btab); return pixd; } /*! * \brief pixDitherOctindexWithCmap() * * \param[in] pixs 32 bpp rgb * \param[in] pixd 8 bpp cmapped * \param[in] rtab, gtab, btab tables from rval to octindex * \param[in] indexmap array mapping octindex to cmap index * \param[in] difcap max allowed dither transfer; * use 0 for infinite cap * \return 0 if OK, 1 on error * *
* Notes: * (1) This performs dithering to generate the colormap indices * in pixd. The colormap has been calculated, along with * four input LUTs that together give the inverse colormapping * from RGB to colormap index. * (2) For pixOctreeQuantByPopulation(), %indexmap maps from the * standard octindex to colormap index (after subtracting 1). * The basic pixel-level function, without dithering, is: * extractRGBValues(lines[j], &rval, &gval, &bval); * octindex = rtab[rval] | gtab[gval] | btab[bval]; * SET_DATA_BYTE(lined, j, indexmap[octindex] - 1); * (3) This can be used in any situation where the general * prescription for finding the colormap index from the rgb * value is precisely this: * cmapindex = indexmap[rtab[rval] | gtab[gval] | btab[bval]] - 1 * For example, in pixFixedOctcubeQuant256(), we don't use * standard octcube indexing, the rtab (etc) LUTs map directly * to the colormap index, and %indexmap just compensates for * the 1-off indexing assumed to be in that table. **/ static l_int32 pixDitherOctindexWithCmap(PIX *pixs, PIX *pixd, l_uint32 *rtab, l_uint32 *gtab, l_uint32 *btab, l_int32 *indexmap, l_int32 difcap) { l_uint8 *bufu8r, *bufu8g, *bufu8b; l_int32 i, j, w, h, wpld, octindex, cmapindex, success; l_int32 rval, gval, bval, rc, gc, bc; l_int32 dif, val1, val2, val3; l_int32 *buf1r, *buf1g, *buf1b, *buf2r, *buf2g, *buf2b; l_uint32 *datad, *lined; PIXCMAP *cmap; PROCNAME("pixDitherOctindexWithCmap"); if (!pixs || pixGetDepth(pixs) != 32) return ERROR_INT("pixs undefined or not 32 bpp", procName, 1); if (!pixd || pixGetDepth(pixd) != 8) return ERROR_INT("pixd undefined or not 8 bpp", procName, 1); if ((cmap = pixGetColormap(pixd)) == NULL) return ERROR_INT("pixd not cmapped", procName, 1); if (!rtab || !gtab || !btab || !indexmap) return ERROR_INT("not all 4 tables defined", procName, 1); pixGetDimensions(pixs, &w, &h, NULL); if (pixGetWidth(pixd) != w || pixGetHeight(pixd) != h) return ERROR_INT("pixs and pixd not same size", procName, 1); success = TRUE; bufu8r = bufu8g = bufu8b = NULL; buf1r = buf1g = buf1b = buf2r = buf2g = buf2b = NULL; bufu8r = (l_uint8 *)LEPT_CALLOC(w, sizeof(l_uint8)); bufu8g = (l_uint8 *)LEPT_CALLOC(w, sizeof(l_uint8)); bufu8b = (l_uint8 *)LEPT_CALLOC(w, sizeof(l_uint8)); buf1r = (l_int32 *)LEPT_CALLOC(w, sizeof(l_int32)); buf1g = (l_int32 *)LEPT_CALLOC(w, sizeof(l_int32)); buf1b = (l_int32 *)LEPT_CALLOC(w, sizeof(l_int32)); buf2r = (l_int32 *)LEPT_CALLOC(w, sizeof(l_int32)); buf2g = (l_int32 *)LEPT_CALLOC(w, sizeof(l_int32)); buf2b = (l_int32 *)LEPT_CALLOC(w, sizeof(l_int32)); if (!bufu8r || !bufu8g || !bufu8b || !buf1r || !buf1g || !buf1b || !buf2r || !buf2g || !buf2b) { L_ERROR("buffer not made\n", procName); success = FALSE; goto buffer_cleanup; } /* Start by priming buf2; line 1 is above line 2 */ pixGetRGBLine(pixs, 0, bufu8r, bufu8g, bufu8b); for (j = 0; j < w; j++) { buf2r[j] = 64 * bufu8r[j]; buf2g[j] = 64 * bufu8g[j]; buf2b[j] = 64 * bufu8b[j]; } datad = pixGetData(pixd); wpld = pixGetWpl(pixd); for (i = 0; i < h - 1; i++) { /* Swap data 2 --> 1, and read in new line 2 */ memcpy(buf1r, buf2r, 4 * w); memcpy(buf1g, buf2g, 4 * w); memcpy(buf1b, buf2b, 4 * w); pixGetRGBLine(pixs, i + 1, bufu8r, bufu8g, bufu8b); for (j = 0; j < w; j++) { buf2r[j] = 64 * bufu8r[j]; buf2g[j] = 64 * bufu8g[j]; buf2b[j] = 64 * bufu8b[j]; } /* Dither */ lined = datad + i * wpld; for (j = 0; j < w - 1; j++) { rval = buf1r[j] / 64; gval = buf1g[j] / 64; bval = buf1b[j] / 64; octindex = rtab[rval] | gtab[gval] | btab[bval]; cmapindex = indexmap[octindex] - 1; SET_DATA_BYTE(lined, j, cmapindex); pixcmapGetColor(cmap, cmapindex, &rc, &gc, &bc); dif = buf1r[j] / 8 - 8 * rc; if (difcap > 0) { if (dif > difcap) dif = difcap; if (dif < -difcap) dif = -difcap; } if (dif != 0) { val1 = buf1r[j + 1] + 3 * dif; val2 = buf2r[j] + 3 * dif; val3 = buf2r[j + 1] + 2 * dif; if (dif > 0) { buf1r[j + 1] = L_MIN(16383, val1); buf2r[j] = L_MIN(16383, val2); buf2r[j + 1] = L_MIN(16383, val3); } else { buf1r[j + 1] = L_MAX(0, val1); buf2r[j] = L_MAX(0, val2); buf2r[j + 1] = L_MAX(0, val3); } } dif = buf1g[j] / 8 - 8 * gc; if (difcap > 0) { if (dif > difcap) dif = difcap; if (dif < -difcap) dif = -difcap; } if (dif != 0) { val1 = buf1g[j + 1] + 3 * dif; val2 = buf2g[j] + 3 * dif; val3 = buf2g[j + 1] + 2 * dif; if (dif > 0) { buf1g[j + 1] = L_MIN(16383, val1); buf2g[j] = L_MIN(16383, val2); buf2g[j + 1] = L_MIN(16383, val3); } else { buf1g[j + 1] = L_MAX(0, val1); buf2g[j] = L_MAX(0, val2); buf2g[j + 1] = L_MAX(0, val3); } } dif = buf1b[j] / 8 - 8 * bc; if (difcap > 0) { if (dif > difcap) dif = difcap; if (dif < -difcap) dif = -difcap; } if (dif != 0) { val1 = buf1b[j + 1] + 3 * dif; val2 = buf2b[j] + 3 * dif; val3 = buf2b[j + 1] + 2 * dif; if (dif > 0) { buf1b[j + 1] = L_MIN(16383, val1); buf2b[j] = L_MIN(16383, val2); buf2b[j + 1] = L_MIN(16383, val3); } else { buf1b[j + 1] = L_MAX(0, val1); buf2b[j] = L_MAX(0, val2); buf2b[j + 1] = L_MAX(0, val3); } } } /* Get last pixel in row; no downward propagation */ rval = buf1r[w - 1] / 64; gval = buf1g[w - 1] / 64; bval = buf1b[w - 1] / 64; octindex = rtab[rval] | gtab[gval] | btab[bval]; cmapindex = indexmap[octindex] - 1; SET_DATA_BYTE(lined, w - 1, cmapindex); } /* Get last row of pixels; no leftward propagation */ lined = datad + (h - 1) * wpld; for (j = 0; j < w; j++) { rval = buf2r[j] / 64; gval = buf2g[j] / 64; bval = buf2b[j] / 64; octindex = rtab[rval] | gtab[gval] | btab[bval]; cmapindex = indexmap[octindex] - 1; SET_DATA_BYTE(lined, j, cmapindex); } buffer_cleanup: LEPT_FREE(bufu8r); LEPT_FREE(bufu8g); LEPT_FREE(bufu8b); LEPT_FREE(buf1r); LEPT_FREE(buf1g); LEPT_FREE(buf1b); LEPT_FREE(buf2r); LEPT_FREE(buf2g); LEPT_FREE(buf2b); return (success) ? 0 : 1; } /*---------------------------------------------------------------------------* * Adaptive octree quantization to 4 and 8 bpp with max colors * *---------------------------------------------------------------------------*/ /*! * \brief pixOctreeQuantNumColors() * * \param[in] pixs 32 bpp rgb * \param[in] maxcolors 8 to 256; the actual number of colors used * may be less than this * \param[in] subsample factor for computing color distribution; * use 0 for default * \return pixd 4 or 8 bpp, colormapped, or NULL on error * *
* pixOctreeColorQuant is very flexible in terms of the relative * depth of different cubes of the octree. By contrast, this function, * pixOctreeQuantNumColors is also adaptive, but it supports octcube * leaves at only two depths: a smaller depth that guarantees * full coverage of the color space and octcubes at one level * deeper for more accurate colors. Its main virutes are simplicity * and speed, which are both derived from the natural indexing of * the octcubes from the RGB values. * * Before describing pixOctreeQuantNumColors, consider an even simpler * approach for 4 bpp with either 8 or 16 colors. With 8 colors, * you simply go to level 1 octcubes and use the average color * found in each cube. For 16 colors, you find which of the three * colors has the largest variance at the second level, and use two * indices for that color. The result is quite poor, because 1 some * of the cubes are nearly empty and 2 you don't get much color * differentiation for the extra 8 colors. Trust me, this method may * be simple, but it isn't worth anything. * * In pixOctreeQuantNumColors, we generate colormapped images at * either 4 bpp or 8 bpp. For 4 bpp, we have a minimum of 8 colors * for the level 1 octcubes, plus up to 8 additional colors that * are determined from the level 2 popularity. If the number of colors * is between 8 and 16, the output is a 4 bpp image. If the number of * colors is greater than 16, the output is a 8 bpp image. * * We use a priority queue, implemented with a heap, to select the * requisite number of most populated octcubes at the deepest level * level 2 for 64 or fewer colors; level 3 for more than 64 colors. * These are combined with one color for each octcube one level above, * which is used to span the color space of octcubes that were not * included at the deeper level. * * If the deepest level is 2, we combine the popular level 2 octcubes * out of a total of 64 with the 8 level 1 octcubes. If the deepest * level is 3, we combine the popular level 3 octcubes out of a * total 512 with the 64 level 2 octcubes that span the color space. * In the latter case, we require a minimum of 64 colors for the level 2 * octcubes, plus up to 192 additional colors determined from level 3 * popularity. * * The parameter 'maxlevel' is the deepest octcube level that is used. * The implementation also uses two LUTs, which are employed in * two successive traversals of the dest image. The first maps * from the src octindex at 'maxlevel' to the color table index, * which is the value that is stored in the 4 or 8 bpp dest pixel. * The second LUT maps from that colormap value in the dest to a * new colormap value for a minimum sized colormap, stored back in * the dest. It is used to remove any color map entries that * correspond to color space regions that have no pixels in the * source image. These regions can be either from the higher level * e.g., level 1 for 4 bpp, or from octcubes at 'maxlevel' that * are unoccupied. This remapping results in the minimum number * of colors used according to the constraints induced by the * input 'maxcolors'. We also compute the average R, G and B color * values in each region of the color space represented by a * colormap entry, and store them in the colormap. * * The maximum number of colors is input, which determines the * following properties of the dest image and octcube regions used: * * Number of colors dest image depth maxlevel * ---------------- ---------------- -------- * 8 to 16 4 bpp 2 * 17 to 64 8 bpp 2 * 65 to 256 8 bpp 3 * * It may turn out that the number of extra colors, beyond the * minimum 8 and 64 for maxlevel 2 and 3, respectively, is larger * than the actual number of occupied cubes at these levels * In that case, all the pixels are contained in this * subset of cubes at maxlevel, and no colormap colors are needed * to represent the remainder pixels one level above. Thus, for * example, in use one often finds that the pixels in an image * occupy less than 192 octcubes at level 3, so they can be represented * by a colormap for octcubes at level 3 only. **/ PIX * pixOctreeQuantNumColors(PIX *pixs, l_int32 maxcolors, l_int32 subsample) { l_int32 w, h, minside, bpp, wpls, wpld, i, j, actualcolors; l_int32 rval, gval, bval, nbase, nextra, maxlevel, ncubes, val; l_int32 *lut1, *lut2; l_uint32 index; l_uint32 *lines, *lined, *datas, *datad, *pspixel; l_uint32 *rtab, *gtab, *btab; OQCELL *oqc; OQCELL **oqca; L_HEAP *lh; PIX *pixd; PIXCMAP *cmap; PROCNAME("pixOctreeQuantNumColors"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); if (pixGetDepth(pixs) != 32) return (PIX *)ERROR_PTR("pixs not 32 bpp", procName, NULL); if (maxcolors < 8) { L_WARNING("max colors < 8; setting to 8\n", procName); maxcolors = 8; } if (maxcolors > 256) { L_WARNING("max colors > 256; setting to 256\n", procName); maxcolors = 256; } pixGetDimensions(pixs, &w, &h, NULL); datas = pixGetData(pixs); wpls = pixGetWpl(pixs); minside = L_MIN(w, h); if (subsample <= 0) { subsample = L_MAX(1, minside / 200); } if (maxcolors <= 16) { bpp = 4; pixd = pixCreate(w, h, bpp); maxlevel = 2; ncubes = 64; /* 2^6 */ nbase = 8; nextra = maxcolors - nbase; } else if (maxcolors <= 64) { bpp = 8; pixd = pixCreate(w, h, bpp); maxlevel = 2; ncubes = 64; /* 2^6 */ nbase = 8; nextra = maxcolors - nbase; } else { /* maxcolors <= 256 */ bpp = 8; pixd = pixCreate(w, h, bpp); maxlevel = 3; ncubes = 512; /* 2^9 */ nbase = 64; nextra = maxcolors - nbase; } pixCopyResolution(pixd, pixs); pixCopyInputFormat(pixd, pixs); /*----------------------------------------------------------* * If we're using the minimum number of colors, it is * * much simpler. We just use 'nbase' octcubes. * * For this case, we don't eliminate any extra colors. * *----------------------------------------------------------*/ if (nextra == 0) { /* prepare the OctcubeQuantCell array */ if ((oqca = (OQCELL **)LEPT_CALLOC(nbase, sizeof(OQCELL *))) == NULL) { pixDestroy(&pixd); return (PIX *)ERROR_PTR("oqca not made", procName, NULL); } for (i = 0; i < nbase; i++) { oqca[i] = (OQCELL *)LEPT_CALLOC(1, sizeof(OQCELL)); oqca[i]->n = 0.0; } rtab = gtab = btab = NULL; makeRGBToIndexTables(maxlevel - 1, &rtab, >ab, &btab); /* Go through the entire image, gathering statistics and * assigning pixels to their quantized value */ datad = pixGetData(pixd); wpld = pixGetWpl(pixd); for (i = 0; i < h; i++) { lines = datas + i * wpls; lined = datad + i * wpld; for (j = 0; j < w; j++) { pspixel = lines + j; extractRGBValues(*pspixel, &rval, &gval, &bval); getOctcubeIndexFromRGB(rval, gval, bval, rtab, gtab, btab, &index); /* fprintf(stderr, "rval = %d, gval = %d, bval = %d," " index = %d\n", rval, gval, bval, index); */ if (bpp == 4) SET_DATA_QBIT(lined, j, index); else /* bpp == 8 */ SET_DATA_BYTE(lined, j, index); oqca[index]->n += 1.0; oqca[index]->rcum += rval; oqca[index]->gcum += gval; oqca[index]->bcum += bval; } } /* Compute average color values in each octcube, and * generate colormap */ cmap = pixcmapCreate(bpp); pixSetColormap(pixd, cmap); for (i = 0; i < nbase; i++) { oqc = oqca[i]; if (oqc->n != 0) { oqc->rval = (l_int32)(oqc->rcum / oqc->n); oqc->gval = (l_int32)(oqc->gcum / oqc->n); oqc->bval = (l_int32)(oqc->bcum / oqc->n); } else { getRGBFromOctcube(i, maxlevel - 1, &oqc->rval, &oqc->gval, &oqc->bval); } pixcmapAddColor(cmap, oqc->rval, oqc->gval, oqc->bval); } for (i = 0; i < nbase; i++) LEPT_FREE(oqca[i]); LEPT_FREE(oqca); LEPT_FREE(rtab); LEPT_FREE(gtab); LEPT_FREE(btab); return pixd; } /*------------------------------------------------------------* * General case: we will use colors in octcubes at maxlevel. * * We also remove any colors that are not populated from * * the colormap. * *------------------------------------------------------------*/ /* Prepare the OctcubeQuantCell array */ if ((oqca = (OQCELL **)LEPT_CALLOC(ncubes, sizeof(OQCELL *))) == NULL) { pixDestroy(&pixd); return (PIX *)ERROR_PTR("oqca not made", procName, NULL); } for (i = 0; i < ncubes; i++) { oqca[i] = (OQCELL *)LEPT_CALLOC(1, sizeof(OQCELL)); oqca[i]->n = 0.0; } /* Make the tables to map color to the octindex, * of which there are 'ncubes' at 'maxlevel' */ rtab = gtab = btab = NULL; makeRGBToIndexTables(maxlevel, &rtab, >ab, &btab); /* Estimate the color distribution; we want to find the * most popular nextra colors at 'maxlevel' */ for (i = 0; i < h; i += subsample) { lines = datas + i * wpls; for (j = 0; j < w; j += subsample) { pspixel = lines + j; extractRGBValues(*pspixel, &rval, &gval, &bval); getOctcubeIndexFromRGB(rval, gval, bval, rtab, gtab, btab, &index); oqca[index]->n += 1.0; oqca[index]->octindex = index; oqca[index]->rcum += rval; oqca[index]->gcum += gval; oqca[index]->bcum += bval; } } /* Transfer the OQCELL from the array, and order in a heap */ lh = lheapCreate(512, L_SORT_DECREASING); for (i = 0; i < ncubes; i++) lheapAdd(lh, oqca[i]); LEPT_FREE(oqca); /* don't need this array */ /* Prepare a new OctcubeQuantCell array, with maxcolors cells */ oqca = (OQCELL **)LEPT_CALLOC(maxcolors, sizeof(OQCELL *)); for (i = 0; i < nbase; i++) { /* make nbase cells */ oqca[i] = (OQCELL *)LEPT_CALLOC(1, sizeof(OQCELL)); oqca[i]->n = 0.0; } /* Remove the nextra most populated ones, and put them in the array */ for (i = 0; i < nextra; i++) { oqc = (OQCELL *)lheapRemove(lh); oqc->n = 0.0; /* reinit */ oqc->rcum = 0; oqc->gcum = 0; oqc->bcum = 0; oqca[nbase + i] = oqc; /* store it in the array */ } /* Destroy the heap and its remaining contents */ lheapDestroy(&lh, TRUE); /* Generate a lookup table from octindex at maxlevel * to color table index */ lut1 = (l_int32 *)LEPT_CALLOC(ncubes, sizeof(l_int32)); for (i = 0; i < nextra; i++) lut1[oqca[nbase + i]->octindex] = nbase + i; for (index = 0; index < ncubes; index++) { if (lut1[index] == 0) /* not one of the extras; need to assign */ lut1[index] = index >> 3; /* remove the least significant bits */ /* fprintf(stderr, "lut1[%d] = %d\n", index, lut1[index]); */ } /* Go through the entire image, gathering statistics and * assigning pixels to their quantized value */ datad = pixGetData(pixd); wpld = pixGetWpl(pixd); for (i = 0; i < h; i++) { lines = datas + i * wpls; lined = datad + i * wpld; for (j = 0; j < w; j++) { pspixel = lines + j; extractRGBValues(*pspixel, &rval, &gval, &bval); getOctcubeIndexFromRGB(rval, gval, bval, rtab, gtab, btab, &index); /* fprintf(stderr, "rval = %d, gval = %d, bval = %d, index = %d\n", rval, gval, bval, index); */ val = lut1[index]; switch (bpp) { case 4: SET_DATA_QBIT(lined, j, val); break; case 8: SET_DATA_BYTE(lined, j, val); break; default: LEPT_FREE(oqca); LEPT_FREE(lut1); return (PIX *)ERROR_PTR("bpp not 4 or 8!", procName, NULL); break; } oqca[val]->n += 1.0; oqca[val]->rcum += rval; oqca[val]->gcum += gval; oqca[val]->bcum += bval; } } /* Compute averages, set up a colormap, and make a second * lut that converts from the color values currently in * the image to a minimal set */ lut2 = (l_int32 *)LEPT_CALLOC(ncubes, sizeof(l_int32)); cmap = pixcmapCreate(bpp); pixSetColormap(pixd, cmap); for (i = 0, index = 0; i < maxcolors; i++) { oqc = oqca[i]; lut2[i] = index; if (oqc->n == 0) /* no occupancy; don't bump up index */ continue; oqc->rval = (l_int32)(oqc->rcum / oqc->n); oqc->gval = (l_int32)(oqc->gcum / oqc->n); oqc->bval = (l_int32)(oqc->bcum / oqc->n); pixcmapAddColor(cmap, oqc->rval, oqc->gval, oqc->bval); index++; } /* pixcmapWriteStream(stderr, cmap); */ actualcolors = pixcmapGetCount(cmap); /* fprintf(stderr, "Number of different colors = %d\n", actualcolors); */ /* Last time through the image; use the lookup table to * remap the pixel value to the minimal colormap */ if (actualcolors < maxcolors) { for (i = 0; i < h; i++) { lined = datad + i * wpld; for (j = 0; j < w; j++) { switch (bpp) { case 4: val = GET_DATA_QBIT(lined, j); SET_DATA_QBIT(lined, j, lut2[val]); break; case 8: val = GET_DATA_BYTE(lined, j); SET_DATA_BYTE(lined, j, lut2[val]); break; } } } } if (oqca) { for (i = 0; i < maxcolors; i++) LEPT_FREE(oqca[i]); } LEPT_FREE(oqca); LEPT_FREE(lut1); LEPT_FREE(lut2); LEPT_FREE(rtab); LEPT_FREE(gtab); LEPT_FREE(btab); return pixd; } /*-------------------------------------------------------------------------* * Mixed color/gray quantization with specified number of colors * *-------------------------------------------------------------------------*/ /*! * \brief pixOctcubeQuantMixedWithGray() * * \param[in] pixs 32 bpp rgb * \param[in] depth of output pix * \param[in] graylevels graylevels (must be > 1) * \param[in] delta threshold for deciding if a pix is color or gray * \return pixd quantized to octcube and gray levels or NULL on error * *
* Notes: * (1) Generates a colormapped image, where the colormap table values * have two components: octcube values representing pixels with * color content, and grayscale values for the rest. * (2) The threshold (delta) is the maximum allowable difference of * the max abs value of | r - g |, | r - b | and | g - b |. * (3) The octcube values are the averages of all pixels that are * found in the octcube, and that are far enough from gray to * be considered color. This can roughly be visualized as all * the points in the rgb color cube that are not within a "cylinder" * of diameter approximately 'delta' along the main diagonal. * (4) We want to guarantee full coverage of the rgb color space; thus, * if the output depth is 4, the octlevel is 1 (2 x 2 x 2 = 8 cubes) * and if the output depth is 8, the octlevel is 2 (4 x 4 x 4 * = 64 cubes). * (5) Consequently, we have the following constraint on the number * of allowed gray levels: for 4 bpp, 8; for 8 bpp, 192. **/ PIX * pixOctcubeQuantMixedWithGray(PIX *pixs, l_int32 depth, l_int32 graylevels, l_int32 delta) { l_int32 w, h, wpls, wpld, i, j, size, octlevels; l_int32 rval, gval, bval, del, val, midval; l_int32 *carray, *rarray, *garray, *barray; l_int32 *tabval; l_uint32 octindex; l_uint32 *rtab, *gtab, *btab; l_uint32 *lines, *lined, *datas, *datad; PIX *pixd; PIXCMAP *cmap; PROCNAME("pixOctcubeQuantMixedWithGray"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); if (pixGetDepth(pixs) != 32) return (PIX *)ERROR_PTR("pixs not 32 bpp", procName, NULL); if (graylevels < 2) return (PIX *)ERROR_PTR("invalid graylevels", procName, NULL); if (depth == 4) { octlevels = 1; size = 8; /* 2 ** 3 */ if (graylevels > 8) return (PIX *)ERROR_PTR("max 8 gray levels", procName, NULL); } else if (depth == 8) { octlevels = 2; size = 64; /* 2 ** 6 */ if (graylevels > 192) return (PIX *)ERROR_PTR("max 192 gray levels", procName, NULL); } else { return (PIX *)ERROR_PTR("output depth not 4 or 8 bpp", procName, NULL); } pixd = NULL; /* Make octcube index tables */ rtab = gtab = btab = NULL; makeRGBToIndexTables(octlevels, &rtab, >ab, &btab); /* Make octcube arrays for storing points in each cube */ carray = (l_int32 *)LEPT_CALLOC(size, sizeof(l_int32)); rarray = (l_int32 *)LEPT_CALLOC(size, sizeof(l_int32)); garray = (l_int32 *)LEPT_CALLOC(size, sizeof(l_int32)); barray = (l_int32 *)LEPT_CALLOC(size, sizeof(l_int32)); /* Make lookup table, using computed thresholds */ tabval = makeGrayQuantIndexTable(graylevels); if (!rtab || !gtab || !btab || !carray || !rarray || !garray || !barray || !tabval) { L_ERROR("calloc fail for an array\n", procName); goto array_cleanup; } /* Make colormapped output pixd */ pixGetDimensions(pixs, &w, &h, NULL); if ((pixd = pixCreate(w, h, depth)) == NULL) { L_ERROR("pixd not made\n", procName); goto array_cleanup; } pixCopyResolution(pixd, pixs); pixCopyInputFormat(pixd, pixs); cmap = pixcmapCreate(depth); for (j = 0; j < size; j++) /* reserve octcube colors */ pixcmapAddColor(cmap, 1, 1, 1); /* a color that won't be used */ for (j = 0; j < graylevels; j++) { /* set grayscale colors */ val = (255 * j) / (graylevels - 1); pixcmapAddColor(cmap, val, val, val); } pixSetColormap(pixd, cmap); wpld = pixGetWpl(pixd); datad = pixGetData(pixd); /* Go through src image: assign dest pixels to colormap values * and compute average colors in each occupied octcube */ datas = pixGetData(pixs); wpls = pixGetWpl(pixs); for (i = 0; i < h; i++) { lines = datas + i * wpls; lined = datad + i * wpld; for (j = 0; j < w; j++) { extractRGBValues(lines[j], &rval, &gval, &bval); if (rval > gval) { if (gval > bval) { /* r > g > b */ del = rval - bval; midval = gval; } else if (rval > bval) { /* r > b > g */ del = rval - gval; midval = bval; } else { /* b > r > g */ del = bval - gval; midval = rval; } } else { /* gval >= rval */ if (rval > bval) { /* g > r > b */ del = gval - bval; midval = rval; } else if (gval > bval) { /* g > b > r */ del = gval - rval; midval = bval; } else { /* b > g > r */ del = bval - rval; midval = gval; } } if (del > delta) { /* assign to color */ octindex = rtab[rval] | gtab[gval] | btab[bval]; carray[octindex]++; rarray[octindex] += rval; garray[octindex] += gval; barray[octindex] += bval; if (depth == 4) SET_DATA_QBIT(lined, j, octindex); else /* depth == 8 */ SET_DATA_BYTE(lined, j, octindex); } else { /* assign to grayscale */ val = size + tabval[midval]; if (depth == 4) SET_DATA_QBIT(lined, j, val); else /* depth == 8 */ SET_DATA_BYTE(lined, j, val); } } } /* Average the colors in each bin and reset the colormap */ for (i = 0; i < size; i++) { if (carray[i] > 0) { rarray[i] /= carray[i]; garray[i] /= carray[i]; barray[i] /= carray[i]; pixcmapResetColor(cmap, i, rarray[i], garray[i], barray[i]); } } array_cleanup: LEPT_FREE(carray); LEPT_FREE(rarray); LEPT_FREE(garray); LEPT_FREE(barray); LEPT_FREE(rtab); LEPT_FREE(gtab); LEPT_FREE(btab); LEPT_FREE(tabval); return pixd; } /*-------------------------------------------------------------------------* * Fixed partition octcube quantization with 256 cells * *-------------------------------------------------------------------------*/ /*! * \brief pixFixedOctcubeQuant256() * * \param[in] pixs 32 bpp; 24-bit color * \param[in] ditherflag 1 for dithering; 0 for no dithering * \return pixd 8 bit with colormap, or NULL on error * *
* Notes: * This simple 1-pass color quantization works by breaking the * color space into 256 pieces, with 3 bits quantized for each of * red and green, and 2 bits quantized for blue. We shortchange * blue because the eye is least sensitive to blue. This * division of the color space is into two levels of octrees, * followed by a further division by 4 not 8, where both * blue octrees have been combined in the third level. * * The color map is generated from the 256 color centers by * taking the representative color to be the center of the * cell volume. This gives a maximum error in the red and * green values of 16 levels, and a maximum error in the * blue sample of 32 levels. * * Each pixel in the 24-bit color image is placed in its containing * cell, given by the relevant MSbits of the red, green and blue * samples. An error-diffusion dithering is performed on each * color sample to give the appearance of good average local color. * Dithering is required; without it, the contouring and visible * color errors are very bad. * * I originally implemented this algorithm in two passes, * where the first pass was used to compute the weighted average * of each sample in each pre-allocated region of color space. * The idea was to use these centroids in the dithering algorithm * of the second pass, to reduce the average error that was * being dithered. However, with dithering, there is * virtually no difference, so there is no reason to make the * first pass. Consequently, this 1-pass version just assigns * the pixels to the centers of the pre-allocated cells. * We use dithering to spread the difference between the sample * value and the location of the center of the cell. For speed * and simplicity, we use integer dithering and propagate only * to the right, down, and diagonally down-right, with ratios * 3/8, 3/8 and 1/4, respectively. The results should be nearly * as good, and a bit faster, with propagation only to the right * and down. * * The algorithm is very fast, because there is no search, * only fast generation of the cell index for each pixel. * We use a simple mapping from the three 8 bit rgb samples * to the 8 bit cell index; namely, r7 r6 r5 g7 g6 g5 b7 b6. * This is not in an octcube format, but it doesn't matter. * There are no storage requirements. We could keep a * running average of the center of each sample in each * cluster, rather than using the center of the cell, but * this is just extra work, esp. with dithering. * * This method gives surprisingly good results with dithering. * However, without dithering, the loss of color accuracy is * evident in regions that are very light or that have subtle * blending of colors. **/ PIX * pixFixedOctcubeQuant256(PIX *pixs, l_int32 ditherflag) { l_uint8 index; l_int32 rval, gval, bval; l_int32 w, h, wpls, wpld, i, j, cindex; l_uint32 *rtab, *gtab, *btab; l_int32 *itab; l_uint32 *datas, *datad, *lines, *lined; PIX *pixd; PIXCMAP *cmap; PROCNAME("pixFixedOctcubeQuant256"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); if (pixGetDepth(pixs) != 32) return (PIX *)ERROR_PTR("pixs not 32 bpp", procName, NULL); /* Do not dither if image is very small */ pixGetDimensions(pixs, &w, &h, NULL); if (w < MinDitherSize && h < MinDitherSize && ditherflag == 1) { L_INFO("Small image: dithering turned off\n", procName); ditherflag = 0; } /* Find the centers of the 256 cells, each of which represents * the 3 MSBits of the red and green components, and the * 2 MSBits of the blue component. This gives a mapping * from a "cube index" to the rgb values. Save all 256 * rgb values of these centers in a colormap. * For example, to get the red color of the cell center, * you take the 3 MSBits of to the index and add the * offset to the center of the cell, which is 0x10. */ cmap = pixcmapCreate(8); for (cindex = 0; cindex < 256; cindex++) { rval = (cindex & 0xe0) | 0x10; gval = ((cindex << 3) & 0xe0) | 0x10; bval = ((cindex << 6) & 0xc0) | 0x20; pixcmapAddColor(cmap, rval, gval, bval); } /* Make output 8 bpp palette image */ datas = pixGetData(pixs); wpls = pixGetWpl(pixs); if ((pixd = pixCreate(w, h, 8)) == NULL) { pixcmapDestroy(&cmap); return (PIX *)ERROR_PTR("pixd not made", procName, NULL); } pixSetColormap(pixd, cmap); pixCopyResolution(pixd, pixs); pixCopyInputFormat(pixd, pixs); datad = pixGetData(pixd); wpld = pixGetWpl(pixd); /* Set dest pix values to colortable indices */ if (ditherflag == 0) { /* no dithering */ for (i = 0; i < h; i++) { lines = datas + i * wpls; lined = datad + i * wpld; for (j = 0; j < w; j++) { extractRGBValues(lines[j], &rval, &gval, &bval); index = (rval & 0xe0) | ((gval >> 3) & 0x1c) | (bval >> 6); SET_DATA_BYTE(lined, j, index); } } } else { /* ditherflag == 1 */ /* Set up conversion tables from rgb directly to the colormap * index. However, the dithering function expects these tables * to generate an octcube index (+1), and the table itab[] to * convert to the colormap index. So we make a trivial * itab[], that simply compensates for the -1 in * pixDitherOctindexWithCmap(). No cap is required on * the propagated difference. */ rtab = (l_uint32 *)LEPT_CALLOC(256, sizeof(l_uint32)); gtab = (l_uint32 *)LEPT_CALLOC(256, sizeof(l_uint32)); btab = (l_uint32 *)LEPT_CALLOC(256, sizeof(l_uint32)); itab = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32)); if (!rtab || !gtab || !btab || !itab) { pixDestroy(&pixd); return (PIX *)ERROR_PTR("calloc fail for table", procName, NULL); } for (i = 0; i < 256; i++) { rtab[i] = i & 0xe0; gtab[i] = (i >> 3) & 0x1c; btab[i] = i >> 6; itab[i] = i + 1; } pixDitherOctindexWithCmap(pixs, pixd, rtab, gtab, btab, itab, FIXED_DIF_CAP); LEPT_FREE(rtab); LEPT_FREE(gtab); LEPT_FREE(btab); LEPT_FREE(itab); } return pixd; } /*---------------------------------------------------------------------------* * Nearly exact quantization for images with few colors * *---------------------------------------------------------------------------*/ /*! * \brief pixFewColorsOctcubeQuant1() * * \param[in] pixs 32 bpp rgb * \param[in] level significant bits for each of RGB; valid in [1...6] * \return pixd quantized to octcube or NULL on error * *
* Notes: * (1) Generates a colormapped image, where the colormap table values * are the averages of all pixels that are found in the octcube. * (2) This fails if there are more than 256 colors (i.e., more * than 256 occupied octcubes). * (3) Often level 3 (512 octcubes) will succeed because not more * than half of them are occupied with 1 or more pixels. * (4) The depth of the result, which is either 2, 4 or 8 bpp, * is the minimum required to hold the number of colors that * are found. * (5) This can be useful for quantizing orthographically generated * images such as color maps, where there may be more than 256 colors * because of aliasing or jpeg artifacts on text or lines, but * there are a relatively small number of solid colors. Then, * use with level = 3 can often generate a compact and accurate * representation of the original RGB image. For this purpose, * it is better than pixFewColorsOctcubeQuant2(), because it * uses the average value of pixels in the octcube rather * than the first found pixel. It is also simpler to use, * because it generates the histogram internally. **/ PIX * pixFewColorsOctcubeQuant1(PIX *pixs, l_int32 level) { l_int32 w, h, wpls, wpld, i, j, depth, size, ncolors, index; l_int32 rval, gval, bval; l_int32 *carray, *rarray, *garray, *barray; l_uint32 octindex; l_uint32 *rtab, *gtab, *btab; l_uint32 *lines, *lined, *datas, *datad, *pspixel; PIX *pixd; PIXCMAP *cmap; PROCNAME("pixFewColorsOctcubeQuant1"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); if (pixGetDepth(pixs) != 32) return (PIX *)ERROR_PTR("pixs not 32 bpp", procName, NULL); if (level < 1 || level > 6) return (PIX *)ERROR_PTR("invalid level", procName, NULL); pixd = NULL; if (octcubeGetCount(level, &size)) /* array size = 2 ** (3 * level) */ return (PIX *)ERROR_PTR("size not returned", procName, NULL); rtab = gtab = btab = NULL; makeRGBToIndexTables(level, &rtab, >ab, &btab); carray = (l_int32 *)LEPT_CALLOC(size, sizeof(l_int32)); rarray = (l_int32 *)LEPT_CALLOC(size, sizeof(l_int32)); garray = (l_int32 *)LEPT_CALLOC(size, sizeof(l_int32)); barray = (l_int32 *)LEPT_CALLOC(size, sizeof(l_int32)); if (!carray || !rarray || !garray || !barray) { L_ERROR("calloc fail for an array\n", procName); goto array_cleanup; } /* Place the pixels in octcube leaves. */ pixGetDimensions(pixs, &w, &h, NULL); datas = pixGetData(pixs); wpls = pixGetWpl(pixs); for (i = 0; i < h; i++) { lines = datas + i * wpls; for (j = 0; j < w; j++) { pspixel = lines + j; extractRGBValues(*pspixel, &rval, &gval, &bval); octindex = rtab[rval] | gtab[gval] | btab[bval]; carray[octindex]++; rarray[octindex] += rval; garray[octindex] += gval; barray[octindex] += bval; } } /* Find the number of different colors */ for (i = 0, ncolors = 0; i < size; i++) { if (carray[i] > 0) ncolors++; } if (ncolors > 256) { L_WARNING("%d colors found; more than 256\n", procName, ncolors); goto array_cleanup; } if (ncolors <= 4) depth = 2; else if (ncolors <= 16) depth = 4; else depth = 8; /* Average the colors in each octcube leaf and add to colormap table; * then use carray to hold the colormap index + 1 */ cmap = pixcmapCreate(depth); for (i = 0, index = 0; i < size; i++) { if (carray[i] > 0) { rarray[i] /= carray[i]; garray[i] /= carray[i]; barray[i] /= carray[i]; pixcmapAddColor(cmap, rarray[i], garray[i], barray[i]); carray[i] = index + 1; /* to avoid storing 0 */ index++; } } pixd = pixCreate(w, h, depth); pixSetColormap(pixd, cmap); pixCopyResolution(pixd, pixs); pixCopyInputFormat(pixd, pixs); datad = pixGetData(pixd); wpld = pixGetWpl(pixd); for (i = 0; i < h; i++) { lines = datas + i * wpls; lined = datad + i * wpld; for (j = 0; j < w; j++) { pspixel = lines + j; extractRGBValues(*pspixel, &rval, &gval, &bval); octindex = rtab[rval] | gtab[gval] | btab[bval]; switch (depth) { case 2: SET_DATA_DIBIT(lined, j, carray[octindex] - 1); break; case 4: SET_DATA_QBIT(lined, j, carray[octindex] - 1); break; case 8: SET_DATA_BYTE(lined, j, carray[octindex] - 1); break; default: L_WARNING("shouldn't get here\n", procName); } } } array_cleanup: LEPT_FREE(carray); LEPT_FREE(rarray); LEPT_FREE(garray); LEPT_FREE(barray); LEPT_FREE(rtab); LEPT_FREE(gtab); LEPT_FREE(btab); return pixd; } /*! * \brief pixFewColorsOctcubeQuant2() * * \param[in] pixs 32 bpp rgb * \param[in] level of octcube indexing, for histogram: 3, 4, 5, 6 * \param[in] na histogram of pixel occupation in octree leaves * at given level * \param[in] ncolors number of occupied octree leaves at given level * \param[out] pnerrors [optional] num of pixels not exactly * represented in the colormap * \return pixd 2, 4 or 8 bpp with colormap, or NULL on error * *
* Notes: * (1) Generates a colormapped image, where the colormap table values * are the averages of all pixels that are found in the octcube. * (2) This fails if there are more than 256 colors (i.e., more * than 256 occupied octcubes). * (3) Often level 3 (512 octcubes) will succeed because not more * than half of them are occupied with 1 or more pixels. * (4) For an image with not more than 256 colors, it is unlikely * that two pixels of different color will fall in the same * octcube at level = 4. However it is possible, and this * function optionally returns %nerrors, the number of pixels * where, because more than one color is in the same octcube, * the pixel color is not exactly reproduced in the colormap. * The colormap for an occupied leaf of the octree contains * the color of the first pixel encountered in that octcube. * (5) This differs from pixFewColorsOctcubeQuant1(), which also * requires not more than 256 occupied leaves, but represents * the color of each leaf by an average over the pixels in * that leaf. This also requires precomputing the histogram * of occupied octree leaves, which is generated using * pixOctcubeHistogram(). * (6) This is used in pixConvertRGBToColormap() for images that * are determined, by their histogram, to have relatively few * colors. This typically happens with orthographically * produced images (as oppopsed to natural images), where * it is expected that most of the pixels within a leaf * octcube have exactly the same color, and quantization to * that color is lossless. **/ PIX * pixFewColorsOctcubeQuant2(PIX *pixs, l_int32 level, NUMA *na, l_int32 ncolors, l_int32 *pnerrors) { l_int32 w, h, wpls, wpld, i, j, nerrors; l_int32 ncubes, depth, cindex, oval; l_int32 rval, gval, bval; l_int32 *octarray; l_uint32 octindex; l_uint32 *rtab, *gtab, *btab; l_uint32 *lines, *lined, *datas, *datad, *ppixel; l_uint32 *colorarray; PIX *pixd; PIXCMAP *cmap; PROCNAME("pixFewColorsOctcubeQuant2"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); if (pixGetDepth(pixs) != 32) return (PIX *)ERROR_PTR("pixs not 32 bpp", procName, NULL); if (level < 3 || level > 6) return (PIX *)ERROR_PTR("level not in {4, 5, 6}", procName, NULL); if (ncolors > 256) return (PIX *)ERROR_PTR("ncolors > 256", procName, NULL); if (pnerrors) *pnerrors = UNDEF; pixd = NULL; /* Represent the image with a set of leaf octcubes * at 'level', one for each color. */ rtab = gtab = btab = NULL; makeRGBToIndexTables(level, &rtab, >ab, &btab); /* The octarray will give a ptr from the octcube to the colorarray */ ncubes = numaGetCount(na); octarray = (l_int32 *)LEPT_CALLOC(ncubes, sizeof(l_int32)); /* The colorarray will hold the colors of the first pixel * that lands in the leaf octcube. After filling, it is * used to generate the colormap. */ colorarray = (l_uint32 *)LEPT_CALLOC(ncolors + 1, sizeof(l_uint32)); if (!octarray || !colorarray) { L_ERROR("octarray or colorarray not made\n", procName); goto cleanup_arrays; } /* Determine the output depth from the number of colors */ pixGetDimensions(pixs, &w, &h, NULL); datas = pixGetData(pixs); wpls = pixGetWpl(pixs); if (ncolors <= 4) depth = 2; else if (ncolors <= 16) depth = 4; else /* ncolors <= 256 */ depth = 8; if ((pixd = pixCreate(w, h, depth)) == NULL) { L_ERROR("pixd not made\n", procName); goto cleanup_arrays; } pixCopyResolution(pixd, pixs); pixCopyInputFormat(pixd, pixs); datad = pixGetData(pixd); wpld = pixGetWpl(pixd); /* For each pixel, get the octree index for its leaf octcube. * Check if a pixel has already been found in this octcube. * ~ If not yet found, save that color in the colorarray * and save the cindex in the octarray. * ~ If already found, compare the pixel color with the * color in the colorarray, and note if it differs. * Then set the dest pixel value to the cindex - 1, which * will be the cmap index for this color. */ cindex = 1; /* start with 1 */ nerrors = 0; for (i = 0; i < h; i++) { lines = datas + i * wpls; lined = datad + i * wpld; for (j = 0; j < w; j++) { ppixel = lines + j; extractRGBValues(*ppixel, &rval, &gval, &bval); octindex = rtab[rval] | gtab[gval] | btab[bval]; oval = octarray[octindex]; if (oval == 0) { octarray[octindex] = cindex; colorarray[cindex] = *ppixel; setPixelLow(lined, j, depth, cindex - 1); cindex++; } else { /* already have seen this color; is it unique? */ setPixelLow(lined, j, depth, oval - 1); if (colorarray[oval] != *ppixel) nerrors++; } } } if (pnerrors) *pnerrors = nerrors; #if DEBUG_FEW_COLORS fprintf(stderr, "ncubes = %d, ncolors = %d\n", ncubes, ncolors); for (i = 0; i < ncolors; i++) fprintf(stderr, "color[%d] = %x\n", i, colorarray[i + 1]); #endif /* DEBUG_FEW_COLORS */ /* Make the colormap. */ cmap = pixcmapCreate(depth); for (i = 0; i < ncolors; i++) { ppixel = colorarray + i + 1; extractRGBValues(*ppixel, &rval, &gval, &bval); pixcmapAddColor(cmap, rval, gval, bval); } pixSetColormap(pixd, cmap); cleanup_arrays: LEPT_FREE(octarray); LEPT_FREE(colorarray); LEPT_FREE(rtab); LEPT_FREE(gtab); LEPT_FREE(btab); return pixd; } /*! * \brief pixFewColorsOctcubeQuantMixed() * * \param[in] pixs 32 bpp rgb * \param[in] level significant octcube bits for each of RGB; * valid in [1...6]; use 0 for default * \param[in] darkthresh threshold near black; if the lightest component * is below this, the pixel is not considered to * be gray or color; uses 0 for default * \param[in] lightthresh threshold near white; if the darkest component * is above this, the pixel is not considered to * be gray or color; use 0 for default * \param[in] diffthresh thresh for the max difference between component * values; for differences below this, the pixel * is considered to be gray; use 0 for default * \param[in] minfract min fraction of pixels for gray histo bin; * use 0.0 for default * \param[in] maxspan max size of gray histo bin; use 0 for default * \return pixd 8 bpp, quantized to octcube for pixels that are * not gray; gray pixels are quantized separately * over the full gray range, or NULL on error * *
* Notes: * (1) First runs pixFewColorsOctcubeQuant1(). If this succeeds, * it separates the color from gray(ish) entries in the cmap, * and re-quantizes the gray pixels. The result has some pixels * in color and others in gray. * (2) This fails if there are more than 256 colors (i.e., more * than 256 occupied octcubes in the color quantization). * (3) Level 3 (512 octcubes) will usually succeed because not more * than half of them are occupied with 1 or more pixels. * (4) This uses the criterion from pixColorFraction() for deciding * if a colormap entry is color; namely, if the color components * are not too close to either black or white, and the maximum * difference between component values equals or exceeds a threshold. * (5) For quantizing the gray pixels, it uses a histogram-based * method where input parameters determining the buckets are * the minimum population fraction and the maximum allowed size. * (6) Recommended input parameters are: * %level: 3 or 4 (3 is default) * %darkthresh: 20 * %lightthresh: 244 * %diffthresh: 20 * %minfract: 0.05 * %maxspan: 15 * These numbers are intended to be conservative (somewhat over- * sensitive) in color detection, It's usually better to pay * extra with octcube quantization of a grayscale image than * to use grayscale quantization on an image that has some * actual color. Input 0 on any of these to get the default. * (7) This can be useful for quantizing orthographically generated * images such as color maps, where there may be more than 256 colors * because of aliasing or jpeg artifacts on text or lines, but * there are a relatively small number of solid colors. It usually * gives results that are better than pixOctcubeQuantMixedWithGray(), * both in size and appearance. But it is a bit slower. **/ PIX * pixFewColorsOctcubeQuantMixed(PIX *pixs, l_int32 level, l_int32 darkthresh, l_int32 lightthresh, l_int32 diffthresh, l_float32 minfract, l_int32 maxspan) { l_int32 i, j, w, h, wplc, wplm, wpld, ncolors, index; l_int32 rval, gval, bval, val, minval, maxval; l_int32 *lut; l_uint32 *datac, *datam, *datad, *linec, *linem, *lined; PIX *pixc, *pixm, *pixg, *pixd; PIXCMAP *cmap, *cmapd; PROCNAME("pixFewColorsOctcubeQuantMixed"); if (!pixs || pixGetDepth(pixs) != 32) return (PIX *)ERROR_PTR("pixs undefined or not 32 bpp", procName, NULL); if (level <= 0) level = 3; if (level > 6) return (PIX *)ERROR_PTR("invalid level", procName, NULL); if (darkthresh <= 0) darkthresh = 20; if (lightthresh <= 0) lightthresh = 244; if (diffthresh <= 0) diffthresh = 20; if (minfract <= 0.0) minfract = 0.05; if (maxspan <= 2) maxspan = 15; /* Start with a simple fixed octcube quantizer. */ if ((pixc = pixFewColorsOctcubeQuant1(pixs, level)) == NULL) return (PIX *)ERROR_PTR("too many colors", procName, NULL); /* Identify and save color entries in the colormap. Set up a LUT * that returns -1 for any gray pixel. */ cmap = pixGetColormap(pixc); ncolors = pixcmapGetCount(cmap); cmapd = pixcmapCreate(8); lut = (l_int32 *)LEPT_CALLOC(256, sizeof(l_int32)); for (i = 0; i < 256; i++) lut[i] = -1; for (i = 0, index = 0; i < ncolors; i++) { pixcmapGetColor(cmap, i, &rval, &gval, &bval); minval = L_MIN(rval, gval); minval = L_MIN(minval, bval); if (minval > lightthresh) /* near white */ continue; maxval = L_MAX(rval, gval); maxval = L_MAX(maxval, bval); if (maxval < darkthresh) /* near black */ continue; /* Use the max diff between components to test for color */ if (maxval - minval >= diffthresh) { pixcmapAddColor(cmapd, rval, gval, bval); lut[i] = index; index++; } } /* Generate dest pix with just the color pixels set to their * colormap indices. At the same time, make a 1 bpp mask * of the non-color pixels */ pixGetDimensions(pixs, &w, &h, NULL); pixd = pixCreate(w, h, 8); pixSetColormap(pixd, cmapd); pixm = pixCreate(w, h, 1); datac = pixGetData(pixc); datam = pixGetData(pixm); datad = pixGetData(pixd); wplc = pixGetWpl(pixc); wplm = pixGetWpl(pixm); wpld = pixGetWpl(pixd); for (i = 0; i < h; i++) { linec = datac + i * wplc; linem = datam + i * wplm; lined = datad + i * wpld; for (j = 0; j < w; j++) { val = GET_DATA_BYTE(linec, j); if (lut[val] == -1) SET_DATA_BIT(linem, j); else SET_DATA_BYTE(lined, j, lut[val]); } } /* Fill in the gray values. Use a grayscale version of pixs * as input, along with the mask over the actual gray pixels. */ pixg = pixConvertTo8(pixs, 0); pixGrayQuantFromHisto(pixd, pixg, pixm, minfract, maxspan); LEPT_FREE(lut); pixDestroy(&pixc); pixDestroy(&pixm); pixDestroy(&pixg); return pixd; } /*---------------------------------------------------------------------------* * Fixed partition octcube quantization with RGB output * *---------------------------------------------------------------------------*/ /*! * \brief pixFixedOctcubeQuantGenRGB() * * \param[in] pixs 32 bpp rgb * \param[in] level significant bits for each of r,g,b * \return pixd rgb; quantized to octcube centers, or NULL on error * *
* Notes: * (1) Unlike the other color quantization functions, this one * generates an rgb image. * (2) The pixel values are quantized to the center of each octcube * (at the specified level) containing the pixel. They are * not quantized to the average of the pixels in that octcube. **/ PIX * pixFixedOctcubeQuantGenRGB(PIX *pixs, l_int32 level) { l_int32 w, h, wpls, wpld, i, j; l_int32 rval, gval, bval; l_uint32 octindex; l_uint32 *rtab, *gtab, *btab; l_uint32 *lines, *lined, *datas, *datad; PIX *pixd; PROCNAME("pixFixedOctcubeQuantGenRGB"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); if (pixGetDepth(pixs) != 32) return (PIX *)ERROR_PTR("pixs not 32 bpp", procName, NULL); if (level < 1 || level > 6) return (PIX *)ERROR_PTR("level not in {1,...6}", procName, NULL); if (makeRGBToIndexTables(level, &rtab, >ab, &btab)) return (PIX *)ERROR_PTR("tables not made", procName, NULL); pixGetDimensions(pixs, &w, &h, NULL); pixd = pixCreate(w, h, 32); pixCopyResolution(pixd, pixs); pixCopyInputFormat(pixd, pixs); datad = pixGetData(pixd); wpld = pixGetWpl(pixd); datas = pixGetData(pixs); wpls = pixGetWpl(pixs); for (i = 0; i < h; i++) { lines = datas + i * wpls; lined = datad + i * wpld; for (j = 0; j < w; j++) { extractRGBValues(lines[j], &rval, &gval, &bval); octindex = rtab[rval] | gtab[gval] | btab[bval]; getRGBFromOctcube(octindex, level, &rval, &gval, &bval); composeRGBPixel(rval, gval, bval, lined + j); } } LEPT_FREE(rtab); LEPT_FREE(gtab); LEPT_FREE(btab); return pixd; } /*------------------------------------------------------------------* * Color quantize RGB image using existing colormap * *------------------------------------------------------------------*/ /*! * \brief pixQuantFromCmap() * * \param[in] pixs 8 bpp grayscale without cmap, or 32 bpp rgb * \param[in] cmap to quantize to; insert copy into dest pix * \param[in] mindepth minimum depth of pixd: can be 2, 4 or 8 bpp * \param[in] level of octcube used for finding nearest color in cmap * \param[in] metric L_MANHATTAN_DISTANCE, L_EUCLIDEAN_DISTANCE * \return pixd 2, 4 or 8 bpp, colormapped, or NULL on error * *
* Notes: * (1) This is a top-level wrapper for quantizing either grayscale * or rgb images to a specified colormap. * (2) The actual output depth is constrained by %mindepth and * by the number of colors in %cmap. * (3) For grayscale, %level and %metric are ignored. * (4) If the cmap has color and pixs is grayscale, the color is * removed from the cmap before quantizing pixs. **/ PIX * pixQuantFromCmap(PIX *pixs, PIXCMAP *cmap, l_int32 mindepth, l_int32 level, l_int32 metric) { l_int32 d; PROCNAME("pixQuantFromCmap"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); if (mindepth != 2 && mindepth != 4 && mindepth != 8) return (PIX *)ERROR_PTR("invalid mindepth", procName, NULL); d = pixGetDepth(pixs); if (d == 8) return pixGrayQuantFromCmap(pixs, cmap, mindepth); else if (d == 32) return pixOctcubeQuantFromCmap(pixs, cmap, mindepth, level, metric); else return (PIX *)ERROR_PTR("d not 8 or 32 bpp", procName, NULL); } /*! * \brief pixOctcubeQuantFromCmap() * * \param[in] pixs 32 bpp rgb * \param[in] cmap to quantize to; insert copy into dest pix * \param[in] mindepth minimum depth of pixd: can be 2, 4 or 8 bpp * \param[in] level of octcube used for finding nearest color in cmap * \param[in] metric L_MANHATTAN_DISTANCE, L_EUCLIDEAN_DISTANCE * \return pixd 2, 4 or 8 bpp, colormapped, or NULL on error * *
* Notes: * (1) In typical use, we are doing an operation, such as * interpolative scaling, on a colormapped pix, where it is * necessary to remove the colormap before the operation. * We then want to re-quantize the RGB result using the same * colormap. * (2) The level is used to divide the color space into octcubes. * Each input pixel is, in effect, placed at the center of an * octcube at the given level, and it is mapped into the * exact color (given in the colormap) that is the closest * to that location. We need to know that distance, for each color * in the colormap. The higher the level of the octtree, the smaller * the octcubes in the color space, and hence the more accurately * we can determine the closest color in the colormap; however, * the size of the LUT, which is the total number of octcubes, * increases by a factor of 8 for each increase of 1 level. * The time required to acquire a level 4 mapping table, which has * about 4K entries, is less than 1 msec, so that is the * recommended minimum size to be used. At that size, the * octcubes have their centers 16 units apart in each (r,g,b) * direction. If two colors are in the same octcube, the one * closest to the center will always be chosen. The maximum * error for any component occurs when the correct color is * at a cube corner and there is an incorrect color just inside * the cube next to the opposite corner, giving an error of * 14 units (out of 256) for each component. Using a level 5 * mapping table reduces the maximum error to 6 units. * (3) Typically you should use the Euclidean metric, because the * resulting voronoi cells (which are generated using the actual * colormap values as seeds) are convex for Euclidean distance * but not for Manhattan distance. In terms of the octcubes, * convexity of the voronoi cells means that if the 8 corners * of any cube (of which the octcubes are special cases) * are all within a cell, then every point in the cube will * lie within the cell. * (4) The depth of the output pixd is equal to the maximum of * (a) %mindepth and (b) the minimum (2, 4 or 8 bpp) necessary * to hold the indices in the colormap. * (5) We build a mapping table from octcube to colormap index so * that this function can run in a time (otherwise) independent * of the number of colors in the colormap. This avoids a * brute-force search for the closest colormap color to each * pixel in the image. * (6) This is similar to the function pixAssignToNearestColor() * used for color segmentation. * (7) Except for very small images or when using level > 4, * it takes very little time to generate the tables, * compared to the generation of the colormapped dest pix, * so one would not typically use the low-level version. **/ PIX * pixOctcubeQuantFromCmap(PIX *pixs, PIXCMAP *cmap, l_int32 mindepth, l_int32 level, l_int32 metric) { l_int32 *cmaptab; l_uint32 *rtab, *gtab, *btab; PIX *pixd; PROCNAME("pixOctcubeQuantFromCmap"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); if (pixGetDepth(pixs) != 32) return (PIX *)ERROR_PTR("pixs not 32 bpp", procName, NULL); if (!cmap) return (PIX *)ERROR_PTR("cmap not defined", procName, NULL); if (mindepth != 2 && mindepth != 4 && mindepth != 8) return (PIX *)ERROR_PTR("invalid mindepth", procName, NULL); if (level < 1 || level > 6) return (PIX *)ERROR_PTR("level not in {1...6}", procName, NULL); if (metric != L_MANHATTAN_DISTANCE && metric != L_EUCLIDEAN_DISTANCE) return (PIX *)ERROR_PTR("invalid metric", procName, NULL); /* Set up the tables to map rgb to the nearest colormap index */ rtab = gtab = btab = NULL; makeRGBToIndexTables(level, &rtab, >ab, &btab); cmaptab = pixcmapToOctcubeLUT(cmap, level, metric); pixd = pixOctcubeQuantFromCmapLUT(pixs, cmap, mindepth, cmaptab, rtab, gtab, btab); LEPT_FREE(cmaptab); LEPT_FREE(rtab); LEPT_FREE(gtab); LEPT_FREE(btab); return pixd; } /*! * \brief pixOctcubeQuantFromCmapLUT() * * \param[in] pixs 32 bpp rgb * \param[in] cmap to quantize to; insert copy into dest pix * \param[in] mindepth minimum depth of pixd: can be 2, 4 or 8 bpp * \param[in] cmaptab table mapping from octindex to colormap index * \param[in] rtab, gtab, btab tables mapping from RGB to octindex * \return pixd 2, 4 or 8 bpp, colormapped, or NULL on error * *
* Notes: * (1) See the notes in the higher-level function * pixOctcubeQuantFromCmap(). The octcube level for * the generated octree is specified there, along with * the distance metric for determining the closest * color in the colormap to each octcube. * (2) If the colormap, level and metric information have already * been used to construct the set of mapping tables, * this low-level function can be used directly (i.e., * independently of pixOctcubeQuantFromCmap()) to build * a colormapped pix that uses the specified colormap. **/ static PIX * pixOctcubeQuantFromCmapLUT(PIX *pixs, PIXCMAP *cmap, l_int32 mindepth, l_int32 *cmaptab, l_uint32 *rtab, l_uint32 *gtab, l_uint32 *btab) { l_int32 i, j, w, h, depth, wpls, wpld; l_int32 rval, gval, bval, index; l_uint32 octindex; l_uint32 *lines, *lined, *datas, *datad; PIX *pixd; PIXCMAP *cmapc; PROCNAME("pixOctcubeQuantFromCmapLUT"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); if (pixGetDepth(pixs) != 32) return (PIX *)ERROR_PTR("pixs not 32 bpp", procName, NULL); if (!cmap) return (PIX *)ERROR_PTR("cmap not defined", procName, NULL); if (mindepth != 2 && mindepth != 4 && mindepth != 8) return (PIX *)ERROR_PTR("invalid mindepth", procName, NULL); if (!rtab || !gtab || !btab || !cmaptab) return (PIX *)ERROR_PTR("tables not all defined", procName, NULL); /* Init dest pix (with minimum bpp depending on cmap) */ pixcmapGetMinDepth(cmap, &depth); depth = L_MAX(depth, mindepth); pixGetDimensions(pixs, &w, &h, NULL); if ((pixd = pixCreate(w, h, depth)) == NULL) return (PIX *)ERROR_PTR("pixd not made", procName, NULL); cmapc = pixcmapCopy(cmap); pixSetColormap(pixd, cmapc); pixCopyResolution(pixd, pixs); pixCopyInputFormat(pixd, pixs); /* Insert the colormap index of the color nearest to the input pixel */ datas = pixGetData(pixs); datad = pixGetData(pixd); wpls = pixGetWpl(pixs); wpld = pixGetWpl(pixd); for (i = 0; i < h; i++) { lines = datas + i * wpls; lined = datad + i * wpld; for (j = 0; j < w; j++) { extractRGBValues(lines[j], &rval, &gval, &bval); /* Map from rgb to octcube index */ getOctcubeIndexFromRGB(rval, gval, bval, rtab, gtab, btab, &octindex); /* Map from octcube index to nearest colormap index */ index = cmaptab[octindex]; if (depth == 2) SET_DATA_DIBIT(lined, j, index); else if (depth == 4) SET_DATA_QBIT(lined, j, index); else /* depth == 8 */ SET_DATA_BYTE(lined, j, index); } } return pixd; } /*---------------------------------------------------------------------------* * Generation of octcube histogram * *---------------------------------------------------------------------------*/ /*! * \brief pixOctcubeHistogram() * * \param[in] pixs 32 bpp rgb * \param[in] level significant bits for each of RGB; valid in [1...6] * \param[out] pncolors [optional] number of occupied cubes * \return numa histogram of color pixels, or NULL on error * *
* Notes: * (1) Input NULL for &ncolors to prevent computation and return value. **/ NUMA * pixOctcubeHistogram(PIX *pixs, l_int32 level, l_int32 *pncolors) { l_int32 size, i, j, w, h, wpl, ncolors, val; l_int32 rval, gval, bval; l_uint32 octindex; l_uint32 *rtab, *gtab, *btab; l_uint32 *data, *line; l_float32 *array; NUMA *na; PROCNAME("pixOctcubeHistogram"); if (pncolors) *pncolors = 0; if (!pixs) return (NUMA *)ERROR_PTR("pixs not defined", procName, NULL); if (pixGetDepth(pixs) != 32) return (NUMA *)ERROR_PTR("pixs not 32 bpp", procName, NULL); pixGetDimensions(pixs, &w, &h, NULL); wpl = pixGetWpl(pixs); data = pixGetData(pixs); if (octcubeGetCount(level, &size)) /* array size = 2 ** (3 * level) */ return (NUMA *)ERROR_PTR("size not returned", procName, NULL); rtab = gtab = btab = NULL; makeRGBToIndexTables(level, &rtab, >ab, &btab); if ((na = numaCreate(size)) == NULL) { L_ERROR("na not made\n", procName); goto cleanup_arrays; } numaSetCount(na, size); array = numaGetFArray(na, L_NOCOPY); for (i = 0; i < h; i++) { line = data + i * wpl; for (j = 0; j < w; j++) { extractRGBValues(line[j], &rval, &gval, &bval); octindex = rtab[rval] | gtab[gval] | btab[bval]; #if DEBUG_OCTINDEX if ((level == 1 && octindex > 7) || (level == 2 && octindex > 63) || (level == 3 && octindex > 511) || (level == 4 && octindex > 4097) || (level == 5 && octindex > 32783) || (level == 6 && octindex > 262271)) { fprintf(stderr, "level = %d, octindex = %d, index error!\n", level, octindex); continue; } #endif /* DEBUG_OCTINDEX */ array[octindex] += 1.0; } } if (pncolors) { for (i = 0, ncolors = 0; i < size; i++) { numaGetIValue(na, i, &val); if (val > 0) ncolors++; } *pncolors = ncolors; } cleanup_arrays: LEPT_FREE(rtab); LEPT_FREE(gtab); LEPT_FREE(btab); return na; } /*------------------------------------------------------------------* * Get filled octcube table from colormap * *------------------------------------------------------------------*/ /*! * \brief pixcmapToOctcubeLUT() * * \param[in] cmap * \param[in] level significant bits for each of RGB; valid in [1...6] * \param[in] metric L_MANHATTAN_DISTANCE, L_EUCLIDEAN_DISTANCE * \return tab[2**3 * level] * *
* Notes: * (1) This function is used to quickly find the colormap color * that is closest to any rgb color. It is used to assign * rgb colors to an existing colormap. It can be very expensive * to search through the entire colormap for the closest color * to each pixel. Instead, we first set up this table, which is * populated by the colormap index nearest to each octcube * color. Then we go through the image; for each pixel, * do two table lookups: first to generate the octcube index * from rgb and second to use this table to read out the * colormap index. * (2) Do a slight modification for white and black. For level = 4, * each octcube size is 16. The center of the whitest octcube * is at (248, 248, 248), which is closer to 242 than 255. * Consequently, any gray color between 242 and 254 will * be selected, even if white (255, 255, 255) exists. This is * typically not optimal, because the original color was * likely white. Therefore, if white exists in the colormap, * use it for any rgb color that falls into the most white octcube. * Do the similar thing for black. * (3) Here are the actual function calls for quantizing to a * specified colormap: * ~ first make the tables that map from rgb --> octcube index * makeRGBToIndexTables() * ~ then for each pixel: * * use the tables to get the octcube index * getOctcubeIndexFromRGB() * * use this table to get the nearest color in the colormap * cmap_index = tab[index] * (4) Distance can be either manhattan or euclidean. * (5) In typical use, level = 4 gives reasonable results, and * level = 5 is slightly better. When this function is used * for color segmentation, there are typically a small number * of colors and the number of levels can be small (e.g., level = 3). **/ l_int32 * pixcmapToOctcubeLUT(PIXCMAP *cmap, l_int32 level, l_int32 metric) { l_int32 i, k, size, ncolors, mindist, dist, mincolor, index; l_int32 rval, gval, bval; /* color at center of the octcube */ l_int32 *rmap, *gmap, *bmap, *tab; PROCNAME("pixcmapToOctcubeLUT"); if (!cmap) return (l_int32 *)ERROR_PTR("cmap not defined", procName, NULL); if (level < 1 || level > 6) return (l_int32 *)ERROR_PTR("level not in {1...6}", procName, NULL); if (metric != L_MANHATTAN_DISTANCE && metric != L_EUCLIDEAN_DISTANCE) return (l_int32 *)ERROR_PTR("invalid metric", procName, NULL); if (octcubeGetCount(level, &size)) /* array size = 2 ** (3 * level) */ return (l_int32 *)ERROR_PTR("size not returned", procName, NULL); if ((tab = (l_int32 *)LEPT_CALLOC(size, sizeof(l_int32))) == NULL) return (l_int32 *)ERROR_PTR("tab not allocated", procName, NULL); ncolors = pixcmapGetCount(cmap); pixcmapToArrays(cmap, &rmap, &gmap, &bmap, NULL); /* Assign based on the closest octcube center to the cmap color */ for (i = 0; i < size; i++) { getRGBFromOctcube(i, level, &rval, &gval, &bval); mindist = 1000000; mincolor = 0; /* irrelevant init */ for (k = 0; k < ncolors; k++) { if (metric == L_MANHATTAN_DISTANCE) { dist = L_ABS(rval - rmap[k]) + L_ABS(gval - gmap[k]) + L_ABS(bval - bmap[k]); } else { /* L_EUCLIDEAN_DISTANCE */ dist = (rval - rmap[k]) * (rval - rmap[k]) + (gval - gmap[k]) * (gval - gmap[k]) + (bval - bmap[k]) * (bval - bmap[k]); } if (dist < mindist) { mindist = dist; mincolor = k; } } tab[i] = mincolor; } /* Reset black and white if available in the colormap. * The darkest octcube is at octindex 0. * The lightest octcube is at the max octindex. */ pixcmapGetNearestIndex(cmap, 0, 0, 0, &index); pixcmapGetColor(cmap, index, &rval, &gval, &bval); if (rval < 7 && gval < 7 && bval < 7) { tab[0] = index; } pixcmapGetNearestIndex(cmap, 255, 255, 255, &index); pixcmapGetColor(cmap, index, &rval, &gval, &bval); if (rval > 248 && gval > 248 && bval > 248) { tab[(1 << (3 * level)) - 1] = index; } LEPT_FREE(rmap); LEPT_FREE(gmap); LEPT_FREE(bmap); return tab; } /*------------------------------------------------------------------* * Strip out unused elements in colormap * *------------------------------------------------------------------*/ /*! * \brief pixRemoveUnusedColors() * * \param[in] pixs colormapped * \return 0 if OK, 1 on error * *
* Notes: * (1) This is an in-place operation. * (2) If the image doesn't have a colormap, returns without error. * (3) Unusued colors are removed from the colormap, and the * image pixels are re-numbered. **/ l_ok pixRemoveUnusedColors(PIX *pixs) { l_int32 i, j, w, h, d, nc, wpls, val, newval, index, zerofound; l_int32 rval, gval, bval; l_uint32 *datas, *lines; l_int32 *histo, *map1, *map2; PIXCMAP *cmap, *cmapd; PROCNAME("pixRemoveUnusedColors"); if (!pixs) return ERROR_INT("pixs not defined", procName, 1); if ((cmap = pixGetColormap(pixs)) == NULL) return 0; d = pixGetDepth(pixs); if (d != 2 && d != 4 && d != 8) return ERROR_INT("d not in {2, 4, 8}", procName, 1); /* Find which indices are actually used */ nc = pixcmapGetCount(cmap); if ((histo = (l_int32 *)LEPT_CALLOC(nc, sizeof(l_int32))) == NULL) return ERROR_INT("histo not made", procName, 1); pixGetDimensions(pixs, &w, &h, NULL); wpls = pixGetWpl(pixs); datas = pixGetData(pixs); for (i = 0; i < h; i++) { lines = datas + i * wpls; for (j = 0; j < w; j++) { switch (d) { case 2: val = GET_DATA_DIBIT(lines, j); break; case 4: val = GET_DATA_QBIT(lines, j); break; case 8: val = GET_DATA_BYTE(lines, j); break; default: LEPT_FREE(histo); return ERROR_INT("switch ran off end!", procName, 1); } if (val >= nc) { L_WARNING("cmap index out of bounds!\n", procName); continue; } histo[val]++; } } /* Check if there are any zeroes. If none, quit. */ zerofound = FALSE; for (i = 0; i < nc; i++) { if (histo[i] == 0) { zerofound = TRUE; break; } } if (!zerofound) { LEPT_FREE(histo); return 0; } /* Generate mapping tables between indices */ map1 = (l_int32 *)LEPT_CALLOC(nc, sizeof(l_int32)); map2 = (l_int32 *)LEPT_CALLOC(nc, sizeof(l_int32)); index = 0; for (i = 0; i < nc; i++) { if (histo[i] != 0) { map1[index] = i; /* get old index from new */ map2[i] = index; /* get new index from old */ index++; } } /* Generate new colormap and attach to pixs */ cmapd = pixcmapCreate(d); for (i = 0; i < index; i++) { pixcmapGetColor(cmap, map1[i], &rval, &gval, &bval); pixcmapAddColor(cmapd, rval, gval, bval); } pixSetColormap(pixs, cmapd); /* Map pixel (index) values to new cmap */ for (i = 0; i < h; i++) { lines = datas + i * wpls; for (j = 0; j < w; j++) { switch (d) { case 2: val = GET_DATA_DIBIT(lines, j); newval = map2[val]; SET_DATA_DIBIT(lines, j, newval); break; case 4: val = GET_DATA_QBIT(lines, j); newval = map2[val]; SET_DATA_QBIT(lines, j, newval); break; case 8: val = GET_DATA_BYTE(lines, j); newval = map2[val]; SET_DATA_BYTE(lines, j, newval); break; default: LEPT_FREE(histo); LEPT_FREE(map1); LEPT_FREE(map2); return ERROR_INT("switch ran off end!", procName, 1); } } } LEPT_FREE(histo); LEPT_FREE(map1); LEPT_FREE(map2); return 0; } /*------------------------------------------------------------------* * Find number of occupied octcubes at the specified level * *------------------------------------------------------------------*/ /*! * \brief pixNumberOccupiedOctcubes() * * \param[in] pix 32 bpp * \param[in] level of octcube * \param[in] mincount minimum num pixels in an octcube to be counted; * -1 to not use * \param[in] minfract minimum fract of pixels in an octcube to be * counted; -1 to not use * \param[out] pncolors number of occupied octcubes * \return 0 if OK, 1 on error * *
* Notes: * (1) Exactly one of (%mincount, %minfract) must be -1, so, e.g., * if %mincount == -1, then we use %minfract. * (2) If all occupied octcubes are to count, set %mincount == 1. * Setting %minfract == 0.0 is taken to mean the same thing. **/ l_ok pixNumberOccupiedOctcubes(PIX *pix, l_int32 level, l_int32 mincount, l_float32 minfract, l_int32 *pncolors) { l_int32 i, j, w, h, d, wpl, ncolors, size, octindex; l_int32 rval, gval, bval; l_int32 *carray; l_uint32 *data, *line, *rtab, *gtab, *btab; PROCNAME("pixNumberOccupiedOctcubes"); if (!pncolors) return ERROR_INT("&ncolors not defined", procName, 1); *pncolors = 0; if (!pix) return ERROR_INT("pix not defined", procName, 1); pixGetDimensions(pix, &w, &h, &d); if (d != 32) return ERROR_INT("pix not 32 bpp", procName, 1); if (level < 1 || level > 6) return ERROR_INT("invalid level", procName, 1); if ((mincount < 0 && minfract < 0) || (mincount >= 0.0 && minfract >= 0.0)) return ERROR_INT("invalid mincount/minfract", procName, 1); if (mincount == 0 || minfract == 0.0) mincount = 1; else if (minfract > 0.0) mincount = L_MIN(1, (l_int32)(minfract * w * h)); if (octcubeGetCount(level, &size)) /* array size = 2 ** (3 * level) */ return ERROR_INT("size not returned", procName, 1); rtab = gtab = btab = NULL; makeRGBToIndexTables(level, &rtab, >ab, &btab); if ((carray = (l_int32 *)LEPT_CALLOC(size, sizeof(l_int32))) == NULL) { L_ERROR("carray not made\n", procName); goto cleanup_arrays; } /* Mark the occupied octcube leaves */ data = pixGetData(pix); wpl = pixGetWpl(pix); for (i = 0; i < h; i++) { line = data + i * wpl; for (j = 0; j < w; j++) { extractRGBValues(line[j], &rval, &gval, &bval); octindex = rtab[rval] | gtab[gval] | btab[bval]; carray[octindex]++; } } /* Count them */ for (i = 0, ncolors = 0; i < size; i++) { if (carray[i] >= mincount) ncolors++; } *pncolors = ncolors; cleanup_arrays: LEPT_FREE(carray); LEPT_FREE(rtab); LEPT_FREE(gtab); LEPT_FREE(btab); return 0; }