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, &gtab, &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;
}


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, &gtab, &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;
}

Fixed issue 22140 in oss-fuzz: Heap-buffer-overflow
* color quantized pix must be 8 bpp before extra colors are added.


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;
}


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;
}


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       *pix1, *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 ((pix1 = pixFewColorsOctcubeQuant1(pixs, level)) == NULL)
        return (PIX *)ERROR_PTR("too many colors", procName, NULL);
    pixc = pixConvertTo8(pix1, 1);  /* must be 8 bpp */
    pixDestroy(&pix1);

        /* 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;
}


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;
}


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;
}


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, &gtab, &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;
}


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;
}


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;
}


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);
}


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;
}


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;
}


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, &gtab, &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;
}


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, &gtab, &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
    lept_stderr("ncubes = %d, ncolors = %d\n", ncubes, ncolors);
    for (i = 0; i < ncolors; i++)
        lept_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;
}


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, &gtab, &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;
}


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;
}


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, &gtab, &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)) {
                lept_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;
}


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;
}


rfbScreenInfoPtr rfbScalingFind(rfbClientPtr cl, int width, int height)
{
    rfbScreenInfoPtr ptr;
    /* include the original in the search (ie: fine 1:1 scaled version of the frameBuffer) */
    for (ptr=cl->screen; ptr!=NULL; ptr=ptr->scaledScreenNext)
    {
        if ((ptr->width==width) && (ptr->height==height))
            return ptr;
    }
    return NULL;
}

libvncserver: scale: cast to 64 bit before shifting

Since pixel_value is 64 bit the data type of the shift operand should
be 64 bit too to prevent integer overflows.

#define CEIL(x)  ( (double) ((int) (x)) == (x) ? \
        (double) ((int) (x)) : (double) ((int) (x) + 1) )

#define FLOOR(x) ( (double) ((int) (x)) )


void rfbScaledScreenUpdate(rfbScreenInfoPtr screen, int x1, int y1, int x2, int y2)
{
    /* ok, now the task is to update each and every scaled version of the framebuffer
     * and we only have to do this for this specific changed rectangle!
     */
    rfbScreenInfoPtr ptr;
    int count=0;

    /* We don't point to cl->screen as it is the original */
    for (ptr=screen->scaledScreenNext;ptr!=NULL;ptr=ptr->scaledScreenNext)
    {
        /* Only update if it has active clients... */
        if (ptr->scaledScreenRefCount>0)
        {
          rfbScaledScreenUpdateRect(screen, ptr, x1, y1, x2-x1, y2-y1);
          count++;
        }
    }
}


int ScaleY(rfbScreenInfoPtr from, rfbScreenInfoPtr to, int y)
{
    if ((from==to) || (from==NULL) || (to==NULL)) return y;
    return ((int)(((double) y / (double)from->height) * (double)to->height ));
}


rfbScreenInfoPtr rfbScaledScreenAllocate(rfbClientPtr cl, int width, int height)
{
    rfbScreenInfoPtr ptr;
    ptr = malloc(sizeof(rfbScreenInfo));
    if (ptr!=NULL)
    {
        int allocSize;

        /* copy *everything* (we don't use most of it, but just in case) */
        memcpy(ptr, cl->screen, sizeof(rfbScreenInfo));

        /* SECURITY: make sure that no integer overflow will occur afterwards.
         * Note: this is defensive coding, as the check should have already been
         * performed during initial, non-scaled screen setup.
         */
        allocSize = pad4(width * (ptr->bitsPerPixel/8)); /* per protocol, width<2**16 and bpp<256 */
        if (height == 0 || allocSize >= SIZE_MAX / height)
        {
          free(ptr);
          return NULL; /* malloc() will allocate an incorrect buffer size - early abort */
        }

        /* Resume copy everything */
        ptr->width = width;
        ptr->height = height;
        ptr->paddedWidthInBytes = (ptr->bitsPerPixel/8)*ptr->width;

        /* Need to by multiples of 4 for Sparc systems */
        ptr->paddedWidthInBytes = pad4(ptr->paddedWidthInBytes);

        /* Reset the reference count to 0! */
        ptr->scaledScreenRefCount = 0;

        ptr->sizeInBytes = ptr->paddedWidthInBytes * ptr->height;
        ptr->serverFormat = cl->screen->serverFormat;

        ptr->frameBuffer = malloc(ptr->sizeInBytes);
        if (ptr->frameBuffer!=NULL)
        {
            /* Reset to a known condition: scale the entire framebuffer */
            rfbScaledScreenUpdateRect(cl->screen, ptr, 0, 0, cl->screen->width, cl->screen->height);
            /* Now, insert into the chain */
            LOCK(cl->updateMutex);
            ptr->scaledScreenNext = cl->screen->scaledScreenNext;
            cl->screen->scaledScreenNext = ptr;
            UNLOCK(cl->updateMutex);
        }
        else
        {
            /* Failed to malloc the new frameBuffer, cleanup */
            free(ptr);
            ptr=NULL;
        }
    }
    return ptr;
}


static InlineX int pad4(int value)
{
    int remainder = value & 3;
    if (!remainder) return value;
    return value + 4 - remainder;
}

#define CEIL(x)  ( (double) ((int) (x)) == (x) ? \
        (double) ((int) (x)) : (double) ((int) (x) + 1) )

#define FLOOR(x) ( (double) ((int) (x)) )

#define InlineX __inline

#  define InlineX inline

#  define InlineX


void rfbScalingSetup(rfbClientPtr cl, int width, int height)
{
    rfbScreenInfoPtr ptr;

    ptr = rfbScalingFind(cl,width,height);
    if (ptr==NULL)
        ptr = rfbScaledScreenAllocate(cl,width,height);
    /* Now, there is a new screen available (if ptr is not NULL) */
    if (ptr!=NULL)
    {
        /* Update it! */
        if (ptr->scaledScreenRefCount<1)
            rfbScaledScreenUpdateRect(cl->screen, ptr, 0, 0, cl->screen->width, cl->screen->height);
        /*
         * rfbLog("Taking one from %dx%d-%d and adding it to %dx%d-%d\n",
         *    cl->scaledScreen->width, cl->scaledScreen->height,
         *    cl->scaledScreen->scaledScreenRefCount,
         *    ptr->width, ptr->height, ptr->scaledScreenRefCount);
         */

        LOCK(cl->updateMutex);
        cl->scaledScreen->scaledScreenRefCount--;
        ptr->scaledScreenRefCount++;
        cl->scaledScreen=ptr;
        cl->newFBSizePending = TRUE;
        UNLOCK(cl->updateMutex);

        rfbLog("Scaling to %dx%d (refcount=%d)\n",width,height,ptr->scaledScreenRefCount);
    }
    else
        rfbLog("Scaling to %dx%d failed, leaving things alone\n",width,height);
}


