Many thanks to all the regulars on #gimp who answered my questions, gave me advice, and bugged me to write this stuff up.
The GIMP is my friend. It makes me happy. :-) Background ---------- I've recently been trying to reproduce really odd film grain to hide image editing. This process has three steps: 1) Extract the old grain. 2) Make it into a tileable pattern. 3) Merge the pattern into a new image. Step (1): To extract the old grain, make a copy of the image, blur it, and compare it to the original. The "flat" areas of the image should now contain some kind of characteristic noise--grains, vertical lines, and so on. (The trick here is the comparsion; more details below.) You may--or may not--want to desaturate your noise! Step (2): To make a pattern tileable, use "Make Seamless", the Resynthesizer & Homogenizer, hand-hacking, or your favorite tricks. Step (3): To merge the pattern back into the image, create a channel of solid noise, mask it as needed, and add it to the underlying channel. Again, the addition is tricky. Addition & Subtraction ---------------------- >From the "side", a cross-section of film noise might look something like this: __ __/ \__ __ \__/ Notice that the noise swings above /and below/ the average... The "Subtract" mode, however, will clip negative values, and loose half the noise, because it's now less than 0: __ __/ \________ The "Difference" mode takes an absolute value, which totally changes the feel of the noise: __ __ __/ \__/ \__ Basically, we need some way to represent "negative" numbers in the GIMP. We can do this by expressing all values relative to (128,128,128). Two Sample Modes ---------------- The attached patch implementes "Grey" addition and subtraction, both as a layer mode and a brush mode. I've tested it with GIMP 1.2.2, but I'd like to hear any comments before porting it to 1.3.x. The names of the transfer modes are highly provisional! Adding Noise to a Clean Photo ----------------------------- To apply a noise pattern to an image, add a new layer above your image, and flood-fill it with noise. Set the transfer mode to "Grey Addition". Your image should now look ugly, which is just what we want. ;-) Next, let's say that you want lots of noise in your midtones, but none in your highlights or shadows. Here's how: 1) Create a copy of your image, and do a wide-radius blur. This should give you an intensity map. 2) Use the curves dialog to make your shadows dark, your midtones white, and your highlights dark. 3) Copy this image to the clipboard, create a layer mask for your noise layer, and paste it in. 4) To increase or decrease the noise in one area, you can paint into the layer mask with the airbrush. If you're lazy, you can use the magic want to select an area, switch to the quick-mask, blur it heavily, switch back, and flood-fill the area with noise. Just set the bucket to "Pattern Fill", "Grey Addition". Feedback welcome. Share and enjoy! Cheers, Eric diff -ur gimp-1.2.2-old/app/apptypes.h gimp-1.2.2/app/apptypes.h --- gimp-1.2.2-old/app/apptypes.h Sat Dec 16 15:33:41 2000 +++ gimp-1.2.2/app/apptypes.h Wed Sep 26 19:18:40 2001 @@ -78,8 +78,15 @@ DIVIDE_MODE, ERASE_MODE, /*< skip >*/ REPLACE_MODE, /*< skip >*/ - ANTI_ERASE_MODE /*< skip >*/ + ANTI_ERASE_MODE, /*< skip >*/ + GREY_ADDITION_MODE, + GREY_SUBTRACTION_MODE } LayerModeEffects; + +/* Is this a regular brush mode? */ +#define GIMP_IS_BRUSH_MODE(mode) \ + ((NORMAL_MODE <= mode && mode <= DIVIDE_MODE) \ + || (GREY_ADDITION_MODE <= mode && mode <= GREY_SUBTRACTION_MODE)) /* Types of convolutions */ typedef enum diff -ur gimp-1.2.2-old/app/brush_select_cmds.c gimp-1.2.2/app/brush_select_cmds.c --- gimp-1.2.2-old/app/brush_select_cmds.c Thu Feb 17 06:44:26 2000 +++ gimp-1.2.2/app/brush_select_cmds.c Wed Sep 26 20:48:56 2001 @@ -83,7 +83,7 @@ spacing = args[4].value.pdb_int; paint_mode = args[5].value.pdb_int; - if (paint_mode < NORMAL_MODE || paint_mode > DIVIDE_MODE) + if (!GIMP_IS_BRUSH_MODE(paint_mode)) success = FALSE; if (success) @@ -136,7 +136,7 @@ { PDB_INT32, "paint_mode", - "The initial paint mode: { NORMAL_MODE (0), DISSOLVE_MODE (1), BEHIND_MODE (2), MULTIPLY_MODE (3), SCREEN_MODE (4), OVERLAY_MODE (5), DIFFERENCE_MODE (6), ADDITION_MODE (7), SUBTRACT_MODE (8), DARKEN_ONLY_MODE (9), LIGHTEN_ONLY_MODE (10), HUE_MODE (11), SATURATION_MODE (12), COLOR_MODE (13), VALUE_MODE (14), DIVIDE_MODE (15) }" + "The initial paint mode: { NORMAL_MODE (0), DISSOLVE_MODE (1), BEHIND_MODE (2), +MULTIPLY_MODE (3), SCREEN_MODE (4), OVERLAY_MODE (5), DIFFERENCE_MODE (6), +ADDITION_MODE (7), SUBTRACT_MODE (8), DARKEN_ONLY_MODE (9), LIGHTEN_ONLY_MODE (10), +HUE_MODE (11), SATURATION_MODE (12), COLOR_MODE (13), VALUE_MODE (14), DIVIDE_MODE +(15), GREY_ADDITION_MODE (19), GREY_SUBTRACTION_MODE (20) }" } }; @@ -240,7 +240,7 @@ spacing = args[3].value.pdb_int; paint_mode = args[4].value.pdb_int; - if (paint_mode < NORMAL_MODE || paint_mode > DIVIDE_MODE) + if (!GIMP_IS_BRUSH_MODE(paint_mode)) success = FALSE; if (success) @@ -295,7 +295,7 @@ { PDB_INT32, "paint_mode", - "The initial paint mode: { NORMAL_MODE (0), DISSOLVE_MODE (1), BEHIND_MODE (2), MULTIPLY_MODE (3), SCREEN_MODE (4), OVERLAY_MODE (5), DIFFERENCE_MODE (6), ADDITION_MODE (7), SUBTRACT_MODE (8), DARKEN_ONLY_MODE (9), LIGHTEN_ONLY_MODE (10), HUE_MODE (11), SATURATION_MODE (12), COLOR_MODE (13), VALUE_MODE (14), DIVIDE_MODE (15) }" + "The initial paint mode: { NORMAL_MODE (0), DISSOLVE_MODE (1), BEHIND_MODE (2), +MULTIPLY_MODE (3), SCREEN_MODE (4), OVERLAY_MODE (5), DIFFERENCE_MODE (6), +ADDITION_MODE (7), SUBTRACT_MODE (8), DARKEN_ONLY_MODE (9), LIGHTEN_ONLY_MODE (10), +HUE_MODE (11), SATURATION_MODE (12), COLOR_MODE (13), VALUE_MODE (14), DIVIDE_MODE +(15), GREY_ADDITION_MODE (19), GREY_SUBTRACTION_MODE (20) }" } }; diff -ur gimp-1.2.2-old/app/brushes_cmds.c gimp-1.2.2/app/brushes_cmds.c --- gimp-1.2.2-old/app/brushes_cmds.c Sun Dec 17 14:25:56 2000 +++ gimp-1.2.2/app/brushes_cmds.c Wed Sep 26 20:48:56 2001 @@ -369,7 +369,7 @@ { PDB_INT32, "paint_mode", - "The paint mode: { NORMAL_MODE (0), DISSOLVE_MODE (1), BEHIND_MODE (2), MULTIPLY_MODE (3), SCREEN_MODE (4), OVERLAY_MODE (5), DIFFERENCE_MODE (6), ADDITION_MODE (7), SUBTRACT_MODE (8), DARKEN_ONLY_MODE (9), LIGHTEN_ONLY_MODE (10), HUE_MODE (11), SATURATION_MODE (12), COLOR_MODE (13), VALUE_MODE (14), DIVIDE_MODE (15) }" + "The paint mode: { NORMAL_MODE (0), DISSOLVE_MODE (1), BEHIND_MODE (2), +MULTIPLY_MODE (3), SCREEN_MODE (4), OVERLAY_MODE (5), DIFFERENCE_MODE (6), +ADDITION_MODE (7), SUBTRACT_MODE (8), DARKEN_ONLY_MODE (9), LIGHTEN_ONLY_MODE (10), +HUE_MODE (11), SATURATION_MODE (12), COLOR_MODE (13), VALUE_MODE (14), DIVIDE_MODE +(15), GREY_ADDITION_MODE (19), GREY_SUBTRACTION_MODE (20) }" } }; @@ -396,7 +396,7 @@ gint32 paint_mode; paint_mode = args[0].value.pdb_int; - if (paint_mode < NORMAL_MODE || paint_mode > DIVIDE_MODE) + if (!GIMP_IS_BRUSH_MODE(paint_mode)) success = FALSE; if (success) @@ -410,7 +410,7 @@ { PDB_INT32, "paint_mode", - "The paint mode: { NORMAL_MODE (0), DISSOLVE_MODE (1), BEHIND_MODE (2), MULTIPLY_MODE (3), SCREEN_MODE (4), OVERLAY_MODE (5), DIFFERENCE_MODE (6), ADDITION_MODE (7), SUBTRACT_MODE (8), DARKEN_ONLY_MODE (9), LIGHTEN_ONLY_MODE (10), HUE_MODE (11), SATURATION_MODE (12), COLOR_MODE (13), VALUE_MODE (14), DIVIDE_MODE (15) }" + "The paint mode: { NORMAL_MODE (0), DISSOLVE_MODE (1), BEHIND_MODE (2), +MULTIPLY_MODE (3), SCREEN_MODE (4), OVERLAY_MODE (5), DIFFERENCE_MODE (6), +ADDITION_MODE (7), SUBTRACT_MODE (8), DARKEN_ONLY_MODE (9), LIGHTEN_ONLY_MODE (10), +HUE_MODE (11), SATURATION_MODE (12), COLOR_MODE (13), VALUE_MODE (14), DIVIDE_MODE +(15), GREY_ADDITION_MODE (19), GREY_SUBTRACTION_MODE (20) }" } }; @@ -580,7 +580,7 @@ { PDB_INT32, "paint_mode", - "The paint mode: { NORMAL_MODE (0), DISSOLVE_MODE (1), BEHIND_MODE (2), MULTIPLY_MODE (3), SCREEN_MODE (4), OVERLAY_MODE (5), DIFFERENCE_MODE (6), ADDITION_MODE (7), SUBTRACT_MODE (8), DARKEN_ONLY_MODE (9), LIGHTEN_ONLY_MODE (10), HUE_MODE (11), SATURATION_MODE (12), COLOR_MODE (13), VALUE_MODE (14), DIVIDE_MODE (15) }" + "The paint mode: { NORMAL_MODE (0), DISSOLVE_MODE (1), BEHIND_MODE (2), +MULTIPLY_MODE (3), SCREEN_MODE (4), OVERLAY_MODE (5), DIFFERENCE_MODE (6), +ADDITION_MODE (7), SUBTRACT_MODE (8), DARKEN_ONLY_MODE (9), LIGHTEN_ONLY_MODE (10), +HUE_MODE (11), SATURATION_MODE (12), COLOR_MODE (13), VALUE_MODE (14), DIVIDE_MODE +(15), GREY_ADDITION_MODE (19), GREY_SUBTRACTION_MODE (20) }" }, { PDB_INT32, diff -ur gimp-1.2.2-old/app/layer_cmds.c gimp-1.2.2/app/layer_cmds.c --- gimp-1.2.2-old/app/layer_cmds.c Thu Dec 28 12:37:23 2000 +++ gimp-1.2.2/app/layer_cmds.c Wed Sep 26 20:48:56 2001 @@ -134,7 +134,7 @@ success = FALSE; mode = args[6].value.pdb_int; - if (mode < NORMAL_MODE || mode > DIVIDE_MODE) + if (!GIMP_IS_BRUSH_MODE(mode)) success = FALSE; if (success) @@ -187,7 +187,7 @@ { PDB_INT32, "mode", - "The layer combination mode: { NORMAL_MODE (0), DISSOLVE_MODE (1), BEHIND_MODE (2), MULTIPLY_MODE (3), SCREEN_MODE (4), OVERLAY_MODE (5), DIFFERENCE_MODE (6), ADDITION_MODE (7), SUBTRACT_MODE (8), DARKEN_ONLY_MODE (9), LIGHTEN_ONLY_MODE (10), HUE_MODE (11), SATURATION_MODE (12), COLOR_MODE (13), VALUE_MODE (14), DIVIDE_MODE (15) }" + "The layer combination mode: { NORMAL_MODE (0), DISSOLVE_MODE (1), BEHIND_MODE +(2), MULTIPLY_MODE (3), SCREEN_MODE (4), OVERLAY_MODE (5), DIFFERENCE_MODE (6), +ADDITION_MODE (7), SUBTRACT_MODE (8), DARKEN_ONLY_MODE (9), LIGHTEN_ONLY_MODE (10), +HUE_MODE (11), SATURATION_MODE (12), COLOR_MODE (13), VALUE_MODE (14), DIVIDE_MODE +(15), GREY_ADDITION_MODE (19), GREY_SUBTRACTION_MODE (20) }" } }; @@ -1683,7 +1683,7 @@ success = FALSE; mode = args[1].value.pdb_int; - if (mode < NORMAL_MODE || mode > DIVIDE_MODE) + if (!GIMP_IS_BRUSH_MODE(mode)) success = FALSE; if (success) diff -ur gimp-1.2.2-old/app/layers_dialog.c gimp-1.2.2/app/layers_dialog.c --- gimp-1.2.2-old/app/layers_dialog.c Thu Jun 21 04:27:22 2001 +++ gimp-1.2.2/app/layers_dialog.c Wed Sep 26 15:20:37 2001 @@ -384,6 +384,8 @@ _("Difference"), (gpointer) DIFFERENCE_MODE, NULL, _("Addition"), (gpointer) ADDITION_MODE, NULL, _("Subtract"), (gpointer) SUBTRACT_MODE, NULL, + _("Grey Addition"), (gpointer) GREY_ADDITION_MODE,NULL, + _("Grey Subtraction"),(gpointer) GREY_SUBTRACTION_MODE, +NULL, _("Darken Only"), (gpointer) DARKEN_ONLY_MODE, NULL, _("Lighten Only"), (gpointer) LIGHTEN_ONLY_MODE, NULL, _("Hue"), (gpointer) HUE_MODE, NULL, diff -ur gimp-1.2.2-old/app/paint_funcs.c gimp-1.2.2/app/paint_funcs.c --- gimp-1.2.2-old/app/paint_funcs.c Tue Jun 26 04:59:24 2001 +++ gimp-1.2.2/app/paint_funcs.c Wed Sep 26 16:35:07 2001 @@ -103,7 +103,9 @@ { 0, 0, 0, N_("Divide (Dodge)") }, { 1, 0, 1, N_("Erase") }, { 1, 1, 1, N_("Replace") }, - { 1, 0, 1, N_("Anti Erase") } + { 1, 0, 1, N_("Anti Erase") }, + { 0, 0, 0, N_("Grey Addition") }, + { 0, 0, 0, N_("Grey Subtraction") } }; /* ColorHash structure */ @@ -1081,6 +1083,76 @@ } } +void +grey_add_pixels (const unsigned char *src1, + const unsigned char *src2, + unsigned char *dest, + int length, + int bytes1, + int bytes2, + int has_alpha1, + int has_alpha2) +{ + int alpha, b; + int sum; + + alpha = (has_alpha1 || has_alpha2) ? MAX (bytes1, bytes2) - 1 : bytes1; + + while (length --) + { + for (b = 0; b < alpha; b++) + { + /* Add, re-center and clip. */ + sum = src1[b] + src2[b] - 128; + dest[b] = MAX(MIN(sum, 255), 0); + } + + if (has_alpha1 && has_alpha2) + dest[alpha] = MIN (src1[alpha], src2[alpha]); + else if (has_alpha2) + dest[alpha] = src2[alpha]; + + src1 += bytes1; + src2 += bytes2; + dest += bytes2; + } +} + + +void +grey_subtract_pixels (const unsigned char *src1, + const unsigned char *src2, + unsigned char *dest, + int length, + int bytes1, + int bytes2, + int has_alpha1, + int has_alpha2) +{ + int alpha, b; + int diff; + + alpha = (has_alpha1 || has_alpha2) ? MAX (bytes1, bytes2) - 1 : bytes1; + + while (length --) + { + for (b = 0; b < alpha; b++) + { + diff = src1[b] - src2[b] + 128; + dest[b] = MAX(MIN(diff, 255), 0); + } + + if (has_alpha1 && has_alpha2) + dest[alpha] = MIN (src1[alpha], src2[alpha]); + else if (has_alpha2) + dest[alpha] = src2[alpha]; + + src1 += bytes1; + src2 += bytes2; + dest += bytes2; + } +} + void difference_pixels (const unsigned char *src1, @@ -5820,6 +5892,14 @@ case SUBTRACT_MODE: subtract_pixels (src1, src2, *dest, length, bytes1, bytes2, has_alpha1, has_alpha2); + break; + + case GREY_ADDITION_MODE: + grey_add_pixels (src1, src2, *dest, length, bytes1, bytes2, has_alpha1, +has_alpha2); + break; + + case GREY_SUBTRACTION_MODE: + grey_subtract_pixels (src1, src2, *dest, length, bytes1, bytes2, has_alpha1, +has_alpha2); break; case DARKEN_ONLY_MODE: diff -ur gimp-1.2.2-old/app/paint_funcs.h gimp-1.2.2/app/paint_funcs.h --- gimp-1.2.2-old/app/paint_funcs.h Sat Dec 16 12:26:28 2000 +++ gimp-1.2.2/app/paint_funcs.h Wed Sep 26 19:17:57 2001 @@ -114,6 +114,18 @@ int bytes1, int bytes2, int has_alpha1, int has_alpha2); +void grey_add_pixels (const unsigned char *src1, + const unsigned char *src2, + unsigned char *dest, int length, + int bytes1, int bytes2, + int has_alpha1, int has_alpha2); + +void grey_subtract_pixels (const unsigned char *src1, + const unsigned char *src2, + unsigned char *dest, int length, + int bytes1, int bytes2, + int has_alpha1, int has_alpha2); + void difference_pixels (const unsigned char *src1, const unsigned char *src2, unsigned char *dest, int length, diff -ur gimp-1.2.2-old/app/tool_options.c gimp-1.2.2/app/tool_options.c --- gimp-1.2.2-old/app/tool_options.c Sat Dec 16 14:44:25 2000 +++ gimp-1.2.2/app/tool_options.c Wed Sep 26 15:22:39 2001 @@ -929,6 +929,8 @@ _("Difference"), (gpointer) DIFFERENCE_MODE, NULL, _("Addition"), (gpointer) ADDITION_MODE, NULL, _("Subtract"), (gpointer) SUBTRACT_MODE, NULL, + _("Grey Addition"), (gpointer) GREY_ADDITION_MODE, NULL, + _("Grey Subtraction"),(gpointer) GREY_SUBTRACTION_MODE, NULL, _("Darken Only"), (gpointer) DARKEN_ONLY_MODE, NULL, _("Lighten Only"), (gpointer) LIGHTEN_ONLY_MODE, NULL, _("Hue"), (gpointer) HUE_MODE, NULL, diff -ur gimp-1.2.2-old/app/tools_cmds.c gimp-1.2.2/app/tools_cmds.c --- gimp-1.2.2-old/app/tools_cmds.c Mon Apr 24 11:22:56 2000 +++ gimp-1.2.2/app/tools_cmds.c Wed Sep 26 20:50:19 2001 @@ -277,7 +277,7 @@ success = FALSE; paint_mode = args[2].value.pdb_int; - if (paint_mode < NORMAL_MODE || paint_mode > DIVIDE_MODE) + if (!GIMP_IS_BRUSH_MODE(paint_mode)) success = FALSE; gradient_type = args[3].value.pdb_int; @@ -340,7 +340,7 @@ { PDB_INT32, "paint_mode", - "The paint application mode: { NORMAL_MODE (0), DISSOLVE_MODE (1), BEHIND_MODE (2), MULTIPLY_MODE (3), SCREEN_MODE (4), OVERLAY_MODE (5), DIFFERENCE_MODE (6), ADDITION_MODE (7), SUBTRACT_MODE (8), DARKEN_ONLY_MODE (9), LIGHTEN_ONLY_MODE (10), HUE_MODE (11), SATURATION_MODE (12), COLOR_MODE (13), VALUE_MODE (14), DIVIDE_MODE (15) }" + "The paint application mode: { NORMAL_MODE (0), DISSOLVE_MODE (1), BEHIND_MODE +(2), MULTIPLY_MODE (3), SCREEN_MODE (4), OVERLAY_MODE (5), DIFFERENCE_MODE (6), +ADDITION_MODE (7), SUBTRACT_MODE (8), DARKEN_ONLY_MODE (9), LIGHTEN_ONLY_MODE (10), +HUE_MODE (11), SATURATION_MODE (12), COLOR_MODE (13), VALUE_MODE (14), DIVIDE_MODE +(15), GREY_ADDITION_MODE (19), GREY_SUBTRACTION_MODE (20) }" }, { PDB_INT32, @@ -438,7 +438,7 @@ success = FALSE; paint_mode = args[2].value.pdb_int; - if (paint_mode < NORMAL_MODE || paint_mode > DIVIDE_MODE) + if (!GIMP_IS_BRUSH_MODE(paint_mode)) success = FALSE; opacity = args[3].value.pdb_float; @@ -480,7 +480,7 @@ { PDB_INT32, "paint_mode", - "The paint application mode: { NORMAL_MODE (0), DISSOLVE_MODE (1), BEHIND_MODE (2), MULTIPLY_MODE (3), SCREEN_MODE (4), OVERLAY_MODE (5), DIFFERENCE_MODE (6), ADDITION_MODE (7), SUBTRACT_MODE (8), DARKEN_ONLY_MODE (9), LIGHTEN_ONLY_MODE (10), HUE_MODE (11), SATURATION_MODE (12), COLOR_MODE (13), VALUE_MODE (14), DIVIDE_MODE (15) }" + "The paint application mode: { NORMAL_MODE (0), DISSOLVE_MODE (1), BEHIND_MODE +(2), MULTIPLY_MODE (3), SCREEN_MODE (4), OVERLAY_MODE (5), DIFFERENCE_MODE (6), +ADDITION_MODE (7), SUBTRACT_MODE (8), DARKEN_ONLY_MODE (9), LIGHTEN_ONLY_MODE (10), +HUE_MODE (11), SATURATION_MODE (12), COLOR_MODE (13), VALUE_MODE (14), DIVIDE_MODE +(15), GREY_ADDITION_MODE (19), GREY_SUBTRACTION_MODE (20) }" }, { PDB_FLOAT, _______________________________________________ Gimp-developer mailing list [EMAIL PROTECTED] http://lists.xcf.berkeley.edu/mailman/listinfo/gimp-developer