556 lines
16 KiB
C
556 lines
16 KiB
C
/* PIKA - Photo and Image Kooker Application
|
|
* a rebranding of The GNU Image Manipulation Program (created with heckimp)
|
|
* A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio
|
|
*
|
|
* Original copyright, applying to most contents (license remains unchanged):
|
|
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
|
|
*
|
|
* IfsCompose is a interface for creating IFS fractals by
|
|
* direct manipulation.
|
|
* Copyright (C) 1997 Owen Taylor
|
|
*
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 3 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
|
|
* MERCHANTBILITY 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, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <string.h> /* strlen */
|
|
|
|
#include <gdk/gdk.h>
|
|
|
|
#include <libpika/pika.h>
|
|
|
|
#include "ifs-compose.h"
|
|
|
|
|
|
typedef enum {
|
|
TOKEN_INVALID = G_TOKEN_LAST,
|
|
TOKEN_ITERATIONS,
|
|
TOKEN_MAX_MEMORY,
|
|
TOKEN_SUBDIVIDE,
|
|
TOKEN_RADIUS,
|
|
TOKEN_ASPECT_RATIO,
|
|
TOKEN_CENTER_X,
|
|
TOKEN_CENTER_Y,
|
|
TOKEN_ELEMENT,
|
|
TOKEN_X,
|
|
TOKEN_Y,
|
|
TOKEN_THETA,
|
|
TOKEN_SCALE,
|
|
TOKEN_ASYM,
|
|
TOKEN_SHEAR,
|
|
TOKEN_FLIP,
|
|
TOKEN_RED_COLOR,
|
|
TOKEN_GREEN_COLOR,
|
|
TOKEN_BLUE_COLOR,
|
|
TOKEN_BLACK_COLOR,
|
|
TOKEN_TARGET_COLOR,
|
|
TOKEN_HUE_SCALE,
|
|
TOKEN_VALUE_SCALE,
|
|
TOKEN_SIMPLE_COLOR,
|
|
TOKEN_PROB
|
|
} IfsComposeToken;
|
|
|
|
static struct
|
|
{
|
|
const gchar *name;
|
|
IfsComposeToken token;
|
|
} symbols[] = {
|
|
{ "iterations", TOKEN_ITERATIONS },
|
|
{ "max_memory", TOKEN_MAX_MEMORY },
|
|
{ "subdivide", TOKEN_SUBDIVIDE },
|
|
{ "radius", TOKEN_RADIUS },
|
|
{ "aspect_ratio", TOKEN_ASPECT_RATIO },
|
|
{ "center_x", TOKEN_CENTER_X },
|
|
{ "center_y", TOKEN_CENTER_Y },
|
|
{ "element", TOKEN_ELEMENT },
|
|
{ "x", TOKEN_X },
|
|
{ "y", TOKEN_Y },
|
|
{ "theta", TOKEN_THETA },
|
|
{ "scale", TOKEN_SCALE },
|
|
{ "asym", TOKEN_ASYM },
|
|
{ "shear", TOKEN_SHEAR },
|
|
{ "flip", TOKEN_FLIP },
|
|
{ "red_color", TOKEN_RED_COLOR },
|
|
{ "green_color", TOKEN_GREEN_COLOR },
|
|
{ "blue_color", TOKEN_BLUE_COLOR },
|
|
{ "black_color", TOKEN_BLACK_COLOR },
|
|
{ "target_color", TOKEN_TARGET_COLOR },
|
|
{ "hue_scale", TOKEN_HUE_SCALE },
|
|
{ "value_scale", TOKEN_VALUE_SCALE },
|
|
{ "simple_color", TOKEN_SIMPLE_COLOR },
|
|
{ "prob", TOKEN_PROB }
|
|
};
|
|
|
|
static GTokenType
|
|
ifsvals_parse_color (GScanner *scanner,
|
|
PikaRGB *result)
|
|
{
|
|
GTokenType token;
|
|
|
|
token = g_scanner_get_next_token (scanner);
|
|
if (token != G_TOKEN_LEFT_CURLY)
|
|
return G_TOKEN_LEFT_CURLY;
|
|
|
|
token = g_scanner_get_next_token (scanner);
|
|
if (token == G_TOKEN_FLOAT)
|
|
result->r = scanner->value.v_float;
|
|
else if (token == G_TOKEN_INT)
|
|
result->r = scanner->value.v_int;
|
|
else
|
|
return G_TOKEN_FLOAT;
|
|
|
|
token = g_scanner_get_next_token (scanner);
|
|
if (token != G_TOKEN_COMMA)
|
|
return G_TOKEN_COMMA;
|
|
|
|
token = g_scanner_get_next_token (scanner);
|
|
if (token == G_TOKEN_FLOAT)
|
|
result->g = scanner->value.v_float;
|
|
else if (token == G_TOKEN_INT)
|
|
result->g = scanner->value.v_int;
|
|
else
|
|
return G_TOKEN_FLOAT;
|
|
|
|
token = g_scanner_get_next_token (scanner);
|
|
if (token != G_TOKEN_COMMA)
|
|
return G_TOKEN_COMMA;
|
|
|
|
token = g_scanner_get_next_token (scanner);
|
|
if (token == G_TOKEN_FLOAT)
|
|
result->b = scanner->value.v_float;
|
|
else if (token == G_TOKEN_INT)
|
|
result->b = scanner->value.v_int;
|
|
else
|
|
return G_TOKEN_FLOAT;
|
|
|
|
token = g_scanner_get_next_token (scanner);
|
|
if (token != G_TOKEN_RIGHT_CURLY)
|
|
return G_TOKEN_RIGHT_CURLY;
|
|
|
|
return G_TOKEN_NONE;
|
|
}
|
|
|
|
/* Parse a float which (unlike G_TOKEN_FLOAT) can be negative
|
|
*/
|
|
static GTokenType
|
|
parse_genuine_float (GScanner *scanner,
|
|
gdouble *result)
|
|
{
|
|
gboolean negate = FALSE;
|
|
GTokenType token;
|
|
|
|
token = g_scanner_get_next_token (scanner);
|
|
|
|
if (token == '-')
|
|
{
|
|
negate = TRUE;
|
|
token = g_scanner_get_next_token (scanner);
|
|
}
|
|
|
|
if (token == G_TOKEN_FLOAT)
|
|
{
|
|
*result = negate ? -scanner->value.v_float : scanner->value.v_float;
|
|
return G_TOKEN_NONE;
|
|
}
|
|
else if (token == G_TOKEN_INT)
|
|
{
|
|
*result = negate ? -scanner->value.v_int : scanner->value.v_int;
|
|
return G_TOKEN_NONE;
|
|
}
|
|
else
|
|
return G_TOKEN_FLOAT;
|
|
}
|
|
|
|
static GTokenType
|
|
ifsvals_parse_element (GScanner *scanner,
|
|
AffElementVals *result)
|
|
{
|
|
GTokenType token;
|
|
GTokenType expected_token;
|
|
|
|
token = g_scanner_get_next_token (scanner);
|
|
if (token != G_TOKEN_LEFT_CURLY)
|
|
return G_TOKEN_LEFT_CURLY;
|
|
|
|
token = g_scanner_get_next_token (scanner);
|
|
while (token != G_TOKEN_RIGHT_CURLY)
|
|
{
|
|
switch ((IfsComposeToken) token)
|
|
{
|
|
case TOKEN_X:
|
|
expected_token = parse_genuine_float (scanner, &result->x);
|
|
if (expected_token != G_TOKEN_NONE)
|
|
return expected_token;
|
|
break;
|
|
|
|
case TOKEN_Y:
|
|
expected_token = parse_genuine_float (scanner, &result->y);
|
|
if (expected_token != G_TOKEN_NONE)
|
|
return expected_token;
|
|
break;
|
|
|
|
case TOKEN_THETA:
|
|
expected_token = parse_genuine_float (scanner, &result->theta);
|
|
if (expected_token != G_TOKEN_NONE)
|
|
return expected_token;
|
|
break;
|
|
|
|
case TOKEN_SCALE:
|
|
expected_token = parse_genuine_float (scanner, &result->scale);
|
|
if (expected_token != G_TOKEN_NONE)
|
|
return expected_token;
|
|
break;
|
|
|
|
case TOKEN_ASYM:
|
|
expected_token = parse_genuine_float (scanner, &result->asym);
|
|
if (expected_token != G_TOKEN_NONE)
|
|
return expected_token;
|
|
break;
|
|
|
|
case TOKEN_SHEAR:
|
|
expected_token = parse_genuine_float (scanner, &result->shear);
|
|
if (expected_token != G_TOKEN_NONE)
|
|
return expected_token;
|
|
break;
|
|
|
|
case TOKEN_FLIP:
|
|
token = g_scanner_get_next_token (scanner);
|
|
if (token != G_TOKEN_INT)
|
|
return G_TOKEN_INT;
|
|
|
|
result->flip = scanner->value.v_int;
|
|
break;
|
|
|
|
case TOKEN_RED_COLOR:
|
|
token = ifsvals_parse_color (scanner, &result->red_color);
|
|
if (token != G_TOKEN_NONE)
|
|
return token;
|
|
break;
|
|
|
|
case TOKEN_GREEN_COLOR:
|
|
token = ifsvals_parse_color (scanner, &result->green_color);
|
|
if (token != G_TOKEN_NONE)
|
|
return token;
|
|
break;
|
|
|
|
case TOKEN_BLUE_COLOR:
|
|
token = ifsvals_parse_color (scanner, &result->blue_color);
|
|
if (token != G_TOKEN_NONE)
|
|
return token;
|
|
break;
|
|
|
|
case TOKEN_BLACK_COLOR:
|
|
token = ifsvals_parse_color (scanner, &result->black_color);
|
|
if (token != G_TOKEN_NONE)
|
|
return token;
|
|
break;
|
|
|
|
case TOKEN_TARGET_COLOR:
|
|
token = ifsvals_parse_color (scanner, &result->target_color);
|
|
if (token != G_TOKEN_NONE)
|
|
return token;
|
|
break;
|
|
|
|
case TOKEN_HUE_SCALE:
|
|
expected_token = parse_genuine_float (scanner, &result->hue_scale);
|
|
if (expected_token != G_TOKEN_NONE)
|
|
return expected_token;
|
|
break;
|
|
|
|
case TOKEN_VALUE_SCALE:
|
|
expected_token = parse_genuine_float (scanner, &result->value_scale);
|
|
if (expected_token != G_TOKEN_NONE)
|
|
return expected_token;
|
|
break;
|
|
|
|
case TOKEN_SIMPLE_COLOR:
|
|
token = g_scanner_get_next_token (scanner);
|
|
if (token != G_TOKEN_INT)
|
|
return G_TOKEN_INT;
|
|
|
|
result->simple_color = scanner->value.v_int;
|
|
break;
|
|
|
|
case TOKEN_PROB:
|
|
token = g_scanner_get_next_token (scanner);
|
|
if (token == G_TOKEN_FLOAT)
|
|
result->prob = scanner->value.v_float;
|
|
else if (token == G_TOKEN_INT)
|
|
result->prob = scanner->value.v_int;
|
|
else
|
|
return G_TOKEN_FLOAT;
|
|
|
|
break;
|
|
|
|
default:
|
|
return G_TOKEN_SYMBOL;
|
|
}
|
|
|
|
token = g_scanner_get_next_token (scanner);
|
|
}
|
|
|
|
return G_TOKEN_NONE;
|
|
}
|
|
|
|
/*************************************************************
|
|
* ifsvals_parse:
|
|
* Read in ifsvalues from a GScanner
|
|
* arguments:
|
|
* scanner:
|
|
* vals:
|
|
* elements:
|
|
*
|
|
* results:
|
|
* If parsing succeeded, TRUE; otherwise FALSE, in which
|
|
* case vals and elements are unchanged
|
|
*************************************************************/
|
|
|
|
static gboolean
|
|
ifsvals_parse (GScanner *scanner,
|
|
IfsComposeVals *vals,
|
|
AffElement ***elements)
|
|
{
|
|
GTokenType token, expected_token;
|
|
AffElement *el;
|
|
IfsComposeVals new_vals;
|
|
PikaRGB color;
|
|
|
|
GList *el_list = NULL;
|
|
GList *tmp_list;
|
|
gint i;
|
|
|
|
new_vals = *vals;
|
|
new_vals.num_elements = 0;
|
|
i = 0;
|
|
|
|
expected_token = G_TOKEN_NONE;
|
|
while (expected_token == G_TOKEN_NONE)
|
|
{
|
|
token = g_scanner_get_next_token (scanner);
|
|
|
|
if (g_scanner_eof (scanner))
|
|
break;
|
|
|
|
switch ((IfsComposeToken) token)
|
|
{
|
|
case TOKEN_ITERATIONS:
|
|
token = g_scanner_get_next_token (scanner);
|
|
if (token == G_TOKEN_INT)
|
|
new_vals.iterations = scanner->value.v_int;
|
|
else
|
|
expected_token = G_TOKEN_INT;
|
|
break;
|
|
|
|
case TOKEN_MAX_MEMORY:
|
|
token = g_scanner_get_next_token (scanner);
|
|
if (token == G_TOKEN_INT)
|
|
new_vals.max_memory = scanner->value.v_int;
|
|
else
|
|
expected_token = G_TOKEN_INT;
|
|
break;
|
|
|
|
case TOKEN_SUBDIVIDE:
|
|
token = g_scanner_get_next_token (scanner);
|
|
if (token == G_TOKEN_INT)
|
|
new_vals.subdivide = scanner->value.v_int;
|
|
else
|
|
expected_token = G_TOKEN_INT;
|
|
break;
|
|
|
|
case TOKEN_RADIUS:
|
|
expected_token = parse_genuine_float (scanner, &new_vals.radius);
|
|
break;
|
|
|
|
case TOKEN_ASPECT_RATIO:
|
|
expected_token = parse_genuine_float (scanner, &new_vals.aspect_ratio);
|
|
break;
|
|
|
|
case TOKEN_CENTER_X:
|
|
expected_token = parse_genuine_float (scanner, &new_vals.center_x);
|
|
break;
|
|
|
|
case TOKEN_CENTER_Y:
|
|
expected_token = parse_genuine_float (scanner, &new_vals.center_y);
|
|
break;
|
|
|
|
case TOKEN_ELEMENT:
|
|
el = aff_element_new (0.0,0.0, &color, ++i);
|
|
expected_token = ifsvals_parse_element (scanner, &el->v);
|
|
|
|
if (expected_token == G_TOKEN_NONE)
|
|
{
|
|
el_list = g_list_prepend (el_list, el);
|
|
new_vals.num_elements++;
|
|
}
|
|
else
|
|
aff_element_free (el);
|
|
|
|
break;
|
|
|
|
default:
|
|
expected_token = G_TOKEN_SYMBOL;
|
|
}
|
|
}
|
|
|
|
if (expected_token != G_TOKEN_NONE)
|
|
{
|
|
g_scanner_unexp_token (scanner,
|
|
expected_token,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
"using default values...",
|
|
TRUE);
|
|
g_list_free_full (el_list, (GDestroyNotify) g_free);
|
|
return FALSE;
|
|
}
|
|
|
|
*vals = new_vals;
|
|
|
|
el_list = g_list_reverse (el_list);
|
|
*elements = g_new (AffElement *, new_vals.num_elements);
|
|
|
|
tmp_list = el_list;
|
|
for (i=0; i<new_vals.num_elements; i++)
|
|
{
|
|
(*elements)[i] = tmp_list->data;
|
|
tmp_list = tmp_list->next;
|
|
}
|
|
|
|
g_list_free (el_list);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
ifsvals_parse_string (const gchar *str,
|
|
IfsComposeVals *vals,
|
|
AffElement ***elements)
|
|
{
|
|
GScanner *scanner = g_scanner_new (NULL);
|
|
gboolean result;
|
|
gint i;
|
|
|
|
scanner->config->symbol_2_token = TRUE;
|
|
scanner->config->scan_identifier_1char = TRUE;
|
|
scanner->input_name = "IfsCompose Saved Data";
|
|
|
|
for (i = 0; i < G_N_ELEMENTS (symbols); i++)
|
|
g_scanner_scope_add_symbol (scanner, 0,
|
|
symbols[i].name,
|
|
GINT_TO_POINTER (symbols[i].token));
|
|
|
|
g_scanner_input_text (scanner, str, strlen (str));
|
|
|
|
result = ifsvals_parse (scanner, vals, elements);
|
|
|
|
g_scanner_destroy (scanner);
|
|
|
|
return result;
|
|
}
|
|
|
|
/*************************************************************
|
|
* ifsvals_stringify:
|
|
* Stringify a set of vals and elements
|
|
* arguments:
|
|
* vals:
|
|
* elements
|
|
* results:
|
|
* The stringified result (free with g_free)
|
|
*************************************************************/
|
|
|
|
gchar *
|
|
ifsvals_stringify (IfsComposeVals *vals,
|
|
AffElement **elements)
|
|
{
|
|
gint i;
|
|
gchar buf[G_ASCII_DTOSTR_BUF_SIZE];
|
|
gchar cbuf[3][G_ASCII_DTOSTR_BUF_SIZE];
|
|
GString *result;
|
|
|
|
result = g_string_new (NULL);
|
|
|
|
g_string_append_printf (result, "iterations %d\n", vals->iterations);
|
|
g_string_append_printf (result, "max_memory %d\n", vals->max_memory);
|
|
g_string_append_printf (result, "subdivide %d\n", vals->subdivide);
|
|
g_ascii_dtostr (buf, G_ASCII_DTOSTR_BUF_SIZE, vals->radius);
|
|
g_string_append_printf (result, "radius %s\n", buf);
|
|
g_ascii_dtostr (buf, G_ASCII_DTOSTR_BUF_SIZE, vals->aspect_ratio);
|
|
g_string_append_printf (result, "aspect_ratio %s\n", buf);
|
|
g_ascii_dtostr (buf, G_ASCII_DTOSTR_BUF_SIZE, vals->center_x);
|
|
g_string_append_printf (result, "center_x %s\n", buf);
|
|
g_ascii_dtostr (buf, G_ASCII_DTOSTR_BUF_SIZE, vals->center_y);
|
|
g_string_append_printf (result, "center_y %s\n", buf);
|
|
|
|
for (i=0; i<vals->num_elements; i++)
|
|
{
|
|
g_string_append (result, "element {\n");
|
|
g_ascii_dtostr (buf, G_ASCII_DTOSTR_BUF_SIZE, elements[i]->v.x);
|
|
g_string_append_printf (result, " x %s\n", buf);
|
|
g_ascii_dtostr (buf, G_ASCII_DTOSTR_BUF_SIZE, elements[i]->v.y);
|
|
g_string_append_printf (result, " y %s\n", buf);
|
|
g_ascii_dtostr (buf, G_ASCII_DTOSTR_BUF_SIZE, elements[i]->v.theta);
|
|
g_string_append_printf (result, " theta %s\n", buf);
|
|
g_ascii_dtostr (buf, G_ASCII_DTOSTR_BUF_SIZE, elements[i]->v.scale);
|
|
g_string_append_printf (result, " scale %s\n", buf);
|
|
g_ascii_dtostr (buf, G_ASCII_DTOSTR_BUF_SIZE, elements[i]->v.asym);
|
|
g_string_append_printf (result, " asym %s\n", buf);
|
|
g_ascii_dtostr (buf, G_ASCII_DTOSTR_BUF_SIZE, elements[i]->v.shear);
|
|
g_string_append_printf (result, " shear %s\n", buf);
|
|
g_string_append_printf (result, " flip %d\n", elements[i]->v.flip);
|
|
|
|
g_ascii_dtostr (cbuf[0], G_ASCII_DTOSTR_BUF_SIZE, elements[i]->v.red_color.r);
|
|
g_ascii_dtostr (cbuf[1], G_ASCII_DTOSTR_BUF_SIZE, elements[i]->v.red_color.g);
|
|
g_ascii_dtostr (cbuf[2], G_ASCII_DTOSTR_BUF_SIZE, elements[i]->v.red_color.b);
|
|
g_string_append_printf (result, " red_color { %s,%s,%s }\n",
|
|
cbuf[0], cbuf[1], cbuf[2]);
|
|
|
|
g_ascii_dtostr (cbuf[0], G_ASCII_DTOSTR_BUF_SIZE, elements[i]->v.green_color.r);
|
|
g_ascii_dtostr (cbuf[1], G_ASCII_DTOSTR_BUF_SIZE, elements[i]->v.green_color.g);
|
|
g_ascii_dtostr (cbuf[2], G_ASCII_DTOSTR_BUF_SIZE, elements[i]->v.green_color.b);
|
|
g_string_append_printf (result, " green_color { %s,%s,%s }\n",
|
|
cbuf[0], cbuf[1], cbuf[2]);
|
|
|
|
g_ascii_dtostr (cbuf[0], G_ASCII_DTOSTR_BUF_SIZE, elements[i]->v.blue_color.r);
|
|
g_ascii_dtostr (cbuf[1], G_ASCII_DTOSTR_BUF_SIZE, elements[i]->v.blue_color.g);
|
|
g_ascii_dtostr (cbuf[2], G_ASCII_DTOSTR_BUF_SIZE, elements[i]->v.blue_color.b);
|
|
g_string_append_printf (result, " blue_color { %s,%s,%s }\n",
|
|
cbuf[0], cbuf[1], cbuf[2]);
|
|
|
|
g_ascii_dtostr (cbuf[0], G_ASCII_DTOSTR_BUF_SIZE, elements[i]->v.black_color.r);
|
|
g_ascii_dtostr (cbuf[1], G_ASCII_DTOSTR_BUF_SIZE, elements[i]->v.black_color.g);
|
|
g_ascii_dtostr (cbuf[2], G_ASCII_DTOSTR_BUF_SIZE, elements[i]->v.black_color.b);
|
|
g_string_append_printf (result, " black_color { %s,%s,%s }\n",
|
|
cbuf[0], cbuf[1], cbuf[2]);
|
|
|
|
g_ascii_dtostr (cbuf[0], G_ASCII_DTOSTR_BUF_SIZE, elements[i]->v.target_color.r);
|
|
g_ascii_dtostr (cbuf[1], G_ASCII_DTOSTR_BUF_SIZE, elements[i]->v.target_color.g);
|
|
g_ascii_dtostr (cbuf[2], G_ASCII_DTOSTR_BUF_SIZE, elements[i]->v.target_color.b);
|
|
g_string_append_printf (result, " target_color { %s,%s,%s }\n",
|
|
cbuf[0], cbuf[1], cbuf[2]);
|
|
|
|
g_ascii_dtostr (buf, G_ASCII_DTOSTR_BUF_SIZE, elements[i]->v.hue_scale);
|
|
g_string_append_printf (result, " hue_scale %s\n", buf);
|
|
g_ascii_dtostr (buf, G_ASCII_DTOSTR_BUF_SIZE, elements[i]->v.value_scale);
|
|
g_string_append_printf (result, " value_scale %s\n", buf);
|
|
g_string_append_printf (result, " simple_color %d\n",
|
|
elements[i]->v.simple_color);
|
|
g_ascii_dtostr (buf, G_ASCII_DTOSTR_BUF_SIZE, elements[i]->v.prob);
|
|
g_string_append_printf (result, " prob %s\n", buf);
|
|
g_string_append (result, "}\n");
|
|
}
|
|
|
|
return g_string_free (result, FALSE);
|
|
}
|