void rfbScaledScreenUpdateRect(rfbScreenInfoPtr screen, rfbScreenInfoPtr ptr, int x0, int y0, int w0, int h0)
{
    int x,y,w,v,z;
    int x1, y1, w1, h1;
    int bitsPerPixel, bytesPerPixel, bytesPerLine, areaX, areaY, area2;
    unsigned char *srcptr, *dstptr;

    /* Nothing to do!!! */
    if (screen==ptr) return;

    x1 = x0;
    y1 = y0;
    w1 = w0;
    h1 = h0;

    rfbScaledCorrection(screen, ptr, &x1, &y1, &w1, &h1, "rfbScaledScreenUpdateRect");
    x0 = ScaleX(ptr, screen, x1);
    y0 = ScaleY(ptr, screen, y1);
    w0 = ScaleX(ptr, screen, w1);
    h0 = ScaleY(ptr, screen, h1);

    bitsPerPixel = screen->bitsPerPixel;
    bytesPerPixel = bitsPerPixel / 8;
    bytesPerLine = w1 * bytesPerPixel;
    srcptr = (unsigned char *)(screen->frameBuffer +
     (y0 * screen->paddedWidthInBytes + x0 * bytesPerPixel));
    dstptr = (unsigned char *)(ptr->frameBuffer +
     ( y1 * ptr->paddedWidthInBytes + x1 * bytesPerPixel));
    /* The area of the source framebuffer for each destination pixel */
    areaX = ScaleX(ptr,screen,1);
    areaY = ScaleY(ptr,screen,1);
    area2 = areaX*areaY;

    /* Ensure that we do not go out of bounds */
    if ((x1+w1) > (ptr->width))
    {
      if (x1==0) w1=ptr->width; else x1 = ptr->width - w1;
    }
    if ((y1+h1) > (ptr->height))
    {
      if (y1==0) h1=ptr->height; else y1 = ptr->height - h1;
    }
    /*
     * rfbLog("rfbScaledScreenUpdateRect(%dXx%dY-%dWx%dH  ->  %dXx%dY-%dWx%dH <%dx%d>) {%dWx%dH -> %dWx%dH} 0x%p\n",
     *    x0, y0, w0, h0, x1, y1, w1, h1, areaX, areaY,
     *    screen->width, screen->height, ptr->width, ptr->height, ptr->frameBuffer);
     */

    if (screen->serverFormat.trueColour) { /* Blend neighbouring pixels together */
      unsigned char *srcptr2;
      unsigned long pixel_value, red, green, blue;
      unsigned int redShift = screen->serverFormat.redShift;
      unsigned int greenShift = screen->serverFormat.greenShift;
      unsigned int blueShift = screen->serverFormat.blueShift;
      unsigned long redMax = screen->serverFormat.redMax;
      unsigned long greenMax = screen->serverFormat.greenMax;
      unsigned long blueMax = screen->serverFormat.blueMax;

     /* for each *destination* pixel... */
     for (y = 0; y < h1; y++) {
       for (x = 0; x < w1; x++) {
         red = green = blue = 0;
         /* Get the totals for rgb from the source grid... */
         for (w = 0; w < areaX; w++) {
           for (v = 0; v < areaY; v++) {
             srcptr2 = &srcptr[(((x * areaX) + w) * bytesPerPixel) +
                               (v * screen->paddedWidthInBytes)];
             pixel_value = 0;

             switch (bytesPerPixel) {
             case 4: pixel_value = *((unsigned int *)srcptr2);   break;
             case 2: pixel_value = *((unsigned short *)srcptr2); break;
             case 1: pixel_value = *((unsigned char *)srcptr2);  break;
             default:
               /* fixme: endianness problem? */
               for (z = 0; z < bytesPerPixel; z++)
                 pixel_value += ((unsigned long)srcptr2[z] << (8 * z));
                break;
              }
              /*
              srcptr2 += bytesPerPixel;
              */

            red += ((pixel_value >> redShift) & redMax);
            green += ((pixel_value >> greenShift) & greenMax);
            blue += ((pixel_value >> blueShift) & blueMax);

           }
         }
         /* We now have a total for all of the colors, find the average! */
         red /= area2;
         green /= area2;
         blue /= area2;
          /* Stuff the new value back into memory */
         pixel_value = ((red & redMax) << redShift) | ((green & greenMax) << greenShift) | ((blue & blueMax) << blueShift);

         switch (bytesPerPixel) {
         case 4: *((unsigned int *)dstptr)   = (unsigned int)   pixel_value; break;
         case 2: *((unsigned short *)dstptr) = (unsigned short) pixel_value; break;
         case 1: *((unsigned char *)dstptr)  = (unsigned char)  pixel_value; break;
         default:
           /* fixme: endianness problem? */
           for (z = 0; z < bytesPerPixel; z++)
             dstptr[z]=(pixel_value >> (8 * z)) & 0xff;
            break;
          }
          dstptr += bytesPerPixel;
       }
       srcptr += (screen->paddedWidthInBytes * areaY);
       dstptr += (ptr->paddedWidthInBytes - bytesPerLine);
     }
   } else
   { /* Not truecolour, so we can't blend. Just use the top-left pixel instead */
     for (y = y1; y < (y1+h1); y++) {
       for (x = x1; x < (x1+w1); x++)
         memcpy (&ptr->frameBuffer[(y *ptr->paddedWidthInBytes) + (x * bytesPerPixel)],
                 &screen->frameBuffer[(y * areaY * screen->paddedWidthInBytes) + (x *areaX * bytesPerPixel)], bytesPerPixel);
     }
  }
}


int ScaleX(rfbScreenInfoPtr from, rfbScreenInfoPtr to, int x)
{
    if ((from==to) || (from==NULL) || (to==NULL)) return x;
    return ((int)(((double) x / (double)from->width) * (double)to->width ));
}


