gimp/plug-ins/common/gqbist.c

723 lines
18 KiB
C

/*
* Written 1997 Jens Ch. Restemeier <jchrr@hrz.uni-bielefeld.de>
* This program is based on an algorithm / article by
* Jörn Loviscach.
*
* It appeared in c't 10/95, page 326 and is called
* "Ausgewürfelt - Moderne Kunst algorithmisch erzeugen".
* (~modern art created with algorithms)
*
* It generates one main formula (the middle button) and 8 variations of it.
* If you select a variation it becomes the new main formula. If you
* press "OK" the main formula will be applied to the image.
*
* 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.
*
*/
/*
* History:
* 1.0 first release
* 1.2 now handles RGB*
* 1.5 fixed a small bug
* 1.6 fixed a bug that was added by v1.5 :-(
* 1.7 added patch from Art Haas to make it compile with HP-UX, a small clean-up
* 1.8 Dscho added transform file load/save, bug-fixes
* 1.9 rewrote renderloop.
* 1.9a fixed a bug.
* 1.9b fixed MAIN()
* 1.10 added optimizer
*/
#include "config.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <time.h>
#include <gtk/gtk.h>
#include "libgimp/gimp.h"
#include "libgimp/gimpui.h"
#include "libgimp/stdplugins-intl.h"
/** qbist renderer ***********************************************************/
#define MAX_TRANSFORMS 36
#define NUM_TRANSFORMS 9
#define NUM_REGISTERS 6
#define PLUG_IN_NAME "plug_in_qbist"
#define PLUG_IN_VERSION "March 1998, 1.10"
#define PREVIEW_SIZE 64
/** types *******************************************************************/
typedef gfloat vreg[3];
typedef struct _info {
int transformSequence [MAX_TRANSFORMS];
int source [MAX_TRANSFORMS];
int control [MAX_TRANSFORMS];
int dest [MAX_TRANSFORMS];
} s_info;
#define PROJECTION 0
#define SHIFT 1
#define SHIFTBACK 2
#define ROTATE 3
#define ROTATE2 4
#define MULTIPLY 5
#define SINE 6
#define CONDITIONAL 7
#define COMPLEMENT 8
/** prototypes **************************************************************/
void query (void);
void run (char *name, int nparams, GParam *param, int *nreturn_vals, GParam **return_vals);
void dialog_cancel ();
void dialog_new_variations (GtkWidget *widget, gpointer data);
void dialog_update_previews (GtkWidget *widget, gpointer data);
void dialog_select_preview (GtkWidget *widget, s_info *n_info);
int dialog_create ();
s_info qbist_info;
/** qbist functions *********************************************************/
void
create_info (s_info *info)
{
int k;
for (k=0; k<MAX_TRANSFORMS; k++)
{
info->transformSequence[k]=rand() % NUM_TRANSFORMS;
info->source[k]=rand() % NUM_REGISTERS;
info->control[k]=rand() % NUM_REGISTERS;
info->dest[k]=rand() % NUM_REGISTERS;
}
info->dest[rand() % MAX_TRANSFORMS]=0;
}
void
modify_info (s_info *o_info,
s_info *n_info)
{
int k, n;
memcpy(n_info, o_info, sizeof(s_info));
n=rand() % MAX_TRANSFORMS;
for (k=0;k<n; k++)
{
switch (rand() % 4)
{
case 0: n_info->transformSequence[rand() % MAX_TRANSFORMS] = rand() % NUM_TRANSFORMS; break;
case 1: n_info->source[rand() % MAX_TRANSFORMS] = rand() % NUM_REGISTERS; break;
case 2: n_info->control[rand() % MAX_TRANSFORMS] = rand() % NUM_REGISTERS; break;
case 3: n_info->dest[rand() % MAX_TRANSFORMS] = rand() % NUM_REGISTERS; break;
}
}
}
/*
* Optimizer
*/
int used_trans_flag[MAX_TRANSFORMS];
int used_reg_flag[NUM_REGISTERS];
void
check_last_modified (s_info info,
int p,
int n)
{
p--;
while ((p>=0) && (info.dest[p]!=n)) p--;
if (p<0)
used_reg_flag[n]=1;
else
{
used_trans_flag[p]=1;
check_last_modified(info, p, info.source[p]);
check_last_modified(info, p, info.control[p]);
}
}
void
optimize (s_info info)
{
int i;
/* double-arg fix: */
for (i=0; i<MAX_TRANSFORMS; i++)
{
used_trans_flag[i]=0;
if (i<NUM_REGISTERS)
used_reg_flag[i]=0;
/* double-arg fix: */
switch (info.transformSequence[i])
{
case ROTATE:
case ROTATE2:
case COMPLEMENT:
info.control[i]=info.dest[i];
break;
}
}
/* check for last modified item */
check_last_modified(info, MAX_TRANSFORMS, 0);
}
void qbist (s_info info,
gchar *buffer,
int xp,
int yp,
int num,
int width,
int height,
int bpp)
{
gushort gx;
vreg reg [NUM_REGISTERS];
int i;
gushort sr, cr, dr;
if (num<=0) return;
for(gx=0; gx<num; gx ++)
{
for(i=0; i<NUM_REGISTERS; i++)
{
if (used_reg_flag[i])
{
reg[i][0] = ((float)gx+xp) / ((float)(width));
reg[i][1] = ((float)yp) / ((float)(height));
reg[i][2] = ((float)i) / ((float)NUM_REGISTERS);
}
}
for(i=0;i<MAX_TRANSFORMS; i++)
{
sr=info.source[i];cr=info.control[i];dr=info.dest[i];
if (used_trans_flag[i]) switch (info.transformSequence[i])
{
case PROJECTION:
{
gfloat scalarProd;
scalarProd = (reg[sr][0]*reg[cr][0])+(reg[sr][1]*reg[cr][1])+(reg[sr][2]*reg[cr][2]);
reg[dr][0] = scalarProd*reg[sr][0];
reg[dr][1] = scalarProd*reg[sr][1];
reg[dr][2] = scalarProd*reg[sr][2];
break;
}
case SHIFT:
reg[dr][0] = reg[sr][0]+reg[cr][0];
if (reg[dr][0] >= 1.0) reg[dr][0] -= 1.0;
reg[dr][1] = reg[sr][1]+reg[cr][1];
if (reg[dr][1] >= 1.0) reg[dr][1] -= 1.0;
reg[dr][2] = reg[sr][2]+reg[cr][2];
if (reg[dr][2] >= 1.0) reg[dr][2] -= 1.0;
break;
case SHIFTBACK:
reg[dr][0] = reg[sr][0]-reg[cr][0];
if (reg[dr][0] <= 0.0) reg[dr][0] += 1.0;
reg[dr][1] = reg[sr][1]-reg[cr][1];
if (reg[dr][1] <= 0.0) reg[dr][1] += 1.0;
reg[dr][2] = reg[sr][2]-reg[cr][2];
if (reg[dr][2] <= 0.0) reg[dr][2] += 1.0;
break;
case ROTATE:
reg[dr][0] = reg[sr][1];
reg[dr][1] = reg[sr][2];
reg[dr][2] = reg[sr][0];
break;
case ROTATE2:
reg[dr][0] = reg[sr][2];
reg[dr][1] = reg[sr][0];
reg[dr][2] = reg[sr][1];
break;
case MULTIPLY:
reg[dr][0] = reg[sr][0]*reg[cr][0];
reg[dr][1] = reg[sr][1]*reg[cr][1];
reg[dr][2] = reg[sr][2]*reg[cr][2];
break;
case SINE:
reg[dr][0] = 0.5+(0.5*sin(20.0*reg[sr][0]*reg[cr][0]));
reg[dr][1] = 0.5+(0.5*sin(20.0*reg[sr][1]*reg[cr][1]));
reg[dr][2] = 0.5+(0.5*sin(20.0*reg[sr][2]*reg[cr][2]));
break;
case CONDITIONAL:
if ((reg[cr][0]+reg[cr][1]+reg[cr][2]) > 0.5)
{
reg[dr][0] = reg[sr][0];
reg[dr][1] = reg[sr][1];
reg[dr][2] = reg[sr][2];
}
else
{
reg[dr][0] = reg[cr][0];
reg[dr][1] = reg[cr][1];
reg[dr][2] = reg[cr][2];
}
break;
case COMPLEMENT:
reg[dr][0] = 1.0-reg[sr][0];
reg[dr][1] = 1.0-reg[sr][1];
reg[dr][2] = 1.0-reg[sr][2];
break;
}
}
for (i=0; i<bpp; i++)
{
if (i<3)
{
int a;
a=255.0 * reg[0][i];
buffer[i]=(a<0) ? 0 : ((a>255) ? 255 : a);
}
else
{
buffer[i]=255;
}
}
buffer+=bpp;
}
}
/** Plugin interface *********************************************************/
GPlugInInfo PLUG_IN_INFO =
{
NULL, /* init_proc */
NULL, /* quit_proc */
query, /* query_proc */
run /* run_proc */
};
/* Definition of parameters */
GParamDef args[] =
{
{ PARAM_INT32, "run_mode", "Interactive, non-interactive" },
{ PARAM_IMAGE, "image", "Input image (unused)" },
{ PARAM_DRAWABLE, "drawable", "Input drawable" }
};
GParamDef *return_vals = NULL;
int nargs = sizeof(args) / sizeof(args[0]);
int nreturn_vals = 0;
MAIN()
void
query (void)
{
INIT_I18N();
gimp_install_procedure (PLUG_IN_NAME,
_("Create images based on a random genetic formula"),
_("This Plug-in is based on an article by Jörn Loviscach (appeared in c't 10/95, page 326). It generates modern art pictures from a random genetic formula."),
"Jörn Loviscach, Jens Ch. Restemeier",
"Jörn Loviscach, Jens Ch. Restemeier",
PLUG_IN_VERSION,
N_("<Image>/Filters/Render/Pattern/Qbist..."),
"RGB*",
PROC_PLUG_IN,
nargs,
nreturn_vals,
args,
return_vals);
}
/* Return values */
GParam values[1];
void run (char *name,
int nparams,
GParam *param,
int *nreturn_vals,
GParam **return_vals)
{
gint sel_x1, sel_y1, sel_x2, sel_y2;
gint img_height, img_width, img_bpp, img_has_alpha;
GDrawable *drawable;
GRunModeType run_mode;
GStatusType status;
*nreturn_vals = 1;
*return_vals = values;
status = STATUS_SUCCESS;
if (param[0].type!=PARAM_INT32)
status=STATUS_CALLING_ERROR;
run_mode = param[0].data.d_int32;
if (run_mode == RUN_INTERACTIVE)
{
INIT_I18N_UI();
}
else
{
INIT_I18N();
}
if (param[2].type!=PARAM_DRAWABLE)
status=STATUS_CALLING_ERROR;
drawable = gimp_drawable_get(param[2].data.d_drawable);
img_width = gimp_drawable_width(drawable->id);
img_height = gimp_drawable_height(drawable->id);
img_bpp = gimp_drawable_bpp(drawable->id);
img_has_alpha = gimp_drawable_has_alpha(drawable->id);
gimp_drawable_mask_bounds (drawable->id, &sel_x1, &sel_y1, &sel_x2, &sel_y2);
if (!gimp_drawable_is_rgb(drawable->id))
status=STATUS_CALLING_ERROR;
if (status==STATUS_SUCCESS)
{
create_info(&qbist_info);
switch (run_mode)
{
case RUN_INTERACTIVE:
/* Possibly retrieve data */
gimp_get_data(PLUG_IN_NAME, &qbist_info);
/* Get information from the dialog */
if (dialog_create())
{
status=STATUS_SUCCESS;
gimp_set_data(PLUG_IN_NAME, &qbist_info, sizeof(s_info));
}
else
status=STATUS_EXECUTION_ERROR;
break;
case RUN_NONINTERACTIVE:
status=STATUS_CALLING_ERROR;
break;
case RUN_WITH_LAST_VALS:
/* Possibly retrieve data */
gimp_get_data(PLUG_IN_NAME, &qbist_info);
status=STATUS_SUCCESS;
break;
default:
status=STATUS_CALLING_ERROR;
break;
}
if (status == STATUS_SUCCESS)
{
GPixelRgn imagePR;
guchar *row_data;
gint row;
gimp_tile_cache_ntiles((drawable->width + gimp_tile_width() - 1) / gimp_tile_width());
gimp_pixel_rgn_init (&imagePR, drawable, 0,0, img_width, img_height, TRUE, TRUE);
row_data=(guchar *)malloc((sel_x2-sel_x1)*img_bpp);
optimize(qbist_info);
gimp_progress_init ( _("Qbist ..."));
for (row=sel_y1; row<sel_y2; row++)
{
qbist(qbist_info, (gchar *)row_data, 0, row, sel_x2-sel_x1, sel_x2-sel_x1, sel_y2-sel_y1, img_bpp);
gimp_pixel_rgn_set_row(&imagePR, row_data, sel_x1, row, (sel_x2-sel_x1));
if ((row % 5) == 0)
gimp_progress_update((gfloat)(row-sel_y1)/(gfloat)(sel_y2-sel_y1));
}
free(row_data);
gimp_drawable_flush (drawable);
gimp_drawable_merge_shadow (drawable->id, TRUE);
gimp_drawable_update (drawable->id, sel_x1, sel_y1, (sel_x2 - sel_x1), (sel_y2 - sel_y1));
gimp_displays_flush();
}
}
values[0].type = PARAM_STATUS;
values[0].data.d_status = status;
gimp_drawable_detach(drawable);
}
/** User interface ***********************************************************/
GtkWidget *preview[9];
s_info info[9];
gint result;
void
dialog_cancel (GtkWidget *widget,
gpointer data)
{
gtk_widget_destroy (GTK_WIDGET (data));
}
void dialog_ok (GtkWidget *widget,
gpointer data)
{
result = TRUE;
gtk_widget_destroy (GTK_WIDGET (data));
}
void
dialog_new_variations (GtkWidget *widget,
gpointer data)
{
gint i;
for (i = 1; i < 9; i++)
modify_info (&(info[0]), &(info[i]));
}
void
dialog_update_previews (GtkWidget *widget,
gpointer data)
{
gint i, j;
guchar buf[PREVIEW_SIZE * 3];
for (j=0;j<9;j++)
{
optimize (info[(j+5) % 9]);
for (i = 0; i < PREVIEW_SIZE; i++)
{
qbist (info[(j+5) % 9], (gchar *)buf,
0, i, PREVIEW_SIZE, PREVIEW_SIZE, PREVIEW_SIZE, 3);
gtk_preview_draw_row (GTK_PREVIEW (preview[j]), buf,
0, i, PREVIEW_SIZE);
}
gtk_widget_draw (preview[j], NULL);
}
}
void
dialog_select_preview (GtkWidget *widget,
s_info *n_info)
{
memcpy (&(info[0]), n_info, sizeof (s_info));
dialog_new_variations (widget, NULL);
dialog_update_previews (widget, NULL);
}
/* File I/O stuff */
#define LOBITE(x) ((x)&0xff)
#define HIBITE(x) ((x)>>8)
#define MACBITES(x) (HIBITE(x)+LOBITE(x)<<8)
#define GETMACUSHORT(f) ((fgetc(f)<<8)+(fgetc(f)))
#define PUTMACUSHORT(u, f) fprintf(f, "%c%c", HIBITE(u), LOBITE(u));
int
load_data (char *name)
{
int i;
FILE *f;
f = fopen (name, "rb");
if (f==NULL) return 0;
for (i=0;i<MAX_TRANSFORMS;i++) info[0].transformSequence[i]=GETMACUSHORT(f);
for (i=0;i<MAX_TRANSFORMS;i++) info[0].source[i]=GETMACUSHORT(f);
for (i=0;i<MAX_TRANSFORMS;i++) info[0].control[i]=GETMACUSHORT(f);
for( i=0;i<MAX_TRANSFORMS;i++) info[0].dest[i]=GETMACUSHORT(f);
fclose (f);
return 1;
}
void
save_data (char *name)
{
int i=0;
FILE *f;
f = fopen (name, "wb");
for (i=0;i<MAX_TRANSFORMS;i++) PUTMACUSHORT(info[0].transformSequence[i], f);
for (i=0;i<MAX_TRANSFORMS;i++) PUTMACUSHORT(info[0].source[i], f);
for (i=0;i<MAX_TRANSFORMS;i++) PUTMACUSHORT(info[0].control[i], f);
for (i=0;i<MAX_TRANSFORMS;i++) PUTMACUSHORT(info[0].dest[i], f);
fclose (f);
}
void
file_selection_save (GtkWidget *widget,
GtkWidget *file_select)
{
save_data (gtk_file_selection_get_filename (GTK_FILE_SELECTION (file_select)));
gtk_widget_destroy (file_select);
}
void
file_selection_load (GtkWidget *widget,
GtkWidget *file_select)
{
load_data (gtk_file_selection_get_filename (GTK_FILE_SELECTION (file_select)));
gtk_widget_destroy (file_select);
dialog_new_variations (widget, NULL);
dialog_update_previews (widget, NULL);
}
void
file_selection_cancel (GtkWidget *widget,
GtkWidget *file_select)
{
gtk_widget_destroy (file_select);
}
void
dialog_load (GtkWidget *widget,
gpointer d)
{
GtkWidget *file_select;
file_select = gtk_file_selection_new (_("Load QBE file..."));
gimp_help_connect_help_accel (file_select, gimp_plugin_help_func,
"filters/gqbist.html");
gtk_signal_connect (GTK_OBJECT (GTK_FILE_SELECTION (file_select)->ok_button),
"clicked", (GtkSignalFunc) file_selection_load,
(gpointer)file_select);
gtk_signal_connect (GTK_OBJECT (GTK_FILE_SELECTION (file_select)->cancel_button),
"clicked", (GtkSignalFunc) file_selection_cancel,
(gpointer)file_select);
gtk_widget_show (file_select);
}
void
dialog_save (GtkWidget *widget,
gpointer d)
{
GtkWidget *file_select;
file_select =
gtk_file_selection_new (_("Save (middle transform) as QBE file..."));
gimp_help_connect_help_accel (file_select, gimp_plugin_help_func,
"filters/gqbist.html");
gtk_signal_connect (GTK_OBJECT (GTK_FILE_SELECTION (file_select)->ok_button),
"clicked", (GtkSignalFunc) file_selection_save,
(gpointer)file_select);
gtk_signal_connect (GTK_OBJECT (GTK_FILE_SELECTION (file_select)->cancel_button),
"clicked", (GtkSignalFunc) file_selection_cancel,
(gpointer)file_select);
gtk_widget_show (file_select);
}
int
dialog_create (void)
{
GtkWidget *dialog;
GtkWidget *button;
GtkWidget *table;
guchar *color_cube;
gchar **argv;
gint argc;
gint i;
srand (time (NULL));
argc = 1;
argv = g_new (gchar *, 1);
argv[0] = g_strdup ("gqbist");
gtk_init (&argc, &argv);
gtk_rc_parse (gimp_gtkrc ());
gdk_set_use_xshm (gimp_use_xshm ());
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 ());
dialog = gimp_dialog_new (_("G-Qbist 1.10"), "gqbist",
gimp_plugin_help_func, "filters/gqbist.html",
GTK_WIN_POS_MOUSE,
FALSE, TRUE, FALSE,
_("OK"), dialog_ok,
NULL, NULL, NULL, TRUE, FALSE,
_("Load"), dialog_load,
NULL, NULL, NULL, FALSE, FALSE,
_("Save"), dialog_save,
NULL, NULL, NULL, FALSE, FALSE,
_("Cancel"), dialog_cancel,
NULL, NULL, NULL, FALSE, TRUE,
NULL);
gtk_signal_connect (GTK_OBJECT (dialog), "destroy",
GTK_SIGNAL_FUNC (gtk_main_quit),
NULL);
table = gtk_table_new (3, 3, FALSE);
gtk_table_set_row_spacings (GTK_TABLE (table), 5);
gtk_table_set_col_spacings (GTK_TABLE (table), 5);
gtk_container_set_border_width (GTK_CONTAINER (table), 5);
gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox), table,
TRUE, TRUE, 0);
gtk_widget_show (table);
memcpy ((char *) &(info[0]), (char *) &qbist_info, sizeof (s_info));
dialog_new_variations (NULL, NULL);
for (i = 0; i < 9; i++)
{
button = gtk_button_new();
gtk_signal_connect (GTK_OBJECT (button), "clicked",
(GtkSignalFunc) dialog_select_preview,
(gpointer) &(info[(i+5)%9]));
gtk_table_attach (GTK_TABLE (table), button, i%3, (i%3)+1, i/3, (i/3)+1,
GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0);
gtk_widget_show (button);
preview[i] = gtk_preview_new (GTK_PREVIEW_COLOR);
gtk_preview_size (GTK_PREVIEW (preview[i]), PREVIEW_SIZE, PREVIEW_SIZE);
gtk_container_add (GTK_CONTAINER (button), preview[i]);
gtk_widget_show (preview[i]);
}
dialog_update_previews (NULL, NULL);
gtk_widget_show (dialog);
result = FALSE;
gtk_main ();
gdk_flush ();
if (result)
memcpy ((char *) &qbist_info, (char *) &(info[0]), sizeof (s_info));
return result;
}