[WIP] Starter/Template scale/smear/bkgd color options
Now possible to set scaling and smearing options for each starter or template image. A ".dat" file can be created which describes whether it's okay to scale and crop the image's top/bottom and/or left/right, and if not both, whether to smear the sides of the starter/template to the edges of the canvas (the prior behavior, and the default), or use a specific solid color background. Once finished, will close https://sourceforge.net/p/tuxpaint/feature-requests/190/ Still to do: + Document + Remove debugging printf()s (change to DEBUG_PRINTF()?) + Add files for starters & templates that ship with Tux Paint + Test!
This commit is contained in:
parent
102bb825aa
commit
9b93805ef6
4 changed files with 325 additions and 3 deletions
|
|
@ -7,7 +7,7 @@ Various contributors (see below, and AUTHORS.txt)
|
|||
https://tuxpaint.org/
|
||||
|
||||
|
||||
2023.February.9 (0.9.29)
|
||||
2023.February.10 (0.9.29)
|
||||
* Improvements to "Stamp" tool:
|
||||
-----------------------------
|
||||
* Stamps may now be rotated.
|
||||
|
|
@ -66,6 +66,24 @@ https://tuxpaint.org/
|
|||
-----------
|
||||
* Space_draw.svg by Ingrid Illa Terrier
|
||||
|
||||
* Starter and Template Improvements
|
||||
---------------------------------
|
||||
* [WIP] Now possible to set scaling and smearing options for
|
||||
each starter or template image. A ".dat" file can be
|
||||
created which describes whether it's okay to scale and crop
|
||||
the image's top/bottom and/or left/right, and if not both,
|
||||
whether to smear the sides of the starter/template to the
|
||||
edges of the canvas (the prior behavior, and the default),
|
||||
or use a specific solid color background.
|
||||
Closes https://sourceforge.net/p/tuxpaint/feature-requests/190/
|
||||
Bill Kendrick <bill@newbreedsoftware.com>
|
||||
|
||||
TODO:
|
||||
+ Document
|
||||
+ Remove debugging printf()s (change to DEBUG_PRINTF()?)
|
||||
+ Add files for starters & templates that ship with Tux Paint
|
||||
+ Test!
|
||||
|
||||
* Other Improvements:
|
||||
-------------------
|
||||
* A keyboard shortcut is now available for quickly accessing
|
||||
|
|
|
|||
306
src/tuxpaint.c
306
src/tuxpaint.c
|
|
@ -22,7 +22,7 @@
|
|||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
(See COPYING.txt)
|
||||
|
||||
June 14, 2002 - January 25, 2023
|
||||
June 14, 2002 - February 10, 2023
|
||||
*/
|
||||
|
||||
#include "platform.h"
|
||||
|
|
@ -151,6 +151,19 @@ static scaleparams scaletable[] = {
|
|||
{48, 1}, /* 48 */
|
||||
};
|
||||
|
||||
enum {
|
||||
STARTER_TEMPLATE_SCALE_MODE_NONE, /* smear or apply background color */
|
||||
STARTER_TEMPLATE_SCALE_MODE_HORIZ, /* allow zooming in (cropping left/right) if image is wider than the canvas */
|
||||
STARTER_TEMPLATE_SCALE_MODE_VERT, /* allow zooming in (cropping top/bottom) if image is taller than the canvas */
|
||||
STARTER_TEMPLATE_SCALE_MODE_BOTH /* allow zooming in (cropping anything) if canvas is smaller in either/both dimensions */
|
||||
};
|
||||
|
||||
typedef struct starter_template_options_s {
|
||||
int scale_mode;
|
||||
int smear;
|
||||
int bkgd_color[3];
|
||||
} starter_template_options_t;
|
||||
|
||||
|
||||
/* Macros: */
|
||||
|
||||
|
|
@ -14212,6 +14225,272 @@ static void autoscale_copy_smear_free(SDL_Surface * src, SDL_Surface * dst,
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* Copy an image, scaling and smearing, as needed, into a new surface.
|
||||
* Free the original surface.
|
||||
*
|
||||
* @param SDL_Surface * src -- source surface (will be freed by this function!)
|
||||
* @param SDL_Surface * dst -- destination surface
|
||||
* @param int SDCALL(*blit) -- function for blitting; "NondefectiveBlit" or "SDL_BlitSurface"
|
||||
* @param starter_template_options_t opts -- options (loaded from ".dat" file) describing strategies to take
|
||||
*/
|
||||
static void autoscale_copy_scale_or_smear_free(SDL_Surface * src, SDL_Surface * dst,
|
||||
int SDLCALL(*blit) (SDL_Surface * src,
|
||||
const SDL_Rect * srcrect,
|
||||
SDL_Surface * dst,
|
||||
SDL_Rect * dstrect),
|
||||
starter_template_options_t opts) {
|
||||
int new_w, new_h;
|
||||
float src_aspect, dst_aspect;
|
||||
|
||||
new_w = src->w;
|
||||
new_h = src->h;
|
||||
|
||||
src_aspect = (float) src->w / (float) src->h;
|
||||
dst_aspect = (float) dst->w / (float) dst->h;
|
||||
|
||||
if (src_aspect > dst_aspect) {
|
||||
printf("Image (%d x %d) is of a wider aspect (%0.5f) than canvas (%d x %d) (%0.5f)\n", src->w, src->h, src_aspect, dst->w, dst->h, dst_aspect);
|
||||
if (opts.scale_mode == STARTER_TEMPLATE_SCALE_MODE_HORIZ ||
|
||||
opts.scale_mode == STARTER_TEMPLATE_SCALE_MODE_BOTH) {
|
||||
new_h = dst->h;
|
||||
new_w = dst->h * src_aspect;
|
||||
printf("Okay to crop left/right. Keeping aspect; scaling to %d x %d\n", new_w, new_h);
|
||||
}
|
||||
} else if (src_aspect < dst_aspect) {
|
||||
printf("Image (%d x %d) is of a taller aspect (%0.5f) than canvas (%d x %d) (%0.5f)\n", src->w, src->h, src_aspect, dst->w, dst->h, dst_aspect);
|
||||
if (opts.scale_mode == STARTER_TEMPLATE_SCALE_MODE_VERT ||
|
||||
opts.scale_mode == STARTER_TEMPLATE_SCALE_MODE_BOTH) {
|
||||
new_w = dst->w;
|
||||
new_h = dst->w / src_aspect;
|
||||
printf("Okay to crop top/bottom. Keeping aspect; scaling to %d x %d\n", new_w, new_h);
|
||||
}
|
||||
} else {
|
||||
printf("Image (%d x %d) is the same aspect as canvas (%d x %d) (%0.05f)\n", src->w, src->h, dst->w, dst->h, src_aspect);
|
||||
}
|
||||
|
||||
|
||||
/* Scale and crop based on any aspect-ratio-keeping adjustments */
|
||||
if (new_w != src->w || new_h != src->h) {
|
||||
SDL_Surface * scaled, * src1;
|
||||
SDL_Rect src_rect;
|
||||
|
||||
/* Scale, keeping aspect, which will cause extra content that needs cropping */
|
||||
|
||||
printf("Scaling from %d x %d to %d x %d\n", src->w, src->h, new_w, new_h);
|
||||
|
||||
scaled = thumbnail2(src, new_w, new_h, 0 /* keep aspect */, 1 /* keep alpha */);
|
||||
if (scaled == NULL) {
|
||||
fprintf(stderr, "Failed to scale an image!\n");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Create a new surface to blit (crop) into */
|
||||
src1 = SDL_CreateRGBSurface(src->flags, /* SDL_SWSURFACE, */
|
||||
dst->w, dst->h, src->format->BitsPerPixel,
|
||||
src->format->Rmask, src->format->Gmask,
|
||||
src->format->Bmask, src->format->Amask);
|
||||
if (src1 == NULL) {
|
||||
fprintf(stderr, "Failed to create a surface!\n");
|
||||
return;
|
||||
}
|
||||
|
||||
SDL_FreeSurface(src);
|
||||
src = src1;
|
||||
|
||||
/* Place the new image centered onto the dest */
|
||||
src_rect.x = (scaled->w - dst->w) / 2;
|
||||
src_rect.y = (scaled->h - dst->h) / 2;
|
||||
src_rect.w = scaled->w;
|
||||
src_rect.h = scaled->h;
|
||||
|
||||
printf("Blitting scaled image (%d x %d) into new 'src' image (%d x %d) at (%d,%d) %d x %d\n",
|
||||
scaled->w, scaled->h, src->w, src->h, src_rect.x, src_rect.y, src_rect.w, src_rect.h);
|
||||
|
||||
SDL_BlitSurface(scaled, &src_rect, src, NULL);
|
||||
}
|
||||
|
||||
|
||||
if (src->w != dst->w || src->h != dst->h) {
|
||||
printf("Fitting %d x %d onto %d x %d canvas\n", src->w, src->h, dst->w, dst->h);
|
||||
|
||||
if (opts.smear) {
|
||||
printf("Smearing\n");
|
||||
|
||||
autoscale_copy_smear_free(src, dst, blit);
|
||||
/* Note: autoscale_copy_smear_free() calls SDL_FreeSurface(src)! */
|
||||
} else {
|
||||
SDL_Surface * scaled;
|
||||
SDL_Rect dst_rect;
|
||||
|
||||
if (src->w != dst->w || src->h != dst->h) {
|
||||
if (src->w / (float) dst->w > src->h / (float) dst->h) {
|
||||
printf("Scaling from %d x %d to %d x %d\n", src->w, src->h, dst->w, src->h * dst->w / src->w);
|
||||
scaled = thumbnail(src, dst->w, src->h * dst->w / src->w, 0);
|
||||
} else {
|
||||
printf("Scaling from %d x %d to %d x %d\n", src->w, src->h, src->w * dst->h / src->h, dst->h);
|
||||
scaled = thumbnail(src, src->w * dst->h / src->h, dst->h, 0);
|
||||
}
|
||||
}
|
||||
|
||||
SDL_FreeSurface(src);
|
||||
|
||||
if (scaled == NULL) {
|
||||
fprintf(stderr, "Failed to scale an image!\n");
|
||||
return;
|
||||
}
|
||||
|
||||
printf("Centering on a background color\n");
|
||||
|
||||
SDL_FillRect(dst, NULL, SDL_MapRGB(dst->format, opts.bkgd_color[0], opts.bkgd_color[1], opts.bkgd_color[2]));
|
||||
|
||||
dst_rect.x = (dst->w - scaled->w) / 2;
|
||||
dst_rect.y = (dst->h - scaled->h) / 2;
|
||||
dst_rect.w = scaled->w;
|
||||
dst_rect.h = scaled->h;
|
||||
|
||||
SDL_BlitSurface(scaled, NULL, dst, &dst_rect);
|
||||
|
||||
SDL_FreeSurface(scaled);
|
||||
}
|
||||
} else {
|
||||
printf("No smearing or background needed\n");
|
||||
|
||||
SDL_FreeSurface(src);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Attempt to open a starter/template image's options (".dat")
|
||||
* file and read the settings into an options structure.
|
||||
*
|
||||
* @param char * dirname -- directory source of image
|
||||
* @param char * img_id -- basename of image
|
||||
* @param starter_template_options_t * opts -- pointer to options struct to fill
|
||||
*/
|
||||
static void get_starter_template_options(char * dirname, char * img_id, starter_template_options_t * opts) {
|
||||
char fname[256], buf[256];
|
||||
char * arg;
|
||||
FILE * fi;
|
||||
|
||||
/* Set defaults for all options (in case file missing, or file doesn't specify certain options) */
|
||||
opts->scale_mode = STARTER_TEMPLATE_SCALE_MODE_NONE;
|
||||
opts->smear = 1;
|
||||
opts->bkgd_color[0] = 255;
|
||||
opts->bkgd_color[1] = 255;
|
||||
opts->bkgd_color[2] = 255;
|
||||
|
||||
/* Attempt to open the file */
|
||||
safe_snprintf(fname, sizeof(fname), "%s/%s.dat", dirname, img_id);
|
||||
fi = fopen(fname, "r");
|
||||
if (fi == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
while (!feof(fi)) {
|
||||
if (fgets(buf, sizeof(buf), fi))
|
||||
{
|
||||
if (!feof(fi))
|
||||
{
|
||||
strip_trailing_whitespace(buf);
|
||||
|
||||
if (buf[0] == '\0' || buf[0] == '#') {
|
||||
continue;
|
||||
}
|
||||
|
||||
arg = strchr(buf, '=');
|
||||
if (arg) {
|
||||
*arg++ = '\0';
|
||||
} else {
|
||||
fprintf(stderr, "Don't understand line in '%s': '%s'\n", fname, buf);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (strcmp(buf, "allowscale") == 0) {
|
||||
if (strcmp(arg, "horizontal") == 0) {
|
||||
opts->scale_mode = STARTER_TEMPLATE_SCALE_MODE_HORIZ;
|
||||
} else if (strcmp(arg, "vertical") == 0) {
|
||||
opts->scale_mode = STARTER_TEMPLATE_SCALE_MODE_VERT;
|
||||
} else if (strcmp(arg, "both") == 0) {
|
||||
opts->scale_mode = STARTER_TEMPLATE_SCALE_MODE_BOTH;
|
||||
} else {
|
||||
fprintf(stderr, "Unknown 'autoscale' option in '%s': '%s'\n", fname, arg);
|
||||
}
|
||||
} else if (strcmp(buf, "background") == 0) {
|
||||
if (strcmp(arg, "smear") == 0) {
|
||||
opts->smear = 1;
|
||||
} else {
|
||||
int count;
|
||||
char tmp_str[256];
|
||||
|
||||
opts->smear = 0;
|
||||
|
||||
/* FIXME: This and setup_colors() needs to be modularized -bjk 2023.02.10 */
|
||||
|
||||
if (arg[0] == '#')
|
||||
{
|
||||
/* Hex form */
|
||||
|
||||
sscanf(arg + 1, "%s %n", tmp_str, &count);
|
||||
|
||||
if (strlen(tmp_str) == 6)
|
||||
{
|
||||
printf("6 digit hex ('%s')\n", arg);
|
||||
|
||||
/* Byte (#rrggbb) form */
|
||||
|
||||
opts->bkgd_color[0] =
|
||||
(hex2dec(tmp_str[0]) << 4) + hex2dec(tmp_str[1]);
|
||||
opts->bkgd_color[1] =
|
||||
(hex2dec(tmp_str[2]) << 4) + hex2dec(tmp_str[3]);
|
||||
opts->bkgd_color[2] =
|
||||
(hex2dec(tmp_str[4]) << 4) + hex2dec(tmp_str[5]);
|
||||
}
|
||||
else if (strlen(tmp_str) == 3)
|
||||
{
|
||||
printf("3 digit hex ('%s')\n", arg);
|
||||
|
||||
/* Nybble (#rgb) form */
|
||||
|
||||
opts->bkgd_color[0] =
|
||||
(hex2dec(tmp_str[0]) << 4) + hex2dec(tmp_str[0]);
|
||||
opts->bkgd_color[1] =
|
||||
(hex2dec(tmp_str[1]) << 4) + hex2dec(tmp_str[1]);
|
||||
opts->bkgd_color[2] =
|
||||
(hex2dec(tmp_str[2]) << 4) + hex2dec(tmp_str[2]);
|
||||
} else {
|
||||
printf("Don't understand color hex '%s'\n", arg);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("Integers ('%s')\n", arg);
|
||||
|
||||
/* Assume int form */
|
||||
|
||||
sscanf(arg, "%hu %hu %hu %n",
|
||||
(short unsigned int *) &(opts->bkgd_color[0]),
|
||||
(short unsigned int *) &(opts->bkgd_color[1]),
|
||||
(short unsigned int *) &(opts->bkgd_color[2]),
|
||||
&count);
|
||||
}
|
||||
printf("Background color: %d,%d,%d\n", opts->bkgd_color[0], opts->bkgd_color[1], opts->bkgd_color[2]);
|
||||
}
|
||||
} else {
|
||||
fprintf(stderr, "Unrecognized option in '%s': '%s' (set to '%s')\n", fname, buf, arg);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
fclose(fi);
|
||||
|
||||
/* N.B. If 'allowscale=both', then background options are meaningless;
|
||||
we could report that here (e.g., to stderr) -bjk 2023.02.10 */
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* FIXME
|
||||
*/
|
||||
|
|
@ -14544,6 +14823,7 @@ static void load_template(char *img_id)
|
|||
char *dirname;
|
||||
char fname[256];
|
||||
SDL_Surface *tmp_surf;
|
||||
starter_template_options_t template_options;
|
||||
|
||||
/* Determine path to starter files: */
|
||||
|
||||
|
|
@ -14595,6 +14875,9 @@ static void load_template(char *img_id)
|
|||
SDL_FreeSurface(tmp_surf);
|
||||
}
|
||||
|
||||
/* Get template's options */
|
||||
get_starter_template_options(dirname, img_id, &template_options);
|
||||
|
||||
|
||||
/* Scale if needed... */
|
||||
|
||||
|
|
@ -14611,7 +14894,7 @@ static void load_template(char *img_id)
|
|||
canvas->format->Gmask,
|
||||
canvas->format->Bmask, 0);
|
||||
|
||||
autoscale_copy_smear_free(tmp_surf, img_starter_bkgd, SDL_BlitSurface);
|
||||
autoscale_copy_scale_or_smear_free(tmp_surf, img_starter_bkgd, SDL_BlitSurface, template_options);
|
||||
}
|
||||
|
||||
free(dirname);
|
||||
|
|
@ -14738,6 +15021,11 @@ static void load_current(void)
|
|||
}
|
||||
|
||||
load_embedded_data(fname, org_surf);
|
||||
|
||||
/* FIXME: Consider using the new
|
||||
autoscale_copy_smear_or_scale_free() based on the starter/template
|
||||
file's options? (Will need to do here, rather than first thing,
|
||||
above) -bjk 2023.02.09 */
|
||||
}
|
||||
|
||||
free(fname);
|
||||
|
|
@ -18406,6 +18694,11 @@ static int do_open(void)
|
|||
|
||||
load_embedded_data(fname, org_surf);
|
||||
|
||||
/* FIXME: Consider using the new
|
||||
autoscale_copy_smear_or_scale_free() based on the starter/template
|
||||
file's options? (Will need to do here, rather than first thing,
|
||||
above) -bjk 2023.02.09 */
|
||||
|
||||
reset_avail_tools();
|
||||
|
||||
tool_avail_bak[TOOL_UNDO] = 0;
|
||||
|
|
@ -28274,6 +28567,9 @@ void load_embedded_data(char *fname, SDL_Surface * org_surf)
|
|||
canvas->format->Bmask,
|
||||
0);
|
||||
|
||||
/* FIXME: How to handle starter/template scaling/smearing
|
||||
options!? -bjk 2023.02.10 */
|
||||
|
||||
autoscale_copy_smear_free(aux_surf, img_starter_bkgd,
|
||||
SDL_BlitSurface);
|
||||
}
|
||||
|
|
@ -28355,8 +28651,12 @@ void load_embedded_data(char *fname, SDL_Surface * org_surf)
|
|||
/* 3rd arg ignored for RGBA surfaces */
|
||||
// SDL_SetAlpha(aux_surf, SDL_RLEACCEL, SDL_ALPHA_OPAQUE);
|
||||
SDL_SetSurfaceBlendMode(aux_surf, SDL_BLENDMODE_NONE);
|
||||
|
||||
/* FIXME: How to handle starter/template scaling/smearing
|
||||
options!? -bjk 2023.02.10 */
|
||||
autoscale_copy_smear_free(aux_surf, img_starter,
|
||||
NondefectiveBlit);
|
||||
|
||||
// SDL_SetAlpha(img_starter, SDL_ALPHA_OPAQUE);
|
||||
SDL_SetSurfaceBlendMode(img_starter, SDL_BLENDMODE_NONE);
|
||||
|
||||
|
|
@ -29406,6 +29706,8 @@ static void setup_colors(void)
|
|||
max = max + per;
|
||||
}
|
||||
|
||||
/* FIXME: This and get_starter_template_options() needs to be modularized -bjk 2023.02.10 */
|
||||
|
||||
while (str[strlen(str) - 1] == '\n'
|
||||
|| str[strlen(str) - 1] == '\r')
|
||||
str[strlen(str) - 1] = '\0';
|
||||
|
|
|
|||
1
starters/reef.dat
Normal file
1
starters/reef.dat
Normal file
|
|
@ -0,0 +1 @@
|
|||
allowscale=both
|
||||
1
templates/wool_mill_machine.dat
Normal file
1
templates/wool_mill_machine.dat
Normal file
|
|
@ -0,0 +1 @@
|
|||
allowscale=both
|
||||
Loading…
Add table
Add a link
Reference in a new issue