int rfbSendNewScaleSize(rfbClientPtr cl)
{
    /* if the client supports newFBsize Encoding, use it */
    if (cl->useNewFBSize && cl->newFBSizePending)
	return FALSE;

    LOCK(cl->updateMutex);
    cl->newFBSizePending = FALSE;
    UNLOCK(cl->updateMutex);

    if (cl->PalmVNC==TRUE)
    {
        rfbPalmVNCReSizeFrameBufferMsg pmsg;
        pmsg.type = rfbPalmVNCReSizeFrameBuffer;
        pmsg.pad1 = 0;
        pmsg.desktop_w = Swap16IfLE(cl->screen->width);
        pmsg.desktop_h = Swap16IfLE(cl->screen->height);
        pmsg.buffer_w  = Swap16IfLE(cl->scaledScreen->width);
        pmsg.buffer_h  = Swap16IfLE(cl->scaledScreen->height);
        pmsg.pad2 = 0;

        rfbLog("Sending a response to a PalmVNC style frameuffer resize event (%dx%d)\n", cl->scaledScreen->width, cl->scaledScreen->height);
        if (rfbWriteExact(cl, (char *)&pmsg, sz_rfbPalmVNCReSizeFrameBufferMsg) < 0) {
            rfbLogPerror("rfbNewClient: write");
            rfbCloseClient(cl);
            return FALSE;
        }
    }
    else
    {
        rfbResizeFrameBufferMsg        rmsg;
        rmsg.type = rfbResizeFrameBuffer;
        rmsg.pad1=0;
        rmsg.framebufferWidth  = Swap16IfLE(cl->scaledScreen->width);
        rmsg.framebufferHeigth = Swap16IfLE(cl->scaledScreen->height);
        rfbLog("Sending a response to a UltraVNC style frameuffer resize event (%dx%d)\n", cl->scaledScreen->width, cl->scaledScreen->height);
        if (rfbWriteExact(cl, (char *)&rmsg, sz_rfbResizeFrameBufferMsg) < 0) {
            rfbLogPerror("rfbNewClient: write");
            rfbCloseClient(cl);
            return FALSE;
        }
    }
    return TRUE;
}


