home *** CD-ROM | disk | FTP | other *** search
- /*
- * This is a plug-in for the GIMP.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- *
- *
- */
-
- /*
- * Analyze colorcube.
- *
- * Author: robert@experimental.net
- */
-
- #include <glib.h>
-
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <sys/stat.h>
- #include "libgimp/gimp.h"
- #include "gtk/gtk.h"
-
- #ifndef HAVE_RINT
- #include <math.h>
- #define rint(x) floor((x) + 0.5)
- #endif
- #ifndef bzero
- #define bzero(p,size) memset(p,0,size)
- #endif
-
- /* 0 = not verbose, rest = verbose */
- #define VERBOSE 0
-
- /* calloc new colornode, and sanitycheck */
- #define CALLOC_COLOR(x) { \
- if ((x = (ColorNode *) calloc(1, sizeof(ColorNode))) == NULL) \
- { \
- printf("Ick, couldn't calloc!()\n"); \
- return; \
- } \
- }
-
- /*
- * I found the following implementation of storing a sparse color matrix
- * in Dr. Dobb's Journal #232 (July 1995).
- *
- * The matrix is build as three linked lists, each representing a color-
- * cube axis. Each node in the matrix contains two pointers: one to its
- * neighbour and one to the next color-axis.
- *
- * Each red node contains a pointer to the next red node, and a pointer to
- * the green nodes. Green nodes, in turn, each contain a pointer to the next
- * green node, and a pointer to the blue axis.
- *
- * If we want to find an RGB triplet, we first walk down the red axis, match
- * the red values, from where we start walking down the green axis, etc.
- * If we haven't found our color at the end of the blue axis, it's a new color
- * and we store it in the matrix.
- *
- * For the textual-impaired (number in parentheses are color values):
- *
- * start of table
- * |
- * v
- * RED(91) -> RED(212) -> ...
- * | |
- * | v
- * | GREEN(81) -> GREEN(128) -> ...
- * | | |
- * | | v
- * | | BLUE(93)
- * | v
- * | BLUE(206) -> BLUE(93) -> ...
- * v
- * GREEN(1) -> ...
- * |
- * v
- * BLUE(206) -> BLUE(12) -> ...
- *
- * So, some colors stored are (in RGB triplets): (91, 1, 206), (91, 1, 12),
- * (212, 128, 93), ...
- *
- */
- typedef enum { RED, GREEN, BLUE } ColorType;
-
- typedef struct ColorNode
- {
- struct ColorNode *next_neighbour;
- struct ColorNode *next_axis;
- ColorType color;
- unsigned int r;
- unsigned int g;
- unsigned int b;
- int count;
- } ColorNode;
-
- typedef struct
- {
- unsigned char r;
- unsigned char g;
- unsigned char b;
- } Color;
-
- /* lets prototype */
- static void query();
- static void run(char *, int, GParam *, int *, GParam **);
- static int doDialog();
- static void analyze(GDrawable *);
-
- static void histogram(guchar, guchar, guchar);
- static void fillPreview(GtkWidget *);
- static void insertcolor(guchar, guchar, guchar);
-
- static void doLabel(GtkWidget *, char *);
-
- static void ok_callback(GtkWidget *, gpointer);
- static void close_callback(GtkWidget *, gpointer);
-
- /* some global variables */
- char *filename = NULL;
- int running = 0, width, height, bpp;
- const int verbose = VERBOSE;
- ColorNode *table = NULL;
- char *inputname;
- gint32 hist_red[256], hist_green[256], hist_blue[256];
- int maxred = 0, maxgreen = 0, maxblue = 0;
- int uniques = 0;
- gint32 imageID;
-
- /* size of histogram image */
- static const int PREWIDTH = 256;
- static const int PREWIDTH3 = 768;
- static const int PREHEIGHT = 150;
-
- /* lets declare what we want to do */
- GPlugInInfo PLUG_IN_INFO =
- {
- NULL, /* init_proc */
- NULL, /* quit_proc */
- query, /* query_proc */
- run, /* run_proc */
- };
-
- /* run program */
- MAIN();
-
- /* tell GIMP who we are */
- static
- void query()
- {
- static GParamDef args[] =
- {
- { PARAM_INT32, "run_mode", "Interactive" },
- { PARAM_IMAGE, "image", "Input image" },
- { PARAM_DRAWABLE, "drawable", "Input drawable" },
- };
- static GParamDef *return_vals = NULL;
- static int nargs = sizeof(args) / sizeof(args[0]);
- static int nreturn_vals = 0;
-
- gimp_install_procedure("plug_in_ccanalyze",
- "Colorcube analysis",
- "Analyze colorcube and print some information about the current image (also displays a color-histogram)",
- "robert@experimental.net",
- "robert@experimental.net",
- "June 20th, 1997",
- "<Image>/Image/Colors/Colorcube analysis",
- "RGB*, INDEXED*, GRAY*",
- PROC_PLUG_IN,
- nargs, nreturn_vals,
- args, return_vals);
- }
-
- /* main function */
- static
- void run(char *name, int nparams, GParam *param, int *nreturn_vals, GParam **return_vals)
- {
- static GParam values[1];
- GDrawable *drawable;
- GStatusType status = STATUS_SUCCESS;
-
- *nreturn_vals = 1;
- *return_vals = values;
-
- values[0].type = PARAM_STATUS;
- values[0].data.d_status = status;
-
- drawable = gimp_drawable_get(param[2].data.d_drawable);
- imageID = param[1].data.d_image;
-
- /* not much use of running this in non-interactive mode */
- if (param[0].data.d_int32 == RUN_NONINTERACTIVE)
- {
- status = STATUS_EXECUTION_ERROR;
- return;
- }
-
- if (status == STATUS_SUCCESS)
- {
- if (gimp_drawable_color(drawable->id) || gimp_drawable_is_indexed(drawable->id))
- {
- bzero(hist_red, sizeof(hist_red));
- bzero(hist_green, sizeof(hist_green));
- bzero(hist_blue, sizeof(hist_blue));
- filename = gimp_image_get_filename(imageID);
- gimp_progress_init("Colorcube analysis...");
- gimp_tile_cache_ntiles(2 * (drawable->width / gimp_tile_width() + 1));
- analyze(drawable);
- /* show dialog after we analyzed image */
- doDialog();
- }
- else
- status = STATUS_EXECUTION_ERROR;
- }
- values[0].data.d_status = status;
- gimp_drawable_detach(drawable);
- }
-
- /* do the analyzing */
- static
- void analyze(GDrawable *drawable)
- {
- GPixelRgn srcPR;
- guchar *src_row, *cmap;
- gint x, y, numcol;
- gint x1, y1, x2, y2;
-
- /*
- * Get the input area. This is the bounding box of the selection in
- * the image (or the entire image if there is no selection). Only
- * operating on the input area is simply an optimization. It doesn't
- * need to be done for correct operation. (It simply makes it go
- * faster, since fewer pixels need to be operated on).
- */
- gimp_drawable_mask_bounds(drawable->id, &x1, &y1, &x2, &y2);
-
- /*
- * Get the size of the input image (this will/must be the same
- * as the size of the output image).
- */
- width = drawable->width;
- height = drawable->height;
- bpp = drawable->bpp;
-
- /* allocate row buffer */
- src_row = (guchar *) malloc((x2 - x1) * bpp);
-
- /* initialize the pixel region */
- gimp_pixel_rgn_init(&srcPR, drawable, 0, 0, width, height, FALSE, FALSE);
-
- cmap = gimp_image_get_cmap(imageID, &numcol);
- for (y = y1; y < y2; y++)
- {
- gimp_pixel_rgn_get_row(&srcPR, src_row, x1, y, (x2 - x1));
- for (x = x1; x < x2; x++)
- {
- guchar red, green, blue;
-
- /*
- * If the image is indexed, fetch RGB values
- * from colormap.
- */
- if (cmap)
- {
- guchar idx = src_row[x];
-
- red = cmap[idx * 3];
- green = cmap[idx * 3 + 1];
- blue = cmap[idx * 3 + 2];
- }
- else
- {
- red = src_row[x * bpp];
- green = src_row[x * bpp + 1];
- blue = src_row[x * bpp + 2];
- }
- insertcolor(red, green, blue);
- }
- /* tell the user what we're doing */
- if ((y % 10) == 0)
- gimp_progress_update((double) y / (double) (y2 - y1));
- }
- /* clean up */
- free(src_row);
- }
-
- /* here's were we actually store our color-table */
- static
- void insertcolor(guchar r, guchar g, guchar b)
- {
- ColorNode *node, *next = NULL, *prev = NULL,
- *newred, *newgreen = NULL, *newblue;
- ColorType type = RED;
-
- histogram(r, g, b);
- /* lets walk the tree, and see if it already contains this color */
- for (node = table; node != NULL; prev = node, node = next)
- {
- if (node->color == RED)
- {
- if (node->r == r)
- {
- type = GREEN;
- next = node->next_axis;
- }
- else
- {
- type = RED;
- next = node->next_neighbour;
- }
- continue;
- }
- if (node->color == GREEN)
- {
- if (node->g == g)
- {
- type = BLUE;
- next = node->next_axis;
- }
- else
- {
- type = GREEN;
- next = node->next_neighbour;
- }
- continue;
- }
- if (node->color == BLUE)
- {
- /* found it! */
- if (node->b == b)
- break;
- else
- {
- type = BLUE;
- next = node->next_neighbour;
- }
- }
- }
- /* this color was already stored -> update it's count */
- if (node)
- {
- node->count++;
- return;
- }
- /* New color! */
- /* first, create blue node */
- CALLOC_COLOR(newblue);
- newblue->color = BLUE;
- /* no neighbours or links to another axis */
- newblue->next_neighbour = newblue->next_axis = NULL;
- /*
- * At the end of the list, we store the entire triplet.
- * For now, there is no reason whatsoever to do this, but perhaps
- * it might prove useful someday :)
- */
- newblue->r = r;
- newblue->g = g;
- newblue->b = b;
- newblue->count = 1;
- /* previous was green: create link to axis */
- if (prev && prev->color == GREEN && type == BLUE)
- prev->next_axis = newblue;
- /* previous was blue: create link to neighbour */
- if (prev && prev->color == BLUE && type == BLUE)
- prev->next_neighbour = newblue;
-
- /* green node */
- if (type == GREEN || type == RED)
- {
- CALLOC_COLOR(newgreen);
- newgreen->color = GREEN;
- newgreen->next_neighbour = NULL;
- newgreen->next_axis = newblue;
- newgreen->g = g;
- /* count doesn't matter here */
- newgreen->count = -1;
- /* previous was red: create link to axis */
- if (prev && prev->color == RED && type == GREEN)
- prev->next_axis = newgreen;
- /* previous was green: create link to neighbour */
- if (prev && prev->color == GREEN && type == GREEN)
- prev->next_neighbour = newgreen;
- }
-
- /* red node */
- if (type == RED)
- {
- CALLOC_COLOR(newred);
- newred->color = RED;
- newred->next_neighbour = NULL;
- newred->next_axis = newgreen;
- newred->r = r;
- /* count doesn't matter here */
- newred->count = -1;
- /* previous was red, update its neighbour link */
- if (prev)
- prev->next_neighbour = newred;
- else
- table = newred;
- }
-
- /* increase the number of unique colors */
- uniques++;
- }
-
- /*
- * Update RGB count, and keep track of maximum values (which aren't used
- * anywhere as of yet, but they might be useful sometime).
- */
- void histogram(guchar r, guchar g, guchar b)
- {
- if (++hist_red[r] > maxred)
- maxred = hist_red[r];
- if (++hist_green[g] > maxgreen)
- maxgreen = hist_green[g];
- if (++hist_blue[b] > maxblue)
- maxblue = hist_blue[b];
- }
-
- /* show our results */
- static
- int doDialog()
- {
- struct stat st;
- GtkWidget *dialog;
- GtkWidget *button;
- GtkWidget *frame;
- GtkWidget *xframe;
- GtkWidget *table;
- GtkWidget *preview;
- guchar *color_cube;
- gchar **argv;
- gint argc;
- int filesize = 0;
- char buf[512], *c;
-
- argc = 1;
- argv = g_new(gchar *, 1);
- argv[0] = g_strdup("ccanalyze");
-
- gtk_init(&argc, &argv);
-
- gtk_preview_set_gamma(gimp_gamma());
- gtk_preview_set_install_cmap(gimp_install_cmap());
- color_cube = gimp_color_cube();
- gtk_preview_set_color_cube(color_cube[0], color_cube[1], color_cube[2],
- color_cube[3]);
- gtk_widget_set_default_visual(gtk_preview_get_visual());
- gtk_widget_set_default_colormap(gtk_preview_get_cmap());
-
-
- /* set up the dialog */
- dialog = gtk_dialog_new();
- gtk_window_set_title(GTK_WINDOW(dialog), "Colorcube analysis");
- gtk_window_position(GTK_WINDOW(dialog), GTK_WIN_POS_MOUSE);
- gtk_signal_connect(GTK_OBJECT(dialog), "destroy",
- (GtkSignalFunc) close_callback,
- NULL);
-
- /* lets create some buttons */
- button = gtk_button_new_with_label("Ok");
- GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
- gtk_signal_connect(GTK_OBJECT(button), "clicked",
- (GtkSignalFunc) ok_callback,
- dialog);
- gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->action_area),
- button, TRUE, TRUE, 0);
- gtk_widget_grab_default(button);
- gtk_widget_show(button);
-
- /* set up frame */
- frame = gtk_frame_new("Results");
- gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_ETCHED_IN);
- gtk_container_border_width(GTK_CONTAINER(frame), 10);
- gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox),
- frame, TRUE, TRUE, 0);
- table = gtk_table_new(12, 1, FALSE);
- gtk_container_border_width(GTK_CONTAINER(table), 10);
- gtk_container_add(GTK_CONTAINER(frame), table);
-
- /* use preview for histogram window */
- preview = gtk_preview_new(GTK_PREVIEW_COLOR);
- gtk_preview_size(GTK_PREVIEW(preview), PREWIDTH3, PREHEIGHT);
- xframe = gtk_frame_new(NULL);
- gtk_frame_set_shadow_type(GTK_FRAME(xframe), GTK_SHADOW_IN);
- gtk_container_add(GTK_CONTAINER(xframe), preview);
- fillPreview(preview);
- gtk_widget_show(preview);
- gtk_widget_show(xframe);
- gtk_table_attach(GTK_TABLE(table), xframe, 0, 1, 0, 1, GTK_EXPAND, GTK_EXPAND, 0, 0);
- gtk_widget_show(frame);
-
- /* output results */
- sprintf(buf, "Name: %s", filename);
- doLabel(table, buf);
- sprintf(buf, "Image dimensions: %ux%u", width, height);
- doLabel(table, buf);
- sprintf(buf, "Uncompressed size in bytes: %u", width * height * bpp);
- doLabel(table, buf);
- if (filename && !stat(filename, &st))
- {
- filesize = st.st_size;
- sprintf(buf, "Compressed size in bytes: %u", filesize);
- doLabel(table, buf);
- sprintf(buf, "Compression ratio (approx.): %u to 1", (int) rint((double) (width * height * bpp) / filesize));
- doLabel(table, buf);
- }
- if (!uniques)
- strcpy(buf, "No colors (??)");
- else
- if (uniques == 1)
- strcpy(buf, "Only one unique color");
- else
- sprintf(buf, "Number of unique colors: %u", uniques);
- doLabel(table, buf);
-
- /* show stuff */
- gtk_widget_show(table);
- gtk_widget_show(dialog);
- gtk_main();
- gdk_flush();
-
- return running;
- }
-
- /* shortcut */
- static
- void doLabel(GtkWidget *table, char *text)
- {
- static int idx = 1;
- GtkWidget *label;
-
- label = gtk_label_new(text);
- gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
- gtk_table_attach(GTK_TABLE(table), label, 0, 1, idx, idx + 1, GTK_FILL, 0, 5, 0);
- gtk_widget_show(label);
- idx += 2;
- }
-
- /* fill our preview image with the color-histogram */
- static
- void fillPreview(GtkWidget *preview)
- {
- guchar *image = g_malloc(PREWIDTH3 * PREHEIGHT * 3 * sizeof(guchar)),
- *pimage;
- int x, y, rowstride;
-
- if (!image)
- {
- g_warning("Ick, couldn't malloc() for preview!\n");
- return;
- }
- bzero(image, PREWIDTH3 * PREHEIGHT * 3 * sizeof(guchar));
- rowstride = PREWIDTH3 * 3;
- for (x = 0; x < PREWIDTH; x++)
- {
- int histcount, val;
-
- /*
- * For every channel, calculate a logarithmic value, scale it,
- * and build a one-pixel bar.
- */
- histcount = hist_red[x] ? hist_red[x] : 1;
- val = (int) (log((double) histcount) * (PREHEIGHT / 12));
- if (val > PREHEIGHT)
- val = PREHEIGHT;
- for (y = PREHEIGHT - 1; y > (PREHEIGHT - val); y--)
- {
- guchar *pixel = image + (x * 3) + (y * rowstride);
-
- *pixel = 255;
- *(pixel + 1) = 0;
- *(pixel + 2) = 0;
- }
-
- histcount = hist_green[x] ? hist_green[x] : 1;
- val = (int) (log((double) histcount) * (PREHEIGHT / 12));
- if (val > PREHEIGHT)
- val = PREHEIGHT;
- for (y = PREHEIGHT - 1; y > (PREHEIGHT - val); y--)
- {
- guchar *pixel = image + ((x + PREWIDTH) * 3) + (y * rowstride);
-
- *pixel = 0;
- *(pixel + 1) = 255;
- *(pixel + 2) = 0;
- }
-
- histcount = hist_blue[x] ? hist_blue[x] : 1;
- val = (int) (log((double) histcount) * (PREHEIGHT / 12));
- if (val > PREHEIGHT)
- val = PREHEIGHT;
- for (y = PREHEIGHT - 1; y > (PREHEIGHT - val); y--)
- {
- guchar *pixel = image + ((x + 2 * PREWIDTH) * 3) + (y * rowstride);
-
- *pixel = 0;
- *(pixel + 1) = 0;
- *(pixel + 2) = 255;
- }
- }
- /* move our data into the preview image */
- for (pimage = image, y = 0; y < PREHEIGHT; y++)
- {
- gtk_preview_draw_row(GTK_PREVIEW(preview), pimage, 0, y, PREWIDTH3);
- pimage += 3 * PREWIDTH3;
- }
- free(image);
- }
-
- static
- void ok_callback(GtkWidget *widget, gpointer data)
- {
- running = 1;
- gtk_widget_destroy(GTK_WIDGET(data));
- }
-
- static
- void close_callback(GtkWidget *widget, gpointer data)
- {
- gtk_main_quit();
- }
-