mirror of https://github.com/GNOME/gimp.git
424 lines
9.1 KiB
C
424 lines
9.1 KiB
C
/*
|
|
* Value-Invert plug-in v1.1 by Adam D. Moss, adam@foxbox.org. 1999/02/27
|
|
*/
|
|
|
|
/* The GIMP -- an image manipulation program
|
|
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
|
|
*
|
|
* 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
*/
|
|
|
|
/*
|
|
* BUGS:
|
|
* Is not undoable when operating on indexed images - GIMP's fault.
|
|
*/
|
|
|
|
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include "libgimp/gimp.h"
|
|
|
|
|
|
/* Declare local functions.
|
|
*/
|
|
static void query (void);
|
|
static void indexed_vinvert (gint32 image_ID);
|
|
static void run (char *name,
|
|
int nparams,
|
|
GParam *param,
|
|
int *nreturn_vals,
|
|
GParam **return_vals);
|
|
|
|
static void vinvert (GDrawable *drawable);
|
|
static void vinvert_render_row (const guchar *src_row,
|
|
guchar *dest_row,
|
|
gint row_width,
|
|
const gint bytes);
|
|
|
|
void fp_rgb_to_hsv (float *, float *, float *);
|
|
void fp_hsv_to_rgb (float *, float *, float *);
|
|
|
|
|
|
static GRunModeType run_mode;
|
|
|
|
GPlugInInfo PLUG_IN_INFO =
|
|
{
|
|
NULL, /* init_proc */
|
|
NULL, /* quit_proc */
|
|
query, /* query_proc */
|
|
run, /* run_proc */
|
|
};
|
|
|
|
|
|
MAIN ()
|
|
|
|
static void
|
|
query ()
|
|
{
|
|
static GParamDef args[] =
|
|
{
|
|
{ PARAM_INT32, "run_mode", "Interactive, non-interactive" },
|
|
{ PARAM_IMAGE, "image", "Input image (used for indexed images)" },
|
|
{ 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_vinvert",
|
|
"Invert the 'value' componant of an indexed/RGB image in HSV colourspace",
|
|
"This function takes an indexed/RGB image and inverts its 'value' in HSV space. The upshot of this is that the colour and saturation at any given point remains the same, but its brightness is effectively inverted. Quite strange. Sometimes produces unpleasant colour artifacts on images from lossy sources (ie. JPEG).",
|
|
"Adam D. Moss (adam@foxbox.org)",
|
|
"Adam D. Moss (adam@foxbox.org)",
|
|
"27th March 1997",
|
|
"<Image>/Filters/Colors/Value Invert",
|
|
"RGB*, INDEXED*",
|
|
PROC_PLUG_IN,
|
|
nargs, nreturn_vals,
|
|
args, return_vals);
|
|
}
|
|
|
|
|
|
|
|
void
|
|
fp_rgb_to_hsv (float *r,
|
|
float *g,
|
|
float *b)
|
|
{
|
|
int red, green, blue;
|
|
float h=0, s, v;
|
|
int min, max;
|
|
int delta;
|
|
|
|
red = *r;
|
|
green = *g;
|
|
blue = *b;
|
|
|
|
if (red > green)
|
|
{
|
|
if (red > blue)
|
|
max = red;
|
|
else
|
|
max = blue;
|
|
|
|
if (green < blue)
|
|
min = green;
|
|
else
|
|
min = blue;
|
|
}
|
|
else
|
|
{
|
|
if (green > blue)
|
|
max = green;
|
|
else
|
|
max = blue;
|
|
|
|
if (red < blue)
|
|
min = red;
|
|
else
|
|
min = blue;
|
|
}
|
|
|
|
v = max;
|
|
|
|
if (max != 0)
|
|
s = ((max - min) * 255) / (float) max;
|
|
else
|
|
s = 0;
|
|
|
|
if (s == 0)
|
|
h = 0;
|
|
else
|
|
{
|
|
delta = max - min;
|
|
if (red == max)
|
|
h = (green - blue) / (float) delta;
|
|
else if (green == max)
|
|
h = 2 + (blue - red) / (float) delta;
|
|
else if (blue == max)
|
|
h = 4 + (red - green) / (float) delta;
|
|
h *= 42.5;
|
|
|
|
if (h < 0)
|
|
h += 255;
|
|
if (h > 255)
|
|
h -= 255;
|
|
}
|
|
|
|
*r = h;
|
|
*g = s;
|
|
*b = v;
|
|
}
|
|
|
|
|
|
void
|
|
fp_hsv_to_rgb (float *h,
|
|
float *s,
|
|
float *v)
|
|
{
|
|
float hue, saturation, value;
|
|
float f, p, q, t;
|
|
|
|
if (((int)*s) == 0)
|
|
{
|
|
*h = *v;
|
|
*s = *v;
|
|
*v = *v;
|
|
}
|
|
else
|
|
{
|
|
hue = *h * 6.0 / 255.0;
|
|
saturation = *s / 255.0;
|
|
value = *v / 255.0;
|
|
|
|
f = hue - (int) hue;
|
|
p = value * (1.0 - saturation);
|
|
q = value * (1.0 - (saturation * f));
|
|
t = value * (1.0 - (saturation * (1.0 - f)));
|
|
|
|
switch ((int) hue)
|
|
{
|
|
case 0:
|
|
*h = value * 255.0;
|
|
*s = t * 255.0;
|
|
*v = p * 255.0;
|
|
break;
|
|
case 1:
|
|
*h = q * 255.0;
|
|
*s = value * 255.0;
|
|
*v = p * 255.0;
|
|
break;
|
|
case 2:
|
|
*h = p * 255.0;
|
|
*s = value * 255.0;
|
|
*v = t * 255.0;
|
|
break;
|
|
case 3:
|
|
*h = p * 255.0;
|
|
*s = q * 255.0;
|
|
*v = value * 255.0;
|
|
break;
|
|
case 4:
|
|
*h = t * 255.0;
|
|
*s = p * 255.0;
|
|
*v = value * 255.0;
|
|
break;
|
|
case 5:
|
|
*h = value * 255.0;
|
|
*s = p * 255.0;
|
|
*v = q * 255.0;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
run (char *name,
|
|
int nparams,
|
|
GParam *param,
|
|
int *nreturn_vals,
|
|
GParam **return_vals)
|
|
{
|
|
static GParam values[1];
|
|
GDrawable *drawable;
|
|
gint32 image_ID;
|
|
GStatusType status = STATUS_SUCCESS;
|
|
|
|
run_mode = param[0].data.d_int32;
|
|
|
|
*nreturn_vals = 1;
|
|
*return_vals = values;
|
|
|
|
values[0].type = PARAM_STATUS;
|
|
values[0].data.d_status = status;
|
|
|
|
|
|
/* Get the specified drawable */
|
|
drawable = gimp_drawable_get (param[2].data.d_drawable);
|
|
image_ID = param[1].data.d_image;
|
|
|
|
if (status == STATUS_SUCCESS)
|
|
{
|
|
/* Make sure that the drawable is indexed or RGB color */
|
|
if (gimp_drawable_is_rgb (drawable->id))
|
|
{
|
|
if (run_mode != RUN_NONINTERACTIVE)
|
|
gimp_progress_init ("Value Invert...");
|
|
|
|
vinvert (drawable);
|
|
if (run_mode != RUN_NONINTERACTIVE)
|
|
gimp_displays_flush ();
|
|
}
|
|
else
|
|
if (gimp_drawable_is_indexed (drawable->id))
|
|
{
|
|
indexed_vinvert (image_ID);
|
|
if (run_mode != RUN_NONINTERACTIVE)
|
|
gimp_displays_flush ();
|
|
}
|
|
else
|
|
{
|
|
status = STATUS_EXECUTION_ERROR;
|
|
}
|
|
}
|
|
|
|
values[0].data.d_status = status;
|
|
|
|
gimp_drawable_detach (drawable);
|
|
}
|
|
|
|
|
|
static void
|
|
indexed_vinvert(gint32 image_ID)
|
|
{
|
|
guchar *cmap;
|
|
gint ncols;
|
|
|
|
cmap = gimp_image_get_cmap (image_ID, &ncols);
|
|
|
|
if (cmap==NULL)
|
|
{
|
|
printf("vinvert: cmap was NULL! Quitting...\n");
|
|
gimp_quit();
|
|
}
|
|
|
|
vinvert_render_row(
|
|
cmap,
|
|
cmap,
|
|
ncols,
|
|
3
|
|
);
|
|
|
|
gimp_image_set_cmap (image_ID, cmap, ncols);
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
vinvert_render_row (const guchar *src_data,
|
|
guchar *dest_data,
|
|
gint col, /* row width in pixels */
|
|
const gint bytes)
|
|
{
|
|
while (col--)
|
|
{
|
|
float v1, v2, v3;
|
|
|
|
v1 = (float)src_data[col*bytes ];
|
|
v2 = (float)src_data[col*bytes +1];
|
|
v3 = (float)src_data[col*bytes +2];
|
|
|
|
fp_rgb_to_hsv(&v1, &v2, &v3);
|
|
v3 = 255.0-v3;
|
|
fp_hsv_to_rgb(&v1, &v2, &v3);
|
|
|
|
dest_data[col*bytes ] = (int)v1;
|
|
dest_data[col*bytes +1] = (int)v2;
|
|
dest_data[col*bytes +2] = (int)v3;
|
|
|
|
if (bytes>3)
|
|
{
|
|
gint bytenum;
|
|
|
|
for (bytenum = 3; bytenum<bytes; bytenum++)
|
|
{
|
|
dest_data[col*bytes+bytenum] =
|
|
src_data[col*bytes+bytenum];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
vinvert_render_region (const GPixelRgn srcPR,
|
|
const GPixelRgn destPR)
|
|
{
|
|
gint row;
|
|
guchar* src_ptr = srcPR.data;
|
|
guchar* dest_ptr = destPR.data;
|
|
|
|
for (row = 0; row < srcPR.h ; row++)
|
|
{
|
|
vinvert_render_row (src_ptr, dest_ptr,
|
|
srcPR.w,
|
|
srcPR.bpp);
|
|
|
|
src_ptr += srcPR.rowstride;
|
|
dest_ptr += destPR.rowstride;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
vinvert (GDrawable *drawable)
|
|
{
|
|
GPixelRgn srcPR, destPR;
|
|
gint x1, y1, x2, y2;
|
|
gpointer pr;
|
|
gint total_area, area_so_far;
|
|
gint progress_skip;
|
|
|
|
|
|
/* 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);
|
|
|
|
total_area = (x2 - x1) * (y2 - y1);
|
|
area_so_far = 0;
|
|
progress_skip = 0;
|
|
|
|
/* Initialize the pixel regions. */
|
|
gimp_pixel_rgn_init (&srcPR, drawable, x1, y1, (x2 - x1), (y2 - y1),
|
|
FALSE, FALSE);
|
|
gimp_pixel_rgn_init (&destPR, drawable, x1, y1, (x2 - x1), (y2 - y1),
|
|
TRUE, TRUE);
|
|
|
|
for (pr = gimp_pixel_rgns_register (2, &srcPR, &destPR);
|
|
pr != NULL;
|
|
pr = gimp_pixel_rgns_process (pr))
|
|
{
|
|
vinvert_render_region (srcPR, destPR);
|
|
|
|
if ((run_mode != RUN_NONINTERACTIVE))
|
|
{
|
|
area_so_far += srcPR.w * srcPR.h;
|
|
if (((progress_skip++)%10) == 0)
|
|
gimp_progress_update ((double) area_so_far / (double) total_area);
|
|
}
|
|
}
|
|
|
|
|
|
/* update the processed region */
|
|
gimp_drawable_flush (drawable);
|
|
gimp_drawable_merge_shadow (drawable->id, TRUE);
|
|
gimp_drawable_update (drawable->id, x1, y1, (x2 - x1), (y2 - y1));
|
|
}
|
|
|
|
|
|
|
|
|
|
|