void rfbScaledCorrection(rfbScreenInfoPtr from, rfbScreenInfoPtr to, int *x, int *y, int *w, int *h, const char *function)
{
    double x1,y1,w1,h1, x2, y2, w2, h2;
    double scaleW = ((double) to->width) / ((double) from->width);
    double scaleH = ((double) to->height) / ((double) from->height);

    /*
     * rfbLog("rfbScaledCorrection(%p -> %p, %dx%d->%dx%d (%dXx%dY-%dWx%dH)\n",
     * from, to, from->width, from->height, to->width, to->height, *x, *y, *w, *h);
     */

    /* If it's the original framebuffer... */
    if (from==to) return;

    x1 = ((double) *x) * scaleW;
    y1 = ((double) *y) * scaleH;
    w1 = ((double) *w) * scaleW;
    h1 = ((double) *h) * scaleH;

    /*cast from double to int is same as "*x = floor(x1);" */
    x2 = FLOOR(x1);
    y2 = FLOOR(y1);

    /* include into W and H the jitter of scaling X and Y */
    w2 = CEIL(w1 + ( x1 - x2 ));
    h2 = CEIL(h1 + ( y1 - y2 ));

    /*
     * rfbLog("%s (%dXx%dY-%dWx%dH  ->  %fXx%fY-%fWx%fH) {%dWx%dH -> %dWx%dH}\n",
     *    function, *x, *y, *w, *h, x2, y2, w2, h2,
     *    from->width, from->height, to->width, to->height);
     */

    /* simulate ceil() without math library */
    *x = (int)x2;
    *y = (int)y2;
    *w = (int)w2;
    *h = (int)h2;

    /* Small changes for a thumbnail may be scaled to zero */
    if (*w==0) (*w)++;
    if (*h==0) (*h)++;
    /* scaling from small to big may overstep the size a bit */
    if (*x+*w > to->width)  *w=to->width - *x;
    if (*y+*h > to->height) *h=to->height - *y;
}


    yaffs2_open(TSK_IMG_INFO * img_info, TSK_OFF_T offset,
    TSK_FS_TYPE_ENUM ftype, uint8_t test)
{
    YAFFSFS_INFO *yaffsfs = NULL;
    TSK_FS_INFO *fs = NULL;
    const unsigned int psize = img_info->page_size;
    const unsigned int ssize = img_info->spare_size;
    YaffsHeader * first_header = NULL;
    TSK_FS_DIR *test_dir;
    std::map<std::string, std::string> configParams;
    YAFFS_CONFIG_STATUS config_file_status;

    // clean up any error messages that are lying around
    tsk_error_reset();

    if (TSK_FS_TYPE_ISYAFFS2(ftype) == 0) {
        tsk_error_reset();
        tsk_error_set_errno(TSK_ERR_FS_ARG);
        tsk_error_set_errstr("Invalid FS Type in yaffsfs_open");
        return NULL;
    }

    if (img_info->sector_size == 0) {
        tsk_error_reset();
        tsk_error_set_errno(TSK_ERR_FS_ARG);
        tsk_error_set_errstr("yaffs2_open: sector size is 0");
        return NULL;
    }

    if ((yaffsfs = (YAFFSFS_INFO *) tsk_fs_malloc(sizeof(YAFFSFS_INFO))) == NULL)
        return NULL;
    yaffsfs->cache_objects = NULL;
    yaffsfs->chunkMap = NULL;

    fs = &(yaffsfs->fs_info);

    fs->tag = TSK_FS_INFO_TAG;
    fs->ftype = ftype;
    fs->flags = (TSK_FS_INFO_FLAG_ENUM)0;
    fs->img_info = img_info;
    fs->offset = offset;
    fs->endian = TSK_LIT_ENDIAN;

    // Read config file (if it exists)
    config_file_status = yaffs_load_config_file(img_info, configParams);
    // BL-6929(JTS): When using external readers, this call will fail.
    // Not having a config should not be a fatal error.
  /*if(config_file_status == YAFFS_CONFIG_ERROR){
        // tsk_error was set by yaffs_load_config
        goto on_error;
    }
    else*/ if(config_file_status == YAFFS_CONFIG_OK){
        // Validate the input
        // If it fails validation, return (tsk_error will be set up already)
        if(1 == yaffs_validate_config_file(configParams)){
            goto on_error;
        }
    }

    // If we read these fields from the config file, use those values. Otherwise use the defaults
    if(configParams.find(YAFFS_CONFIG_PAGE_SIZE_STR) != configParams.end()){
        yaffsfs->page_size = atoi(configParams[YAFFS_CONFIG_PAGE_SIZE_STR].c_str());
    }
    else{
        yaffsfs->page_size = psize == 0 ? YAFFS_DEFAULT_PAGE_SIZE : psize;
    }

    if(configParams.find(YAFFS_CONFIG_SPARE_SIZE_STR) != configParams.end()){
        yaffsfs->spare_size = atoi(configParams[YAFFS_CONFIG_SPARE_SIZE_STR].c_str());
    }
    else{
        yaffsfs->spare_size = ssize == 0 ? YAFFS_DEFAULT_SPARE_SIZE : ssize;
    }

    if(configParams.find(YAFFS_CONFIG_CHUNKS_PER_BLOCK_STR) != configParams.end()){
        yaffsfs->chunks_per_block = atoi(configParams[YAFFS_CONFIG_CHUNKS_PER_BLOCK_STR].c_str());
    }
    else{
        yaffsfs->chunks_per_block = 64;
    }

    // TODO: Why are 2 different memory allocation methods used in the same code?
    // This makes things unnecessary complex.
    yaffsfs->max_obj_id = 1;
    yaffsfs->max_version = 0;

    // Keep track of whether we're doing auto-detection of the file system
    if(test){
        yaffsfs->autoDetect = 1;
    }
    else{
        yaffsfs->autoDetect = 0;
    }

    // Determine the layout of the spare area
    // If it was specified in the config file, use those values. Otherwise do the auto-detection
    if(configParams.find(YAFFS_CONFIG_SEQ_NUM_STR) != configParams.end()){
        // In the validation step, we ensured that if one of the offsets was set, we have all of them
        yaffsfs->spare_seq_offset = atoi(configParams[YAFFS_CONFIG_SEQ_NUM_STR].c_str());
        yaffsfs->spare_obj_id_offset = atoi(configParams[YAFFS_CONFIG_OBJ_ID_STR].c_str());
        yaffsfs->spare_chunk_id_offset = atoi(configParams[YAFFS_CONFIG_CHUNK_ID_STR].c_str());

        // Check that the offsets are valid for the given spare area size (fields are 4 bytes long)
        if((yaffsfs->spare_seq_offset + 4 > yaffsfs->spare_size) ||
            (yaffsfs->spare_obj_id_offset + 4 > yaffsfs->spare_size) ||
            (yaffsfs->spare_chunk_id_offset + 4 > yaffsfs->spare_size)){
            tsk_error_reset();
            tsk_error_set_errno(TSK_ERR_FS);
            tsk_error_set_errstr("yaffs2_open: Offset(s) in config file too large for spare area (size %d). %s", yaffsfs->spare_size, YAFFS_HELP_MESSAGE);
            goto on_error;
        }

        // nBytes isn't currently used, so just set to zero
        yaffsfs->spare_nbytes_offset = 0;
    }
    else{
        // Decide how many blocks to test. If we're not doing auto-detection, set to zero (no limit)
        unsigned int maxBlocksToTest;
        if(yaffsfs->autoDetect){
            maxBlocksToTest = YAFFS_DEFAULT_MAX_TEST_BLOCKS;
        }
        else{
            maxBlocksToTest = 0;
        }

        if(yaffs_initialize_spare_format(yaffsfs, maxBlocksToTest) != TSK_OK){
            tsk_error_reset();
            tsk_error_set_errno(TSK_ERR_FS_MAGIC);
            tsk_error_set_errstr("not a YAFFS file system (bad spare format). %s", YAFFS_HELP_MESSAGE);
            if (tsk_verbose)
                fprintf(stderr, "yaffsfs_open: could not find valid spare area format\n%s\n", YAFFS_HELP_MESSAGE);
            goto on_error;
        }
    }

    /*
    * Read the first record, make sure it's a valid header...
    *
    * Used for verification and autodetection of
    * the FS type.
    */
    if (yaffsfs_read_header(yaffsfs, &first_header, 0)) {
        tsk_error_reset();
        tsk_error_set_errno(TSK_ERR_FS_MAGIC);
        tsk_error_set_errstr("not a YAFFS file system (first record). %s", YAFFS_HELP_MESSAGE);
        if (tsk_verbose)
            fprintf(stderr, "yaffsfs_open: invalid first record\n%s\n", YAFFS_HELP_MESSAGE);
        goto on_error;
    }
    free(first_header);
    first_header = NULL;

    fs->duname = "Chunk";

    /*
    * Calculate the meta data info
    */
    //fs->last_inum = 0xffffffff; // Will update this as we go
    fs->last_inum = 0;
    fs->root_inum = YAFFS_OBJECT_ROOT;
    fs->first_inum = YAFFS_OBJECT_FIRST;
    //fs->inum_count = fs->last_inum; // For now this will be the last_inum - 1 (after we calculate it)

    /*
    * Calculate the block info
    */
    fs->dev_bsize = img_info->sector_size;
    fs->block_size = yaffsfs->page_size;
    fs->block_pre_size = 0;
    fs->block_post_size = yaffsfs->spare_size;
    fs->block_count = img_info->size / (fs->block_pre_size + fs->block_size + fs->block_post_size);
    fs->first_block = 0;
    fs->last_block_act = fs->last_block = fs->block_count ? fs->block_count - 1 : 0;

    /* Set the generic function pointers */
    fs->inode_walk = yaffsfs_inode_walk;
    fs->block_walk = yaffsfs_block_walk;
    fs->block_getflags = yaffsfs_block_getflags;

    fs->get_default_attr_type = yaffsfs_get_default_attr_type;
    fs->load_attrs = yaffsfs_load_attrs;

    fs->file_add_meta = yaffs_inode_lookup;
    fs->dir_open_meta = yaffsfs_dir_open_meta;
    fs->fsstat = yaffsfs_fsstat;
    fs->fscheck = yaffsfs_fscheck;
    fs->istat = yaffsfs_istat;
    fs->name_cmp = tsk_fs_unix_name_cmp;

    fs->close = yaffsfs_close;

    /* Journal */
    fs->jblk_walk = yaffsfs_jblk_walk;
    fs->jentry_walk = yaffsfs_jentry_walk;
    fs->jopen = yaffsfs_jopen;

    /* Initialize the caches */
    if (tsk_verbose)
        fprintf(stderr, "yaffsfs_open: building cache...\n");

    /* Build cache */
    /* NOTE: The only modifications to the cache happen here, during at
    *       the open. Should be fine with no lock, even if access to the
    *       cache is shared among threads.
    */
    //tsk_init_lock(&yaffsfs->lock);
    yaffsfs->chunkMap = new std::map<uint32_t, YaffsCacheChunkGroup>;
    if (TSK_OK != yaffsfs_parse_image_load_cache(yaffsfs)) {
        goto on_error;
    }

    if (tsk_verbose) {
        fprintf(stderr, "yaffsfs_open: done building cache!\n");
        //yaffscache_objects_dump(yaffsfs, stderr);
    }

    // Update the number of inums now that we've read in the file system
    fs->inum_count = fs->last_inum - 1;

    test_dir = tsk_fs_dir_open_meta(fs, fs->root_inum);
    if (test_dir == NULL) {
        tsk_error_reset();
        tsk_error_set_errno(TSK_ERR_FS_MAGIC);
        tsk_error_set_errstr("not a YAFFS file system (no root directory). %s", YAFFS_HELP_MESSAGE);
        if (tsk_verbose)
            fprintf(stderr, "yaffsfs_open: invalid file system\n%s\n", YAFFS_HELP_MESSAGE);
        goto on_error;
    }
    tsk_fs_dir_close(test_dir);

    return fs;

on_error:
    // yaffsfs_close frees all the cache objects
    yaffsfs_close(fs);

    return NULL;
}

