static int filter_get_image( mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable )
{
	mlt_filter filter = (mlt_filter) mlt_frame_pop_service( frame );
	int error = 0;


	// Regenerate the LUT if necessary
	mlt_service_lock( MLT_FILTER_SERVICE( filter ) );
	refresh_lut( filter, frame );
	mlt_service_unlock( MLT_FILTER_SERVICE( filter ) );

	// Make sure the format is acceptable
	if( *format != mlt_image_rgb24 && *format != mlt_image_rgb24a )
	{
		*format = mlt_image_rgb24;
	}

	// Get the image
	writable = 1;
	error = mlt_frame_get_image( frame, image, format, width, height, writable );

	// Apply the LUT
	if( !error )
	{
		apply_lut( filter, *image, *format, *width, *height );
	}

	return error;
}
void histo_l_algo(const TRasterImageP &image, int reference)
{
	int lx, ly, wrap;
	int x, y, grey, ref_grey, m, d, dd;
	UCHAR *buffer, *pix, *north, *south;
	int g_histo[256];
	int d_histo[256][256];
	int cum[256];
	long n;
	BIG s;
	float mean_d[256];
	int max_d_grey;
	float max_d;
	int conf;
	int edgelen;
	float fac;
	UCHAR lut[256];

	if (!Edge_init_done)
		edge_init();

	get_virtual_buffer(image, &lx, &ly, &wrap, &buffer);

	for (grey = 0; grey < 256; grey++) {
		g_histo[grey] = 0;
		for (d = 0; d < 256; d++)
			d_histo[grey][d] = 0;
	}

	/* istogramma dei grigi e istogramma delle derivate */
	for (y = 1; y < ly - 1; y++) {
		pix = buffer + y * wrap + 1;
		north = pix + wrap;
		south = pix - wrap;
		for (x = 1; x < lx - 1; x++, pix++, north++, south++) {
			m = 2 * ((int)north[0] + (int)pix[-1] + 2 * (int)pix[0] + (int)pix[1] + (int)south[0]) + (int)north[-1] + (int)north[1] + (int)south[-1] + (int)south[1];
			m = (m + 8) >> 4;
			d = (int)north[-1] + (int)north[0] * 2 + (int)north[1] - (int)south[-1] - (int)south[0] * 2 - (int)south[1];
			TO_ABS(d)
			dd = (int)north[-1] + (int)pix[-1] * 2 + (int)south[-1] - (int)north[1] - (int)pix[1] * 2 - (int)south[1];
			TO_ABS(dd)
			if (dd > d)
				d = dd;
			dd = (int)pix[-1] + (int)north[-1] * 2 + (int)north[0] - (int)pix[1] - (int)south[1] * 2 - (int)south[0];
			TO_ABS(dd)
			if (dd > d)
				d = dd;
			dd = (int)pix[1] + (int)north[1] * 2 + (int)north[0] - (int)pix[-1] - (int)south[-1] * 2 - (int)south[0];
			TO_ABS(dd)
			if (dd > d)
				d = dd;
			d = (d + 2) >> 2;
			d_histo[m][d]++;
			g_histo[*pix]++;
		}
	}
	build_cum(g_histo, cum);

	/* costruzione pendenze medie */
	for (grey = 0; grey < 256; grey++) {
		n = 0;
		CLEAR_BIG(s);
		for (d = 0; d < 256; d++) {
			ADD_BIG(s, d_histo[grey][d] * d);
			n += d_histo[grey][d];
		}
		if (n)
			mean_d[grey] = (float)(BIG_TO_DOUBLE(s) / n);
		else
			mean_d[grey] = 0.0;
	}
	smooth_func256(mean_d, 5);

	/* determinazione grigio di massima pendenza */
	max_d_grey = 0;
	max_d = 0.0;
	for (grey = 0; grey < 256; grey++) {
		if (max_d < mean_d[grey]) {
			max_d = mean_d[grey];
			max_d_grey = grey;
		}
	}

	/* stima della lunghezza dei bordi */
	edgelen = 0;
	for (y = 1; y < ly - 1; y++) {
		pix = buffer + y * wrap + 1;
		north = pix + wrap;
		south = pix - wrap;
		conf = -1;
		for (x = 1; x < lx - 1; x++, pix++, north++, south++) {
			if (pix[0] <= max_d_grey) {
				if (conf >= 0) {
					conf = ((conf << 1) & ((1 << 7) | (1 << 6) | (1 << 2) | (1 << 1))) |
						   ((north[1] <= max_d_grey) << 5) |
						   ((1) << 4) |
						   ((pix[1] <= max_d_grey) << 3) |
						   ((south[1] <= max_d_grey));
				} else {
					conf = ((north[-1] <= max_d_grey) << 7) |
						   ((north[0] <= max_d_grey) << 6) |
						   ((north[1] <= max_d_grey) << 5) |
						   ((pix[-1] <= max_d_grey) << 4) |
						   ((pix[1] <= max_d_grey) << 3) |
						   ((south[-1] <= max_d_grey) << 2) |
						   ((south[0] <= max_d_grey) << 1) |
						   ((south[1] <= max_d_grey));
				}
				edgelen += Edge_value[conf];
			} else
				conf = -1;
		}
	}

	if (reference) {
		for (grey = 0; grey < 256; grey++)
			Ref_cum[grey] = cum[grey];
		Ref_edgelen = edgelen;

		return;
	}

	/* normalizza la cumulativa per il numero di linee */
	fac = (float)Ref_edgelen / edgelen;

	/***
printf ("fac: %f\n", fac);
printf ("cum prima:\n");
for (grey = 0; grey < 256; grey++)
  printf ("%9d\n", cum[grey]);
***/

	for (grey = 0; grey < 255; grey++) {
		cum[grey] = (int)(cum[grey] * fac);
		notMoreThan(cum[255], cum[grey]);
	}

	/***
printf ("cum dopo:\n");
for (grey = 0; grey < 256; grey++)
  printf ("%9d\n", cum[grey]);
printf ("Ref_cum:\n");
for (grey = 0; grey < 256; grey++)
  printf ("%9d\n", Ref_cum[grey]);
***/

	/* equalizza l'istogramma */
	ref_grey = 0;
	for (grey = 0; grey < 255; grey++) {
		while (Ref_cum[ref_grey] < cum[grey])
			ref_grey++;
		lut[grey] = ref_grey;
	}
	lut[255] = 255;
	/* DAFARE
if (Wl_flag)
  write_lut_image (lut);
*/
	apply_lut(image, lut);
}
void black_eq_algo(const TRasterImageP &image)
{
	int lx, ly, wrap;
	int x, y, grey /*, width*/;
	int d_histo[256][256];
	int d, dd, m;
	UCHAR *buffer, *pix, *north, *south;
	UCHAR prev, darkest;
	long n;
	float mean_d[256];
	BIG s;
	int max_d_grey;
	float max_d;
	float fac;
	int val;
	UCHAR lut[256];
	image->getRaster()->lock();

	get_virtual_buffer(image, &lx, &ly, &wrap, &buffer);

	for (grey = 0; grey < 256; grey++) {
		for (d = 0; d < 256; d++)
			d_histo[grey][d] = 0;
	}
	for (y = 1; y < ly - 1; y++) {
		pix = buffer + y * wrap + 1;
		north = pix + wrap;
		south = pix - wrap;
		for (x = 1; x < lx - 1; x++, pix++, north++, south++) {
			m = 2 * ((int)north[0] + (int)pix[-1] + 2 * (int)pix[0] + (int)pix[1] + (int)south[0]) + (int)north[-1] + (int)north[1] + (int)south[-1] + (int)south[1];
			m = (m + 8) >> 4;
			d = (int)north[-1] + (int)north[0] * 2 + (int)north[1] - (int)south[-1] - (int)south[0] * 2 - (int)south[1];
			TO_ABS(d)
			dd = (int)north[-1] + (int)pix[-1] * 2 + (int)south[-1] - (int)north[1] - (int)pix[1] * 2 - (int)south[1];
			TO_ABS(dd)
			if (dd > d)
				d = dd;
			dd = (int)pix[-1] + (int)north[-1] * 2 + (int)north[0] - (int)pix[1] - (int)south[1] * 2 - (int)south[0];
			TO_ABS(dd)
			if (dd > d)
				d = dd;
			dd = (int)pix[1] + (int)north[1] * 2 + (int)north[0] - (int)pix[-1] - (int)south[-1] * 2 - (int)south[0];
			TO_ABS(dd)
			if (dd > d)
				d = dd;
			d = (d + 2) >> 2;
			d_histo[m][d]++;
		}
	}
	for (grey = 0; grey < 256; grey++) {
		n = 0;
		CLEAR_BIG(s);
		for (d = 0; d < 256; d++) {
			ADD_BIG(s, d_histo[grey][d] * d);
			n += d_histo[grey][d];
		}
		if (n)
			mean_d[grey] = (float)(BIG_TO_DOUBLE(s) / n);
		else
			mean_d[grey] = 0;
	}
	smooth_func256(mean_d, 5);
	max_d_grey = 0;
	max_d = 0.0;
	for (grey = 0; grey < 256; grey++) {
		if (max_d < mean_d[grey]) {
			max_d = mean_d[grey];
			max_d_grey = grey;
		}
	}

	n = 0;
	CLEAR_BIG(s);
	for (y = 0; y < ly; y++) {
		pix = buffer + y * wrap;
		prev = 255;
		darkest = 255;
		for (x = 0; x < lx; x++, pix++) {
			if (*pix < max_d_grey) {
				if (prev < max_d_grey)
					notMoreThan(*pix, darkest);
				else
					darkest = *pix;
			} else {
				if (prev < max_d_grey) {
					ADD_BIG(s, darkest);
					n++;
				}
			}
			prev = *pix;
		}
	}
	darkest = troundp(BIG_TO_DOUBLE(s) / n);

	/*
for (grey = 0; grey < darkest; grey++)
  lut[grey] = 0;
fac = (float)255 / (float)(255 - darkest);
for (grey = darkest; grey < 256; grey++)
  lut[grey] = 255 - troundp ((255 - grey) * fac);
*/
	notLessThan(0, Black);
	;
	notMoreThan(255, Black);
	fac = (float)(255 - Black) / (float)(255 - darkest);
	for (grey = 0; grey < 256; grey++) {
		val = 255 - troundp((255 - grey) * fac);
		notLessThan(0, val);
		notMoreThan(255, val);
		lut[grey] = val;
	}

	apply_lut(image, lut);
	image->getRaster()->unlock();
}
Exemple #4
0
// Now the thumbnail generation is combined with the stats calculations,
// to speed things up a little.  Both require a pass through the entire
// image, so it seems natural, and the stats calculation doesn't add much
// overhead.  (The user is waiting while the thumbnail is being generated,
// so we do want this to be as quick as possible.)
unsigned char *generate_thumbnail_data(ImageInfo *ii, int tsx, int tsy)
{
    int i,j;
    unsigned char *bdata = MALLOC(sizeof(unsigned char)*tsx*tsy*3);
    ImageStats *stats = &(ii->stats);

    // we will estimate the stats from the thumbnail data
    clear_stats(ii);

    // Here we do the rather ugly thing of making the thumbnail
    // loading code specific to each supported data type.  This is
    // because we've combined the stats calculation into this...
    if (ii->data_ci->data_type == GREYSCALE_FLOAT) {
        // store data used to build the small image pixmap
        // we will calculate the stats on this subset
        float *fdata = CALLOC(sizeof(float), tsx*tsy);

        load_thumbnail_data(ii->data_ci, tsx, tsy, fdata);
        set_ignores(ii, !g_startup);

        // split out the case where we have no ignore value --
        // should be quite a bit faster...
        if (have_ignore(stats)) {
            // Compute stats -- ignore "no data" value
            int n=0;
            for (i=0; i<tsy; ++i) {
                for (j=0; j<tsx; ++j) {
                    float v = fdata[j+i*tsx];
                    if (!is_ignored(stats, v) && fabs(v)<999999999)
                    {
                        stats->avg += v;

                        // first valid pixel --> initialize max/min
                        // subsequent pixels --> update max/min if needed
                        if (n==0) {
                            stats->act_max = stats->act_min = v;
                        } else {
                            if (v > stats->act_max)
                              stats->act_max = v;
                            if (v < stats->act_min)
                              stats->act_min = v;
                        }
                        ++n;
                    }
                }
            }
            stats->avg /= (double)n;
            for (i=0; i<tsy; ++i) {
                for (j=0; j<tsx; ++j) {
                    float v = fdata[j+i*tsx];
                    if (!is_ignored(stats, v) && fabs(v)<999999999)
                    {
                        stats->stddev +=
                          (v - stats->avg)*(v - stats->avg);
                    }
                }
            }
            stats->stddev = sqrt(stats->stddev / (double)n);
        } else {
            // Compute stats, no ignore (actually, do ignore data that is NaN)
            stats->act_max = stats->act_min = fdata[0];
            for (i=0; i<tsy; ++i) {
                for (j=0; j<tsx; ++j) {
                    float v = fdata[j+i*tsx];
                    // added in the fabs<999... thing... sometimes values
                    // are just ridiculous and we must ignore them
                    if (meta_is_valid_double(v) && fabs(v)<999999999) {
                        stats->avg += v;
                        if (v > stats->act_max) stats->act_max = v;
                        if (v < stats->act_min) stats->act_min = v;
                    }
                }
            }
            stats->avg /= (double)(tsx*tsy);
            for (i=0; i<tsy; ++i) {
                for (j=0; j<tsx; ++j) {
                    float v = fdata[j+i*tsx];
                    if (meta_is_valid_double(v) && fabs(v)<999999999)
                        stats->stddev +=
                          (v - stats->avg) * (v - stats->avg);
                }
            }
            stats->stddev = sqrt(stats->stddev / (double)(tsx*tsy));
        }

        //printf("Avg, StdDev: %f, %f\n", stats->avg, stats->stddev);

        set_mapping(ii, !g_startup);

        // Now actually scale the data, and convert to bytes.
        // Note that we need 3 values, one for each of the RGB channels.
        if (have_lut()) {
            // look up table case -- no scaling, just use the lut
            // to convert from float to rgb byte
            for (i=0; i<tsy; ++i) {
                for (j=0; j<tsx; ++j) {
                    int index = j+i*tsx;
                    int n = 3*index;
                    float val = fdata[index];

                    int ival;
                    if (is_ignored(stats, val) || val<0)
                        ival = ignore_grey_value;
                    else
                        ival = (int)val;

                    apply_lut(ival, &bdata[n], &bdata[n+1], &bdata[n+2]);

                    // histogram will appear as if we were scaling
                    // to greyscale byte
                    unsigned char uval = (unsigned char)
                      calc_scaled_pixel_value(stats, val);
                    stats->hist[uval] += 1;
                }
            }

        } else {
            // normal case -- no lut, apply selected scaling to convert from
            // floating point to byte for display
            for (i=0; i<tsy; ++i) {
                for (j=0; j<tsx; ++j) {
                    int index = j+i*tsx;
                    float val = fdata[index];
                    int n = 3*index;

                    unsigned char uval = (unsigned char)
                      calc_scaled_pixel_value(stats, val);

                    if (is_ignored(stats, val)) {
                      bdata[n] = bdata[n+1] = bdata[n+2] = ignore_grey_value;
                    }
                    else {
                      bdata[n] = uval;
                      bdata[n+1] = uval;
                      bdata[n+2] = uval;

                      stats->hist[uval] += 1;
                    }
                }
            }
        }

        // done with our subset
        free(fdata);
    }
    else if (ii->data_ci->data_type == RGB_BYTE) {

        // store data used to build the small image pixmap
        // we will calculate the stats on this subset
        unsigned char *rgbdata = CALLOC(sizeof(unsigned char), tsx*tsy*3);
        load_thumbnail_data(ii->data_ci, tsx, tsy, (void*)rgbdata);
        set_ignores(ii, !g_startup);

        ImageStatsRGB *stats_r = &ii->stats_r;
        ImageStatsRGB *stats_g = &ii->stats_g;
        ImageStatsRGB *stats_b = &ii->stats_b;

        stats_r->act_min = rgbdata[0];
        stats_r->act_max = rgbdata[0];

        stats_g->act_min = rgbdata[1];
        stats_g->act_max = rgbdata[1];

        stats_b->act_min = rgbdata[2];
        stats_b->act_max = rgbdata[2];

        int nr=0, ng=0, nb=0;
        for (i=0; i<tsy; ++i) {
            for (j=0; j<tsx; ++j) {
                int kk = 3*(j+i*tsx);

                if (!is_ignored_rgb(stats_r, rgbdata[kk])) {
                    stats_r->avg += rgbdata[kk];
                    ++nr;

                    if (rgbdata[kk] < stats_r->act_min)
                      stats_r->act_min = rgbdata[kk];
                    if (rgbdata[kk] > stats_r->act_max)
                      stats_r->act_max = rgbdata[kk];
                }

                if (!is_ignored_rgb(stats_g, rgbdata[kk+1])) {
                    stats_g->avg += rgbdata[kk+1];
                    ++ng;

                    if (rgbdata[kk+1] < stats_g->act_min)
                      stats_g->act_min = rgbdata[kk+1];
                    if (rgbdata[kk+1] > stats_g->act_max)
                      stats_g->act_max = rgbdata[kk+1];
                }

                if (!is_ignored_rgb(stats_b, rgbdata[kk+2])) {
                    stats_b->avg += rgbdata[kk+2];
                    ++nb;

                    if (rgbdata[kk+2] < stats_b->act_min)
                      stats_b->act_min = rgbdata[kk+2];
                    if (rgbdata[kk+2] > stats_b->act_max)
                      stats_b->act_max = rgbdata[kk+2];
                }
            }
        }
        
        if (nr>1) stats_r->avg /= (double)nr;
        if (ng>1) stats_g->avg /= (double)ng;
        if (nb>1) stats_b->avg /= (double)nb;

        for (i=0; i<tsy; ++i) {
            for (j=0; j<tsx; ++j) {
                int kk = 3*(j+i*tsx);

                if (!is_ignored_rgb(stats_r, rgbdata[kk])) {
                    float d = rgbdata[kk] - stats_r->avg;
                    stats_r->stddev += d*d;
                }

                if (!is_ignored_rgb(stats_g, rgbdata[kk+1])) {
                    float d = rgbdata[kk+1] - stats_g->avg;
                    stats_g->stddev += d*d;
                }

                if (!is_ignored_rgb(stats_b, rgbdata[kk+2])) {
                    float d = rgbdata[kk+2] - stats_b->avg;
                    stats_b->stddev += d*d;
                }
            }
        }

        stats_r->stddev = sqrt(stats_r->stddev / (double)nr);
        stats_g->stddev = sqrt(stats_g->stddev / (double)ng);
        stats_b->stddev = sqrt(stats_b->stddev / (double)nb);

        set_mapping(ii, !g_startup);

        // clear out the stats for the greyscale image - we'll just use
        // the histogram
        stats->avg = 0;
        stats->stddev = 0;

	// these are used for the axis labels on the histogram, so we put
	// in the averages of the mins... these are sort of bogus anyway, we
	// really should have 3 histograms
        stats->map_min = calc_fake_min(ii);
	stats->map_max = calc_fake_max(ii);

        for (i=0; i<tsy; ++i) {
            for (j=0; j<tsx; ++j) {
                int kk = 3*(j+i*tsx);

                if (!is_ignored_rgb(stats_r, rgbdata[kk]) &&
                    !is_ignored_rgb(stats_g, rgbdata[kk+1]) &&
                    !is_ignored_rgb(stats_b, rgbdata[kk+2]))
                {
                  bdata[kk] = (unsigned char)calc_rgb_scaled_pixel_value(
                    stats_r, rgbdata[kk]);
                  bdata[kk+1] = (unsigned char)calc_rgb_scaled_pixel_value(
                    stats_g, rgbdata[kk+1]);
                  bdata[kk+2] = (unsigned char)calc_rgb_scaled_pixel_value(
                    stats_b, rgbdata[kk+2]);
                }
                else {
                  bdata[kk] = ignore_red_value;
                  bdata[kk+1] = ignore_green_value;
                  bdata[kk+2] = ignore_blue_value;
                }
            }
        }

        // Update the histogram -- use greyscale average
        for (i=0; i<tsy; ++i) {
            for (j=0; j<tsx; ++j) {
                int kk = 3*(j+i*tsx);
                if (!is_ignored_rgb(stats_r, rgbdata[kk]) &&
                    !is_ignored_rgb(stats_g, rgbdata[kk+1]) &&
                    !is_ignored_rgb(stats_b, rgbdata[kk+2]))
                {   
                  unsigned char uval = (bdata[kk]+bdata[kk+1]+bdata[kk+2])/3;
                  stats->hist[uval] += 1;
                }
            }
        }

        free(rgbdata);
    }
    else if (ii->data_ci->data_type == GREYSCALE_BYTE) {

        // this case is very similar to the RGB case, above, except we
        // have to first grab the data into a greyscale buffer, and
        // then copy it over to the 3-band buffer we're supposed to return
        unsigned char *gsdata = MALLOC(sizeof(unsigned char)*tsx*tsy);
        load_thumbnail_data(ii->data_ci, tsx, tsy, (void*)gsdata);
        set_ignores(ii, !g_startup);

        stats->act_max = 0;
        stats->act_min = 255;
        stats->map_min = 0;
        stats->map_max = 255;

        for (i=0; i<tsy; ++i) {
            for (j=0; j<tsx; ++j) {
                unsigned char uval = gsdata[j+i*tsx];

                stats->avg += uval;
                if (uval > stats->act_max) stats->act_max = uval;
                if (uval < stats->act_min) stats->act_min = uval;
            }
        }

        stats->avg /= (double)(tsx*tsy);

        for (i=0; i<tsy; ++i) {
            for (j=0; j<tsx; ++j) {
                unsigned char uval = gsdata[j+i*tsx];
                stats->stddev += (uval - stats->avg) * (uval - stats->avg);
            }
        }
        stats->stddev = sqrt(stats->stddev / (double)(tsx*tsy));

        set_mapping(ii, !g_startup);

        for (i=0; i<tsy; ++i) {
            for (j=0; j<tsx; ++j) {
                unsigned char uval = gsdata[j+i*tsx];
                int kk = 3*(j+i*tsx);

                if (have_lut()) {
                    apply_lut(uval, &bdata[kk], &bdata[kk+1], &bdata[kk+2]);
                    stats->hist[uval] += 1;
                }
                else if (!is_ignored(stats, uval)) {
                    // apply selected scaling to display values
                    unsigned char display_value = (unsigned char)
                        calc_scaled_pixel_value(stats, uval);
                    bdata[kk] = bdata[kk+1] = bdata[kk+2] = display_value;
                    stats->hist[display_value] += 1;
                }
                else {
                    bdata[kk] = bdata[kk+1] = bdata[kk+2] = ignore_grey_value;
                }
            }
        }

        free(gsdata);
    }
    else if (ii->data_ci->data_type == RGB_FLOAT) {
        // store data used to build the small image pixmap
        // we will calculate the stats on this subset
        float *fdata = MALLOC(sizeof(float)*tsx*tsy*3);
        load_thumbnail_data(ii->data_ci, tsx, tsy, fdata);
        set_ignores(ii, !g_startup);

        ImageStatsRGB *stats_r = &ii->stats_r;
        ImageStatsRGB *stats_g = &ii->stats_g;
        ImageStatsRGB *stats_b = &ii->stats_b;

        stats_r->act_min = fdata[0];
        stats_r->act_max = fdata[0];

        stats_g->act_min = fdata[1];
        stats_g->act_max = fdata[1];

        stats_b->act_min = fdata[2];
        stats_b->act_max = fdata[2];

        int nr=0, ng=0, nb=0;
        for (i=0; i<tsy; ++i) {
            for (j=0; j<tsx; ++j) {
                int kk = 3*(j+i*tsx);

                if (!is_ignored_rgb(stats_r, fdata[kk])) {
                    stats_r->avg += fdata[kk];
                    ++nr;

                    if (fdata[kk] < stats_r->act_min)
                      stats_r->act_min = fdata[kk];
                    if (fdata[kk] > stats_r->act_max)
                      stats_r->act_max = fdata[kk];
                }

                if (!is_ignored_rgb(stats_g, fdata[kk+1])) {
                    stats_g->avg += fdata[kk+1];
                    ++ng;

                    if (fdata[kk+1] < stats_g->act_min)
                      stats_g->act_min = fdata[kk+1];
                    if (fdata[kk+1] > stats_g->act_max)
                      stats_g->act_max = fdata[kk+1];
                }

                if (!is_ignored_rgb(stats_b, fdata[kk+2])) {
                    stats_b->avg += fdata[kk+2];
                    ++nb;

                    if (fdata[kk+2] < stats_b->act_min)
                      stats_b->act_min = fdata[kk+2];
                    if (fdata[kk+2] > stats_b->act_max)
                      stats_b->act_max = fdata[kk+2];
                }
            }
        }
        
        if (nr>1) stats_r->avg /= (double)nr;
        if (ng>1) stats_g->avg /= (double)ng;
        if (nb>1) stats_b->avg /= (double)nb;

        for (i=0; i<tsy; ++i) {
            for (j=0; j<tsx; ++j) {
                int kk = 3*(j+i*tsx);

                if (!is_ignored_rgb(stats_r, fdata[kk])) {
                    float d = fdata[kk] - stats_r->avg;
                    stats_r->stddev += d*d;
                }

                if (!is_ignored_rgb(stats_g, fdata[kk+1])) {
                    float d = fdata[kk+1] - stats_g->avg;
                    stats_g->stddev += d*d;
                }

                if (!is_ignored_rgb(stats_b, fdata[kk+2])) {
                    float d = fdata[kk+2] - stats_b->avg;
                    stats_b->stddev += d*d;
                }
            }
        }

        stats_r->stddev = sqrt(stats_r->stddev / (double)nr);
        stats_g->stddev = sqrt(stats_g->stddev / (double)ng);
        stats_b->stddev = sqrt(stats_b->stddev / (double)nb);

        set_mapping(ii, !g_startup);

        // clear out the stats for the greyscale image - we'll just use
        // the histogram
        stats->avg = 0;
        stats->stddev = 0;

	// these are used for the axis labels on the histogram, so we put
	// in the averages of the mins... these are sort of bogus anyway, we
	// really should have 3 histograms
        stats->map_min = calc_fake_min(ii);
	stats->map_max = calc_fake_max(ii);

        for (i=0; i<tsy; ++i) {
            for (j=0; j<tsx; ++j) {
                int kk = 3*(j+i*tsx);

                if (!is_ignored_rgb(stats_r, fdata[kk]) &&
                    !is_ignored_rgb(stats_g, fdata[kk+1]) &&
                    !is_ignored_rgb(stats_b, fdata[kk+2]))
                {
                  bdata[kk] = (unsigned char)calc_rgb_scaled_pixel_value(
                    stats_r, fdata[kk]);
                  bdata[kk+1] = (unsigned char)calc_rgb_scaled_pixel_value(
                    stats_g, fdata[kk+1]);
                  bdata[kk+2] = (unsigned char)calc_rgb_scaled_pixel_value(
                    stats_b, fdata[kk+2]);
                }
                else {
                  bdata[kk] = ignore_red_value;
                  bdata[kk+1] = ignore_green_value;
                  bdata[kk+2] = ignore_blue_value;
                }
            }
        }

        // Update the histogram -- use greyscale average
        for (i=0; i<tsy; ++i) {
            for (j=0; j<tsx; ++j) {
                int kk = 3*(j+i*tsx);
                if (!is_ignored_rgb(stats_r, fdata[kk]) &&
                    !is_ignored_rgb(stats_g, fdata[kk+1]) &&
                    !is_ignored_rgb(stats_b, fdata[kk+2]))
                {   
                  unsigned char uval = (bdata[kk]+bdata[kk+1]+bdata[kk+2])/3;
                  stats->hist[uval] += 1;
                }
            }
        }

        // done with our subset
        free(fdata);
    }
    else {
	    asfPrintError("Unexpected data type: %d!\n",
                          ii->data_ci->data_type);
    }

    return bdata;
}