home *** CD-ROM | disk | FTP | other *** search
- /*
- * file: hot/hot.c
- *
- * $Id: hot.c,v 1.17 2000/08/22 01:26:54 neo Exp $
- */
-
- /*
- * hot.c - Scan an image for pixels with RGB values that will give
- * "unsafe" values of chrominance signal or composite signal
- * amplitude when encoded into an NTSC or PAL colour signal.
- * (This happens for certain high-intensity high-saturation colours
- * that are rare in real scenes, but can easily be present
- * in synthetic images.)
- *
- * Such pixels can be flagged so the user may then choose other
- * colours. Or, the offending pixels can be made "safe"
- * in a manner that preserves hue.
- *
- * There are two reasonable ways to make a pixel "safe":
- * We can reduce its intensity (luminance) while leaving
- * hue and saturation the same. Or, we can reduce saturation
- * while leaving hue and luminance the same. A #define selects
- * which strategy to use.
- *
- * Note to the user: You must add your own read_pixel() and write_pixel()
- * routines. You may have to modify pix_decode() and pix_encode().
- * MAXPIX, WID, and HGT are likely to need modification.
- */
-
- /*
- * Originally written as "ikNTSC.c" by Alan Wm Paeth,
- * University of Waterloo, August, 1985
- * Updated by Dave Martindale, Imax Systems Corp., December 1990
- */
-
- /*
- * Compile time options:
- *
- *
- * CHROMA_LIM is the limit (in IRE units) of the overall
- * chrominance amplitude; it should be 50 or perhaps
- * very slightly higher.
- *
- * COMPOS_LIM is the maximum amplitude (in IRE units) allowed for
- * the composite signal. A value of 100 is the maximum
- * monochrome white, and is always safe. 120 is the absolute
- * limit for NTSC broadcasting, since the transmitter's carrier
- * goes to zero with 120 IRE input signal. Generally, 110
- * is a good compromise - it allows somewhat brighter colours
- * than 100, while staying safely away from the hard limit.
- */
-
- /*
- * run-time options:
- *
- * Define either NTSC or PAL as 1 to select the colour system.
- * Define the other one as zero, or leave it undefined.
- *
- * Define FLAG_HOT as 1 if you want "hot" pixels set to black
- * to identify them. Otherwise they will be made safe.
- *
- * Define REDUCE_SAT as 1 if you want hot pixels to be repaired by
- * reducing their saturation. By default, luminance is reduced.
- *
- */
-
- #include "config.h"
-
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
-
- #include <gtk/gtk.h>
-
- #include <libgimp/gimp.h>
- #include <libgimp/gimpui.h>
-
- #include "libgimp/stdplugins-intl.h"
-
-
- struct Grgb
- {
- guint8 red;
- guint8 green;
- guint8 blue;
- };
-
- struct GRegion
- {
- gint32 x;
- gint32 y;
- gint32 width;
- gint32 height;
- };
-
- struct piArgs
- {
- gint32 image;
- gint32 drawable;
- gint32 mode;
- gint32 action;
- gint32 new_layerp;
- };
-
- typedef enum
- {
- act_lredux = 0,
- act_sredux = 1,
- act_flag = 2
- } hotAction;
-
- typedef enum
- {
- mode_ntsc = 0,
- mode_pal = 1
- } hotModes;
-
- #define CHROMA_LIM 50.0 /* chroma amplitude limit */
- #define COMPOS_LIM 110.0 /* max IRE amplitude */
-
- /*
- * RGB to YIQ encoding matrix.
- */
-
- struct
- {
- gdouble pedestal;
- gdouble gamma;
- gdouble code[3][3];
- } mode[2] = {
- {
- 7.5,
- 2.2,
- {
- { 0.2989, 0.5866, 0.1144 },
- { 0.5959, -0.2741, -0.3218 },
- { 0.2113, -0.5227, 0.3113 }
- }
- },
- {
- 0.0,
- 2.8,
- {
- { 0.2989, 0.5866, 0.1144 },
- { -0.1473, -0.2891, 0.4364 },
- { 0.6149, -0.5145, -0.1004 }
- }
- }
- };
-
-
- #define SCALE 8192 /* scale factor: do floats with int math */
- #define MAXPIX 255 /* white value */
-
- static gint tab[3][3][MAXPIX+1]; /* multiply lookup table */
- static gdouble chroma_lim; /* chroma limit */
- static gdouble compos_lim; /* composite amplitude limit */
- static glong ichroma_lim2; /* chroma limit squared (scaled integer) */
- static gint icompos_lim; /* composite amplitude limit (scaled integer) */
-
- static void query (void);
- static void run (gchar *name,
- gint nparam,
- GimpParam *param,
- gint *nretvals,
- GimpParam **retvals);
-
- static gint pluginCore (struct piArgs *argp);
- static gint pluginCoreIA (struct piArgs *argp);
- static gint hotp (register guint8 r,
- register guint8 g,
- register guint8 b);
- static void build_tab (int m);
-
- /*
- * gc: apply the gamma correction specified for this video standard.
- * inv_gc: inverse function of gc.
- *
- * These are generally just a call to pow(), but be careful!
- * Future standards may use more complex functions.
- * (e.g. SMPTE 240M's "electro-optic transfer characteristic").
- */
- #define gc(x,m) pow(x, 1.0 / mode[m].gamma)
- #define inv_gc(x,m) pow(x, mode[m].gamma)
-
- /*
- * pix_decode: decode an integer pixel value into a floating-point
- * intensity in the range [0, 1].
- *
- * pix_encode: encode a floating-point intensity into an integer
- * pixel value.
- *
- * The code given here assumes simple linear encoding; you must change
- * these routines if you use a different pixel encoding technique.
- */
- #define pix_decode(v) ((double)v / (double)MAXPIX)
- #define pix_encode(v) ((int)(v * (double)MAXPIX + 0.5))
-
- GimpPlugInInfo PLUG_IN_INFO =
- {
- NULL, /* init_proc */
- NULL, /* quit_proc */
- query, /* query_proc */
- run, /* run_proc */
- };
-
- MAIN ()
-
- static void
- query (void)
- {
- static GimpParamDef args[] =
- {
- { GIMP_PDB_INT32, "run_mode", "Interactive, non-interactive" },
- { GIMP_PDB_IMAGE, "image", "The Image" },
- { GIMP_PDB_DRAWABLE, "drawable", "The Drawable" },
- { GIMP_PDB_INT32, "mode", "Mode -- NTSC/PAL" },
- { GIMP_PDB_INT32, "action", "The action to perform" },
- { GIMP_PDB_INT32, "new_layerp", "Create a new layer iff True" }
- };
- static gint nargs = sizeof (args) / sizeof (args[0]);
-
- gimp_install_procedure ("plug_in_hot",
- "Look for hot NTSC or PAL pixels ",
- "hot scans an image for pixels that will give unsave "
- "values of chrominance or composite signale "
- "amplitude when encoded into an NTSC or PAL signal. "
- "Three actions can be performed on these ``hot'' "
- "pixels. (0) reduce luminance, (1) reduce "
- "saturation, or (2) Blacken.",
- "Eric L. Hernes, Alan Wm Paeth",
- "Eric L. Hernes",
- "1997",
- N_("<Image>/Filters/Colors/Hot..."),
- "RGB",
- GIMP_PLUGIN,
- nargs, 0,
- args, NULL);
- }
-
- static void
- run (gchar *name,
- gint nparam,
- GimpParam *param,
- gint *nretvals,
- GimpParam **retvals)
- {
- static GimpParam rvals[1];
-
- struct piArgs args;
-
- *nretvals = 1;
- *retvals = rvals;
-
- memset (&args, (int) 0, sizeof (struct piArgs));
- args.mode = -1;
-
- gimp_get_data ("plug_in_hot", &args);
-
- args.image = param[1].data.d_image;
- args.drawable = param[2].data.d_drawable;
-
- rvals[0].type = GIMP_PDB_STATUS;
- rvals[0].data.d_status = GIMP_PDB_SUCCESS;
- switch (param[0].data.d_int32)
- {
- case GIMP_RUN_INTERACTIVE:
- INIT_I18N_UI();
- /* XXX: add code here for interactive running */
- if (args.mode == -1)
- {
- args.mode = mode_ntsc;
- args.action = act_lredux;
- args.new_layerp = 1;
- }
-
- if (pluginCoreIA(&args) == -1)
- {
- rvals[0].data.d_status = GIMP_PDB_EXECUTION_ERROR;
- }
- gimp_set_data ("plug_in_hot", &args, sizeof (struct piArgs));
-
- break;
-
- case GIMP_RUN_NONINTERACTIVE:
- INIT_I18N();
- /* XXX: add code here for non-interactive running */
- if (nparam != 6)
- {
- rvals[0].data.d_status = GIMP_PDB_CALLING_ERROR;
- break;
- }
- args.mode = param[3].data.d_int32;
- args.action = param[4].data.d_int32;
- args.new_layerp = param[5].data.d_int32;
-
- if (pluginCore(&args) == -1)
- {
- rvals[0].data.d_status = GIMP_PDB_EXECUTION_ERROR;
- break;
- }
- break;
-
- case GIMP_RUN_WITH_LAST_VALS:
- INIT_I18N();
- /* XXX: add code here for last-values running */
- if (pluginCore (&args) == -1)
- {
- rvals[0].data.d_status = GIMP_PDB_EXECUTION_ERROR;
- }
- break;
- }
- }
-
- static gint
- pluginCore (struct piArgs *argp)
- {
- GimpDrawable *drw, *ndrw=NULL;
- GimpPixelRgn srcPr, dstPr;
- gint retval = 0;
- gint nl=0;
- gint y, x, i;
- gint Y, I, Q;
- guint width, height, Bpp;
- gint prog_interval;
- guchar *src, *s, *dst, *d;
- guchar r, prev_r=0, new_r=0;
- guchar g, prev_g=0, new_g=0;
- guchar b, prev_b=0, new_b=0;
- gdouble fy, fc, t, scale;
- gdouble pr, pg, pb;
- gdouble py;
-
- drw = gimp_drawable_get (argp->drawable);
-
- width = drw->width;
- height = drw->height;
- Bpp = drw->bpp;
- if (argp->new_layerp)
- {
- gchar name[40];
- gchar *mode_names[] =
- {
- "ntsc",
- "pal",
- };
- gchar *action_names[] =
- {
- "lum redux",
- "sat redux",
- "flag",
- };
-
- g_snprintf (name, sizeof (name), "hot mask (%s, %s)",
- mode_names[argp->mode],
- action_names[argp->action]);
-
- nl = gimp_layer_new (argp->image, name, width, height,
- GIMP_RGBA_IMAGE, (gdouble)100, GIMP_NORMAL_MODE);
- ndrw = gimp_drawable_get (nl);
- gimp_drawable_fill (nl, GIMP_TRANS_IMAGE_FILL);
- gimp_image_add_layer (argp->image, nl, 0);
- }
-
- src = g_new (guchar, width * height * Bpp);
- dst = g_new (guchar, width * height * 4);
- gimp_pixel_rgn_init (&srcPr, drw, 0, 0, width, height, FALSE, FALSE);
- if (argp->new_layerp)
- {
- gimp_pixel_rgn_init (&dstPr, ndrw, 0, 0, width, height, FALSE, FALSE);
- }
- else
- {
- gimp_pixel_rgn_init (&dstPr, drw, 0, 0, width, height, TRUE, TRUE);
- }
-
- gimp_pixel_rgn_get_rect (&srcPr, src, 0, 0, width, height);
-
- s = src;
- d = dst;
-
- build_tab (argp->mode);
-
- gimp_progress_init (_("Hot"));
- prog_interval = height / 10;
-
- for (y = 0; y < height; y++)
- {
- if (y % prog_interval == 0)
- gimp_progress_update ((double) y / (double) height);
- for (x = 0; x < width; x++)
- {
- if (hotp (r = *(s + 0), g = *(s + 1), b = *(s + 2)))
- {
- if (argp->action == act_flag)
- {
- for (i = 0; i < 3; i++)
- *d++ = 0;
- s += 3;
- if (Bpp == 4)
- *d++ = *s++;
- else if (argp->new_layerp)
- *d++ = 255;
- }
- else
- {
- /*
- * Optimization: cache the last-computed hot pixel.
- */
- if (r == prev_r && g == prev_g && b == prev_b)
- {
- *d++ = new_r;
- *d++ = new_g;
- *d++ = new_b;
- s += 3;
- if (Bpp == 4)
- *d++ = *s++;
- else if (argp->new_layerp)
- *d++ = 255;
- }
- else
- {
- Y = tab[0][0][r] + tab[0][1][g] + tab[0][2][b];
- I = tab[1][0][r] + tab[1][1][g] + tab[1][2][b];
- Q = tab[2][0][r] + tab[2][1][g] + tab[2][2][b];
-
- prev_r = r;
- prev_g = g;
- prev_b = b;
- /*
- * Get Y and chroma amplitudes in floating point.
- *
- * If your C library doesn't have hypot(), just use
- * hypot(a,b) = sqrt(a*a, b*b);
- *
- * Then extract linear (un-gamma-corrected)
- * floating-point pixel RGB values.
- */
- fy = (double)Y / (double)SCALE;
- fc = hypot ((double) I / (double) SCALE,
- (double) Q / (double) SCALE);
-
- pr = (double) pix_decode (r);
- pg = (double) pix_decode (g);
- pb = (double) pix_decode (b);
-
- /*
- * Reducing overall pixel intensity by scaling R,
- * G, and B reduces Y, I, and Q by the same factor.
- * This changes luminance but not saturation, since
- * saturation is determined by the chroma/luminance
- * ratio.
- *
- * On the other hand, by linearly interpolating
- * between the original pixel value and a grey
- * pixel with the same luminance (R=G=B=Y), we
- * change saturation without affecting luminance.
- */
- if (argp->action == act_lredux)
- {
- /*
- * Calculate a scale factor that will bring the pixel
- * within both chroma and composite limits, if we scale
- * luminance and chroma simultaneously.
- *
- * The calculated chrominance reduction applies
- * to the gamma-corrected RGB values that are
- * the input to the RGB-to-YIQ operation.
- * Multiplying the original un-gamma-corrected
- * pixel values by the scaling factor raised to
- * the "gamma" power is equivalent, and avoids
- * calling gc() and inv_gc() three times each. */
- scale = chroma_lim / fc;
- t = compos_lim / (fy + fc);
- if (t < scale)
- scale = t;
- scale = pow (scale, mode[argp->mode].gamma);
-
- r = (guint8) pix_encode (scale * pr);
- g = (guint8) pix_encode (scale * pg);
- b = (guint8) pix_encode (scale * pb);
- }
- else
- { /* act_sredux hopefully */
- /*
- * Calculate a scale factor that will bring the
- * pixel within both chroma and composite
- * limits, if we scale chroma while leaving
- * luminance unchanged.
- *
- * We have to interpolate gamma-corrected RGB
- * values, so we must convert from linear to
- * gamma-corrected before interpolation and then
- * back to linear afterwards.
- */
- scale = chroma_lim / fc;
- t = (compos_lim - fy) / fc;
- if (t < scale)
- scale = t;
-
- pr = gc (pr, argp->mode);
- pg = gc (pg, argp->mode);
- pb = gc (pb, argp->mode);
- py = pr * mode[argp->mode].code[0][0] + pg *
- mode[argp->mode].code[0][1] + pb *
- mode[argp->mode].code[0][2];
- r = pix_encode (inv_gc (py + scale * (pr - py),
- argp->mode));
- g = pix_encode (inv_gc (py + scale * (pg - py),
- argp->mode));
- b = pix_encode (inv_gc (py + scale * (pb - py),
- argp->mode));
- }
- *d++ = new_r = r;
- *d++ = new_g = g;
- *d++ = new_b = b;
- s += 3;
- if (Bpp == 4)
- *d++ = *s++;
- else if (argp->new_layerp)
- *d++ = 255;
- }
- }
- }
- else
- {
- if (!argp->new_layerp)
- {
- for (i = 0; i < Bpp; i++)
- *d++ = *s++;
- }
- else
- {
- s += Bpp;
- d += 4;
- }
- }
- }
- }
- gimp_pixel_rgn_set_rect (&dstPr, dst, 0, 0, width, height);
-
- free (src);
- free (dst);
-
- if (argp->new_layerp)
- {
- gimp_drawable_flush (ndrw);
- gimp_drawable_update (nl, 0, 0, width, height);
- }
- else
- {
- gimp_drawable_flush (drw);
- gimp_drawable_merge_shadow (drw->id, TRUE);
- gimp_drawable_update (drw->id, 0, 0, width, height);
- }
-
- gimp_displays_flush ();
-
- return retval;
- }
-
- gboolean run_flag = FALSE;
-
- static void
- hot_ok_callback (GtkWidget *widget,
- gpointer data)
- {
- run_flag = TRUE;
-
- gtk_widget_destroy (GTK_WIDGET (data));
- }
-
- static gint
- pluginCoreIA (struct piArgs *argp)
- {
- GtkWidget *dlg;
- GtkWidget *hbox;
- GtkWidget *vbox;
- GtkWidget *toggle;
- GtkWidget *frame;
-
- gimp_ui_init ("hot", FALSE);
-
- dlg = gimp_dialog_new (_("Hot"), "hot",
- gimp_standard_help_func, "filters/hot.html",
- GTK_WIN_POS_MOUSE,
- FALSE, TRUE, FALSE,
-
- _("OK"), hot_ok_callback,
- NULL, NULL, NULL, TRUE, FALSE,
- _("Cancel"), gtk_widget_destroy,
- NULL, 1, NULL, FALSE, TRUE,
-
- NULL);
-
- gtk_signal_connect (GTK_OBJECT (dlg), "destroy",
- GTK_SIGNAL_FUNC (gtk_main_quit),
- NULL);
-
- hbox = gtk_hbox_new (FALSE, 5);
- gtk_container_set_border_width (GTK_CONTAINER (hbox), 5);
- gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dlg)->vbox), hbox, TRUE, TRUE, 0);
- gtk_widget_show (hbox);
-
- vbox = gtk_vbox_new (FALSE, 5);
- gtk_box_pack_start (GTK_BOX (hbox), vbox, TRUE, TRUE, 0);
- gtk_widget_show (vbox);
-
- toggle = gtk_check_button_new_with_label (_("Create New Layer"));
- gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), argp->new_layerp);
- gtk_box_pack_start (GTK_BOX (vbox), toggle, FALSE, FALSE, 0);
- gtk_signal_connect (GTK_OBJECT (toggle), "toggled",
- GTK_SIGNAL_FUNC (gimp_toggle_button_update),
- &argp->new_layerp);
- gtk_widget_show (toggle);
-
- frame = gimp_radio_group_new2 (TRUE, _("Mode"),
- gimp_radio_button_update,
- &argp->mode, (gpointer) argp->mode,
-
- "NTSC", (gpointer) mode_ntsc, NULL,
- "PAL", (gpointer) mode_pal, NULL,
-
- NULL);
-
- gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0);
- gtk_widget_show (frame);
-
- frame =
- gimp_radio_group_new2 (TRUE, _("Action"),
- gimp_radio_button_update,
- &argp->action, (gpointer) argp->action,
-
- _("Reduce Luminance"), (gpointer) act_lredux, NULL,
- _("Reduce Saturation"), (gpointer) act_sredux, NULL,
- _("Blacken"), (gpointer) act_flag, NULL,
-
- NULL);
-
- gtk_box_pack_start (GTK_BOX (hbox), frame, FALSE, FALSE, 0);
- gtk_widget_show (frame);
-
- gtk_widget_show (dlg);
-
- gtk_main ();
- gdk_flush ();
-
- if (run_flag)
- return pluginCore (argp);
- else
- return -1;
- }
-
- /*
- * build_tab: Build multiply lookup table.
- *
- * For each possible pixel value, decode value into floating-point
- * intensity. Then do gamma correction required by the video
- * standard. Scale the result by our fixed-point scale factor.
- * Then calculate 9 lookup table entries for this pixel value.
- *
- * We also calculate floating-point and scaled integer versions
- * of our limits here. This prevents evaluating expressions every pixel
- * when the compiler is too stupid to evaluate constant-valued
- * floating-point expressions at compile time.
- *
- * For convenience, the limits are #defined using IRE units.
- * We must convert them here into the units in which YIQ
- * are measured. The conversion from IRE to internal units
- * depends on the pedestal level in use, since as Y goes from
- * 0 to 1, the signal goes from the pedestal level to 100 IRE.
- * Chroma is always scaled to remain consistent with Y.
- */
- static void
- build_tab (int m)
- {
- register double f;
- register int pv;
-
- for (pv = 0; pv <= MAXPIX; pv++)
- {
- f = (double)SCALE * (double)gc((double)pix_decode(pv),m);
- tab[0][0][pv] = (int)(f * mode[m].code[0][0] + 0.5);
- tab[0][1][pv] = (int)(f * mode[m].code[0][1] + 0.5);
- tab[0][2][pv] = (int)(f * mode[m].code[0][2] + 0.5);
- tab[1][0][pv] = (int)(f * mode[m].code[1][0] + 0.5);
- tab[1][1][pv] = (int)(f * mode[m].code[1][1] + 0.5);
- tab[1][2][pv] = (int)(f * mode[m].code[1][2] + 0.5);
- tab[2][0][pv] = (int)(f * mode[m].code[2][0] + 0.5);
- tab[2][1][pv] = (int)(f * mode[m].code[2][1] + 0.5);
- tab[2][2][pv] = (int)(f * mode[m].code[2][2] + 0.5);
- }
-
- chroma_lim = (double)CHROMA_LIM / (100.0 - mode[m].pedestal);
- compos_lim = ((double)COMPOS_LIM - mode[m].pedestal) /
- (100.0 - mode[m].pedestal);
-
- ichroma_lim2 = (int)(chroma_lim * SCALE + 0.5);
- ichroma_lim2 *= ichroma_lim2;
- icompos_lim = (int)(compos_lim * SCALE + 0.5);
- }
-
- static int
- hotp (register guint8 r,
- register guint8 g,
- register guint8 b)
- {
- register int y, i, q;
- register long y2, c2;
-
- /* fprintf(stderr, "\tr: %d, g: %d, b: %d\n", r, g, b);*/
-
- /*
- * Pixel decoding, gamma correction, and matrix multiplication
- * all done by lookup table.
- *
- * "i" and "q" are the two chrominance components;
- * they are I and Q for NTSC.
- * For PAL, "i" is U (scaled B-Y) and "q" is V (scaled R-Y).
- * Since we only care about the length of the chroma vector,
- * not its angle, we don't care which is which.
- */
- y = tab[0][0][r] + tab[0][1][g] + tab[0][2][b];
- i = tab[1][0][r] + tab[1][1][g] + tab[1][2][b];
- q = tab[2][0][r] + tab[2][1][g] + tab[2][2][b];
-
- /*
- * Check to see if the chrominance vector is too long or the
- * composite waveform amplitude is too large.
- *
- * Chrominance is too large if
- *
- * sqrt(i^2, q^2) > chroma_lim.
- *
- * The composite signal amplitude is too large if
- *
- * y + sqrt(i^2, q^2) > compos_lim.
- *
- * We avoid doing the sqrt by checking
- *
- * i^2 + q^2 > chroma_lim^2
- * and
- * y + sqrt(i^2 + q^2) > compos_lim
- * sqrt(i^2 + q^2) > compos_lim - y
- * i^2 + q^2 > (compos_lim - y)^2
- *
- */
-
- c2 = (long)i * i + (long)q * q;
- y2 = (long)icompos_lim - y;
- y2 *= y2;
- /* fprintf(stderr, "hotp: c2: %d; ichroma_lim2: %d; y2: %d; ",
- c2, ichroma_lim2, y2);*/
-
- if (c2 <= ichroma_lim2 && c2 <= y2)
- { /* no problems */
- /* fprintf(stderr, "nope\n");*/
- return 0;
- }
-
- /* fprintf(stderr, "yup\n");*/
- return 1;
- }
-