Fix stack buffer overflow in yaffsfs_istat

Prevent a stack buffer overflow in yaffsfs_istat by increasing the buffer size to the size required by tsk_fs_time_to_str.

static const int TWELVE_BITS_MASK = 0xFFF;

static uint8_t
    yaffsfs_read_header(YAFFSFS_INFO *yfs, YaffsHeader ** header, TSK_OFF_T offset);

static uint8_t
    yaffsfs_load_attrs(TSK_FS_FILE *file);

typedef struct {
    FILE *hFile;
    int idx;
} YAFFSFS_PRINT_ADDR;

typedef struct _dir_open_cb_args {
    YAFFSFS_INFO *yfs;
    TSK_FS_DIR *dir;
    TSK_INUM_T parent_addr;
} dir_open_cb_args;


    yaffscache_objects_free(YAFFSFS_INFO *yfs)
{
    if((yfs != NULL) && (yfs->cache_objects != NULL)){
        YaffsCacheObject *obj = yfs->cache_objects;
        while(obj != NULL) {
            YaffsCacheObject *to_free = obj;

            YaffsCacheVersion *ver = obj->yco_latest;
            while(ver != NULL) {
                YaffsCacheVersion *v_to_free = ver;
                ver = ver->ycv_prior;
                free(v_to_free);
            }

            obj = obj->yco_next;
            free(to_free);
        }
    }
}

static uint8_t
    yaffsfs_read_header(YAFFSFS_INFO *yfs, YaffsHeader ** header, TSK_OFF_T offset);

typedef TSK_RETVAL_ENUM yc_find_children_cb(YaffsCacheObject *obj, YaffsCacheVersion *version, void *args);

typedef struct _dir_open_cb_args {
    YAFFSFS_INFO *yfs;
    TSK_FS_DIR *dir;
    TSK_INUM_T parent_addr;
} dir_open_cb_args;


    yaffsfs_parse_image_load_cache(YAFFSFS_INFO * yfs)
{
    uint8_t status = TSK_OK;
    uint32_t nentries = 0;
    YaffsSpare *spare = NULL;

    uint8_t tempBuf[8];
    uint32_t parentID;

    if (yfs->cache_objects)
        return 0;

    for(TSK_OFF_T offset = 0;offset < yfs->fs_info.img_info->size;offset += yfs->page_size + yfs->spare_size){
        status = yaffsfs_read_spare( yfs, &spare, offset + yfs->page_size);
        if (status != TSK_OK) {
            break;
        }

        if (yaffsfs_is_spare_valid(yfs, spare) == TSK_OK) {

            if((spare->has_extra_fields) || (spare->chunk_id != 0)){
                yaffscache_chunk_add(yfs,
                    offset,
                    spare->seq_number,
                    spare->object_id,
                    spare->chunk_id,
                    spare->extra_parent_id);
            }
            else{
                // If we have a header block and didn't extract it already from the spare, get the parent ID from
                // the non-spare data
                if(8 == tsk_img_read(yfs->fs_info.img_info, offset, (char*) tempBuf, 8)){
                    memcpy(&parentID, &tempBuf[4], 4);

                    yaffscache_chunk_add(yfs,
                        offset,
                        spare->seq_number,
                        spare->object_id,
                        spare->chunk_id,
                        parentID);
                }
                else{
                    // Really shouldn't happen
                    fprintf(stderr, "Error reading header to get parent id at offset %" PRIxOFF "\n", offset);
                    yaffscache_chunk_add(yfs,
                        offset,
                        spare->seq_number,
                        spare->object_id,
                        spare->chunk_id,
                        0);
                }
            }
        }

        free(spare);
        spare = NULL;

        ++nentries;
    }

    if (tsk_verbose)
        fprintf(stderr, "yaffsfs_parse_image_load_cache: read %d entries\n", nentries);

    if (tsk_verbose)
        fprintf(stderr, "yaffsfs_parse_image_load_cache: started processing chunks for version cache...\n");
    fflush(stderr);

    // At this point, we have a list of chunks sorted by obj id, seq number, and offset
    // This makes the list of objects in cache_objects, which link to different versions
    yaffscache_versions_compute(yfs);

    if (tsk_verbose)
        fprintf(stderr, "yaffsfs_parse_image_load_cache: done version cache!\n");
    fflush(stderr);

    // Having multiple inodes point to the same object seems to cause trouble in TSK, especially in orphan file detection,
    //  so set the version number of the final one to zero.
    // While we're at it, find the highest obj_id and the highest version (before resetting to zero)
    YaffsCacheObject * currObj = yfs->cache_objects;
    YaffsCacheVersion * currVer;
    while(currObj != NULL){
        if(currObj->yco_obj_id > yfs->max_obj_id){
            yfs->max_obj_id = currObj->yco_obj_id;
        }

        currVer = currObj->yco_latest;
        if(currVer->ycv_version > yfs->max_version){
            yfs->max_version = currVer->ycv_version;
        }

        currVer->ycv_version = 0;
        currObj = currObj->yco_next;
    }

    // Use the max object id and version number to construct an upper bound on the inode
    TSK_INUM_T max_inum = 0;
    if (TSK_OK != yaffscache_obj_id_and_version_to_inode(yfs->max_obj_id, yfs->max_version, &max_inum)) {
        return TSK_ERR;
    }
    yfs->fs_info.last_inum = max_inum + 1; // One more for the orphan dir

    // Make sure the orphan dir is greater than the root dir
    if (yfs->fs_info.last_inum <= yfs->fs_info.root_inum) {
        tsk_error_reset();
        tsk_error_set_errno(TSK_ERR_FS);
        tsk_error_set_errstr(
            "yaffsfs_parse_image_load_cache: Maximum inum %" PRIuINUM " is not greater than the root inum", yfs->fs_info.last_inum);
        return TSK_ERR;
    }

    return TSK_OK;
}

static uint8_t
    yaffsfs_read_header(YAFFSFS_INFO *yfs, YaffsHeader ** header, TSK_OFF_T offset);

static uint8_t
    yaffsfs_load_attrs(TSK_FS_FILE *file);

typedef TSK_RETVAL_ENUM yc_find_children_cb(YaffsCacheObject *obj, YaffsCacheVersion *version, void *args);

typedef struct _dir_open_cb_args {
    YAFFSFS_INFO *yfs;
    TSK_FS_DIR *dir;
    TSK_INUM_T parent_addr;
} dir_open_cb_args;


    yaffscache_object_find(YAFFSFS_INFO *yfs, uint32_t obj_id, YaffsCacheObject **obj)
{
    YaffsCacheObject *curr, *prev;
    curr = yfs->cache_objects;
    prev = NULL;

    if (obj == NULL) {
        return TSK_ERR;
    }

    while(curr != NULL) {
        if (curr->yco_obj_id == obj_id) {
            *obj = curr;
            return TSK_OK;
        }
        else if (curr->yco_obj_id > obj_id) {
            *obj = prev;
            return TSK_STOP;
        }

        prev = curr;
        curr = curr->yco_next;
    }

    *obj = prev;
    return TSK_STOP;
}


    yaffscache_chunk_find_insertion_point(YAFFSFS_INFO *yfs, uint32_t obj_id, TSK_OFF_T offset, uint32_t seq_number, YaffsCacheChunk **chunk)
{
    YaffsCacheChunk *curr, *prev;

    // Have we seen this obj_id? If not, add an entry for it
    if(yfs->chunkMap->find(obj_id) == yfs->chunkMap->end()){
        fflush(stderr);
        YaffsCacheChunkGroup chunkGroup;
        chunkGroup.cache_chunks_head = NULL;
        chunkGroup.cache_chunks_tail = NULL;
        yfs->chunkMap->insert(std::make_pair(obj_id, chunkGroup));
    }

    curr = yfs->chunkMap->operator[](obj_id).cache_chunks_head;
    prev = NULL;

    if (chunk == NULL) {
        return TSK_ERR;
    }

    while(curr != NULL) {
        // Compares obj id, then seq num, then offset. -1 => current < new
        int cmp = yaffscache_chunk_compare(curr, obj_id, offset, seq_number);

        if (cmp == 0) {
            *chunk = curr;
            return TSK_OK;
        }
        else if (cmp == 1) {
            *chunk = prev;
            return TSK_STOP;
        }

        prev = curr;
        curr = curr->ycc_next;
    }

    *chunk = prev;
    return TSK_STOP;
}

static const int TWELVE_BITS_MASK = 0xFFF;

static uint8_t
    yaffsfs_read_header(YAFFSFS_INFO *yfs, YaffsHeader ** header, TSK_OFF_T offset);

typedef TSK_RETVAL_ENUM yc_find_children_cb(YaffsCacheObject *obj, YaffsCacheVersion *version, void *args);

typedef struct {
    FILE *hFile;
    int idx;
} YAFFSFS_PRINT_ADDR;

typedef struct _dir_open_cb_args {
    YAFFSFS_INFO *yfs;
    TSK_FS_DIR *dir;
    TSK_INUM_T parent_addr;
} dir_open_cb_args;


    yaffs_make_directory(YAFFSFS_INFO *yaffsfs, TSK_FS_FILE *a_fs_file,
    TSK_INUM_T inode, const char *name)
{
    TSK_FS_FILE *fs_file = a_fs_file;

    fs_file->meta->type = TSK_FS_META_TYPE_DIR;
    fs_file->meta->mode = (TSK_FS_META_MODE_ENUM)0;
    fs_file->meta->nlink = 1;

    if((inode == YAFFS_OBJECT_UNLINKED) || (inode == YAFFS_OBJECT_DELETED) ||
        (inode == yaffsfs->fs_info.last_inum)){
            fs_file->meta->flags =
                (TSK_FS_META_FLAG_ENUM)(TSK_FS_META_FLAG_USED | TSK_FS_META_FLAG_ALLOC);
    }
    else{
        if(yaffs_is_version_allocated(yaffsfs, inode)){
            fs_file->meta->flags =
                (TSK_FS_META_FLAG_ENUM)(TSK_FS_META_FLAG_USED | TSK_FS_META_FLAG_ALLOC);
        }
        else{
            fs_file->meta->flags =
                (TSK_FS_META_FLAG_ENUM)(TSK_FS_META_FLAG_USED | TSK_FS_META_FLAG_UNALLOC);
        }
    }
    fs_file->meta->uid = fs_file->meta->gid = 0;
    fs_file->meta->mtime = fs_file->meta->atime = fs_file->meta->ctime =
        fs_file->meta->crtime = 0;
    fs_file->meta->mtime_nano = fs_file->meta->atime_nano =
        fs_file->meta->ctime_nano = fs_file->meta->crtime_nano = 0;

    if (fs_file->meta->name2 == NULL) {
        if ((fs_file->meta->name2 = (TSK_FS_META_NAME_LIST *)
            tsk_malloc(sizeof(TSK_FS_META_NAME_LIST))) == NULL) {
            return 1;
        }
        fs_file->meta->name2->next = NULL;
    }

    if (fs_file->meta->attr != NULL) {
        tsk_fs_attrlist_markunused(fs_file->meta->attr);
    }
    else {
        fs_file->meta->attr = tsk_fs_attrlist_alloc();
    }

    strncpy(fs_file->meta->name2->name, name,
        TSK_FS_META_NAME_LIST_NSIZE);

    fs_file->meta->size = 0;
    fs_file->meta->attr_state = TSK_FS_META_ATTR_EMPTY;
    fs_file->meta->addr = inode;
    return 0;
}

static const int TWELVE_BITS_MASK = 0xFFF;

static uint8_t
    yaffsfs_read_header(YAFFSFS_INFO *yfs, YaffsHeader ** header, TSK_OFF_T offset);

static uint8_t
    yaffsfs_load_attrs(TSK_FS_FILE *file);

typedef struct _dir_open_cb_args {
    YAFFSFS_INFO *yfs;
    TSK_FS_DIR *dir;
    TSK_INUM_T parent_addr;
} dir_open_cb_args;


yaffs_load_config_file(TSK_IMG_INFO * a_img_info, std::map<std::string, std::string> & results){
    size_t config_file_name_len;
    TSK_TCHAR * config_file_name;
    FILE* config_file;
    char buf[1001];

    // Ensure there is at least one image name
    if(a_img_info->num_img < 1){
        return YAFFS_CONFIG_ERROR;
    }

    // Construct the name of the config file from the first image name
    config_file_name_len = TSTRLEN(a_img_info->images[0]);
    config_file_name_len += TSTRLEN(YAFFS_CONFIG_FILE_SUFFIX);
    config_file_name = (TSK_TCHAR *) tsk_malloc(sizeof(TSK_TCHAR) * (config_file_name_len + 1));

    TSTRNCPY(config_file_name, a_img_info->images[0], TSTRLEN(a_img_info->images[0]) + 1);
    TSTRNCAT(config_file_name, YAFFS_CONFIG_FILE_SUFFIX, TSTRLEN(YAFFS_CONFIG_FILE_SUFFIX) + 1);

#ifdef TSK_WIN32
    HANDLE hWin;

    if ((hWin = CreateFile(config_file_name, GENERIC_READ,
            FILE_SHARE_READ, 0, OPEN_EXISTING, 0,
            0)) == INVALID_HANDLE_VALUE) {

        // For the moment, assume that the file just doesn't exist, which isn't an error
        free(config_file_name);
        return YAFFS_CONFIG_FILE_NOT_FOUND;
    }
    config_file = _fdopen(_open_osfhandle((intptr_t) hWin, _O_RDONLY), "r");
    if (config_file == NULL) {
        tsk_error_reset();
        tsk_error_set_errno(TSK_ERR_FS);
        tsk_error_set_errstr(
                    "yaffs_load_config: Error converting Windows handle to C handle");
        free(config_file_name);
        CloseHandle(hWin);
        return YAFFS_CONFIG_ERROR;
    }
#else
    if (NULL == (config_file = fopen(config_file_name, "r"))) {
        free(config_file_name);
        return YAFFS_CONFIG_FILE_NOT_FOUND;
    }
#endif

    while(fgets(buf, 1000, config_file) != NULL){

        // Is it a comment?
        if((buf[0] == '#') || (buf[0] == ';')){
            continue;
        }

        // Is there a '=' ?
        if(strchr(buf, '=') == NULL){
            continue;
        }

        // Copy to strings while removing whitespace and converting to lower case
        std::string paramName("");
        std::string paramVal("");

        const char * paramNamePtr = strtok(buf, "=");
        while(*paramNamePtr != '\0'){
            if(! isspace((char)(*paramNamePtr))){
                paramName += tolower((char)(*paramNamePtr));
            }
            paramNamePtr++;
        }

        const char * paramValPtr = strtok(NULL, "=");
        while(*paramValPtr != '\0'){
            if(! isspace(*paramValPtr)){
                paramVal += tolower((char)(*paramValPtr));
            }
            paramValPtr++;
        }

        // Make sure this parameter is not already in the map
        if(results.find(paramName) != results.end()){
            // Duplicate parameter - return an error
            tsk_error_reset();
            tsk_error_set_errno(TSK_ERR_FS);
            tsk_error_set_errstr(
                        "yaffs_load_config: Duplicate parameter name in config file (\"%s\"). %s", paramName.c_str(), YAFFS_HELP_MESSAGE);
            fclose(config_file);
            free(config_file_name);
            return YAFFS_CONFIG_ERROR;
        }

        // Add this entry to the map
        results[paramName] = paramVal;
    }

    fclose(config_file);
    free(config_file_name);
    return YAFFS_CONFIG_OK;
}

static const int TWELVE_BITS_MASK = 0xFFF;

static uint8_t
    yaffsfs_load_attrs(TSK_FS_FILE *file);

typedef struct {
    FILE *hFile;
    int idx;
} YAFFSFS_PRINT_ADDR;


    yaffscache_object_dump(FILE *fp, YaffsCacheObject *obj)
{
    YaffsCacheVersion *next_version = obj->yco_latest;
    YaffsCacheChunk *chunk = next_version->ycv_last_chunk;

    fprintf(fp, "Object %d\n", obj->yco_obj_id);
    while(chunk != NULL && chunk->ycc_obj_id == obj->yco_obj_id) {
        if (next_version != NULL &&
            chunk == next_version->ycv_last_chunk) {
                fprintf(fp, "  @%d: %p %p %p\n",
                    next_version->ycv_version,
                    (void*) next_version->ycv_header_chunk,
                    (void*) next_version->ycv_first_chunk,
                    (void*)next_version->ycv_last_chunk);
                next_version = next_version->ycv_prior;
        }

        fprintf(fp, "    + %p %08x %08x %0" PRIxOFF "\n",
            (void*) chunk,
            chunk->ycc_chunk_id,
            chunk->ycc_seq_number,
            chunk->ycc_offset);

        chunk = chunk->ycc_prev;
    }
}

typedef TSK_RETVAL_ENUM yc_find_children_cb(YaffsCacheObject *obj, YaffsCacheVersion *version, void *args);

typedef struct {
    FILE *hFile;
    int idx;
} YAFFSFS_PRINT_ADDR;


    yaffsfs_inode_walk(TSK_FS_INFO *fs, TSK_INUM_T start_inum,
    TSK_INUM_T end_inum, TSK_FS_META_FLAG_ENUM flags,
    TSK_FS_META_WALK_CB a_action, void *a_ptr)
{
    YAFFSFS_INFO *yfs = (YAFFSFS_INFO *)fs;
    TSK_FS_FILE *fs_file;
    TSK_RETVAL_ENUM result;

    uint32_t start_obj_id;
    uint32_t start_ver_number;
    uint32_t end_obj_id;
    uint32_t end_ver_number;

    uint32_t obj_id;

    YaffsCacheObject *curr_obj;
    YaffsCacheVersion *curr_version;

    result = yaffscache_inode_to_obj_id_and_version(start_inum, &start_obj_id, &start_ver_number);

    result = yaffscache_inode_to_obj_id_and_version(end_inum, &end_obj_id, &end_ver_number);

    if (end_obj_id < start_obj_id) {
        tsk_error_reset();
        tsk_error_set_errno(TSK_ERR_FS_WALK_RNG);
        tsk_error_set_errstr("yaffsfs_inode_walk: end object id must be >= start object id: "
            "%" PRIx32 " must be >= %" PRIx32 "",
            end_obj_id, start_obj_id);
        return 1;
    }

    /* The ORPHAN flag is unsupported for YAFFS2 */
    if (flags & TSK_FS_META_FLAG_ORPHAN) {
        if (tsk_verbose){
            tsk_fprintf(stderr, "yaffsfs_inode_walk: ORPHAN flag unsupported by YAFFS2");
        }
    }

    if (((flags & TSK_FS_META_FLAG_ALLOC) == 0) &&
        ((flags & TSK_FS_META_FLAG_UNALLOC) == 0)) {
            flags = (TSK_FS_META_FLAG_ENUM)(flags | TSK_FS_META_FLAG_ALLOC | TSK_FS_META_FLAG_UNALLOC);
    }

    /* If neither of the USED or UNUSED flags are set, then set them
    * both
    */
    if (((flags & TSK_FS_META_FLAG_USED) == 0) &&
        ((flags & TSK_FS_META_FLAG_UNUSED) == 0)) {
            flags = (TSK_FS_META_FLAG_ENUM)(flags | TSK_FS_META_FLAG_USED | TSK_FS_META_FLAG_UNUSED);
    }

    if ((fs_file = tsk_fs_file_alloc(fs)) == NULL)
        return 1;
    if ((fs_file->meta =
        tsk_fs_meta_alloc(YAFFS_FILE_CONTENT_LEN)) == NULL)
        return 1;

    for (obj_id = start_obj_id; obj_id <= end_obj_id; obj_id++) {
        int retval;

        result = yaffscache_version_find_by_inode(yfs, obj_id, &curr_version, &curr_obj);
        if (result == TSK_OK) {

            TSK_INUM_T curr_inode;
            YaffsCacheVersion *version;

            // ALLOC, UNALLOC, or both are set at this point
            if (flags & TSK_FS_META_FLAG_ALLOC) {
                // Allocated only - just look at current version
                if (yaffscache_obj_id_and_version_to_inode(obj_id, curr_obj->yco_latest->ycv_version, &curr_inode) != TSK_OK) {
                    tsk_fs_file_close(fs_file);
                    return 1;
                }

                // It's possible for the current version to be unallocated if the last header was a deleted or unlinked header
                if(yaffs_is_version_allocated(yfs, curr_inode)){
                    if (yaffs_inode_lookup(fs, fs_file, curr_inode) != TSK_OK) {
                        tsk_fs_file_close(fs_file);
                        return 1;
                    }

                    retval = a_action(fs_file, a_ptr);
                    if (retval == TSK_WALK_STOP) {
                        tsk_fs_file_close(fs_file);
                        return 0;
                    }
                    else if (retval == TSK_WALK_ERROR) {
                        tsk_fs_file_close(fs_file);
                        return 1;
                    }
                }
            }
            if (flags & TSK_FS_META_FLAG_UNALLOC){
                for (version = curr_obj->yco_latest; version != NULL; version = version->ycv_prior) {
                    if (yaffscache_obj_id_and_version_to_inode(obj_id, version->ycv_version, &curr_inode) != TSK_OK) {
                        tsk_fs_file_close(fs_file);
                        return 1;
                    }

                    if(! yaffs_is_version_allocated(yfs, curr_inode)){
                        if (yaffs_inode_lookup(fs, fs_file, curr_inode) != TSK_OK) {
                            tsk_fs_file_close(fs_file);
                            return 1;
                        }

                        retval = a_action(fs_file, a_ptr);
                        if (retval == TSK_WALK_STOP) {
                            tsk_fs_file_close(fs_file);
                            return 0;
                        }
                        else if (retval == TSK_WALK_ERROR) {
                            tsk_fs_file_close(fs_file);
                            return 1;
                        }
                    }
                }
            }

            curr_obj = curr_obj->yco_next;
        }
    }

    /*
    * Cleanup.
    */
    tsk_fs_file_close(fs_file);
    return 0;
}

static const int TWELVE_BITS_MASK = 0xFFF;

static uint8_t
    yaffsfs_read_header(YAFFSFS_INFO *yfs, YaffsHeader ** header, TSK_OFF_T offset);

static uint8_t
    yaffsfs_load_attrs(TSK_FS_FILE *file);

typedef TSK_RETVAL_ENUM yc_find_children_cb(YaffsCacheObject *obj, YaffsCacheVersion *version, void *args);

typedef struct {
    FILE *hFile;
    int idx;
} YAFFSFS_PRINT_ADDR;

typedef struct _dir_open_cb_args {
    YAFFSFS_INFO *yfs;
    TSK_FS_DIR *dir;
    TSK_INUM_T parent_addr;
} dir_open_cb_args;