Commit f3772a07 authored by ROOL's avatar ROOL 🤖
Browse files

Initial alpha transparency editing support

Detail:
  * Allow creation of new format sprites:
    - Alpha mask sprites
    - Alpha channel sprites
  * Enhanced Brush tool ideas:
    - Alpha channel sprite support + sprite brushes effectively provide
      airbrush support
  New brushes are provided in the brush tool with Gaussian blur.

  By relying on the underlying OS_SpriteOp calls, only RGB blending of alpha
  sprites is supported; when you paint over a transparent or translucent
  region of a sprite, it always turns fully opaque, except when using the
  Transparent colour.

  On an alpha masked sprite, painting in the transparent colour always turns
  the affected pixels fully transparent.
Admin:
  Submmission for Paint bounty.

Version 2.46. Tagged as 'Paint-2_46'
parent 37b54dd3
No preview for this file type
No preview for this file type
......@@ -9,12 +9,12 @@
GBLS Module_ApplicationDate
GBLS Module_HelpVersion
GBLS Module_ComponentName
Module_MajorVersion SETS "2.45"
Module_Version SETA 245
Module_MajorVersion SETS "2.46"
Module_Version SETA 246
Module_MinorVersion SETS ""
Module_Date SETS "16 Oct 2021"
Module_ApplicationDate SETS "16-Oct-21"
Module_Date SETS "12 Feb 2022"
Module_ApplicationDate SETS "12-Feb-22"
Module_ComponentName SETS "Paint"
Module_FullVersion SETS "2.45"
Module_HelpVersion SETS "2.45 (16 Oct 2021)"
Module_FullVersion SETS "2.46"
Module_HelpVersion SETS "2.46 (12 Feb 2022)"
END
/* (2.45)
/* (2.46)
*
* This file is automatically maintained by srccommit, do not edit manually.
*
*/
#define Module_MajorVersion_CMHG 2.45
#define Module_MajorVersion_CMHG 2.46
#define Module_MinorVersion_CMHG
#define Module_Date_CMHG 16 Oct 2021
#define Module_Date_CMHG 12 Feb 2022
#define Module_MajorVersion "2.45"
#define Module_Version 245
#define Module_MajorVersion "2.46"
#define Module_Version 246
#define Module_MinorVersion ""
#define Module_Date "16 Oct 2021"
#define Module_Date "12 Feb 2022"
#define Module_ApplicationDate "16-Oct-21"
#define Module_ApplicationDate "12-Feb-22"
#define Module_ComponentName "Paint"
#define Module_FullVersion "2.45"
#define Module_HelpVersion "2.45 (16 Oct 2021)"
#define Module_LibraryVersionInfo "2:45"
#define Module_FullVersion "2.46"
#define Module_HelpVersion "2.46 (12 Feb 2022)"
#define Module_LibraryVersionInfo "2:46"
......@@ -2457,10 +2457,20 @@ void menus_sprite_handler (void *handle, char *hit)
ftracef1 ("%s mask\n", create? "Create" : "Delete");
if (create)
if (!menus_ensure_size (sarea, psprite_address (sprite)->next))
{ int bytesneeded;
psprite_info sinfo;
psprite_read_full_info (sprite, &sinfo);
/* If a wide mask will be created, simply doubling the sprite size may not be enough */
if (sinfo.mode & 0x80000000)
bytesneeded = psprite_size (sinfo.width, sinfo.height, sinfo.mode, 1, 0) -
psprite_size (sinfo.width, sinfo.height, sinfo.mode, 0, 0);
else
bytesneeded = psprite_address (sprite)->next;
if (!menus_ensure_size (sarea, bytesneeded))
{ msg = msgs_lookup ("PntEG");
goto masked;
}
}
if ((error = (create? sprite_create_mask: sprite_remove_mask)
(*sarea, &sid)) != NULL)
......@@ -2468,6 +2478,11 @@ void menus_sprite_handler (void *handle, char *hit)
goto masked;
}
if (!create && (psprite_address (sprite)->mode & 0x80000000))
{ /* Unset wide mask flag as it can cause maskless sprites not to render correctly */
psprite_address (sprite)->mode &= ~0x80000000;
}
if (sprite->colourdialogue == 0) colours_set_extent (sprite);
menus_ensure_size (sarea, 0);
sprwindow_invalidate (sprite);
......
......@@ -64,6 +64,7 @@
#include <stdint.h>
#include <swis.h>
#include "Global/VduExt.h"
#include "Global/Sprite.h"
#include "bbc.h"
#include "colourtran.h"
......@@ -123,10 +124,12 @@ static wimp_w Source; /* handle of the source window */
static unsigned Mode; /*just a cache to avoid flicker*/
static menu colours_menu = 0;
static menu palette_menu = 0;
static menu transparency_menu = 0;
static menu last_menu = 0;
static main__menu_str dummy;
static int selected_colours = 0;
static palette_type selected_palette = palette_type_none;
static transparency_type selected_transparency = transparency_type_onoffmask;
/*mode numbers to use for x x y modes (indexed by lb_bpp)*/
static int
......@@ -177,6 +180,57 @@ BOOL psprite_hasmask (main_sprite *sprite)
return addr->image != addr->mask;
}
/************************************
* Has the sprite got an alpha mask *
***********************************/
BOOL psprite_hasalphamask (main_sprite *sprite)
{ return psprite_transparency_type (sprite) == transparency_type_alphamask;
}
/***************************************
* Has the sprite got an alpha channel *
**************************************/
BOOL psprite_hasalphachannel (main_sprite *sprite)
{ return psprite_transparency_type (sprite) == transparency_type_alphachannel;
}
/***************************************
* Is alpha supported on this sprite *
**************************************/
BOOL psprite_canalphablend (main_sprite *sprite)
{ transparency_type tt = psprite_transparency_type (sprite);
return tt == transparency_type_alphachannel || tt == transparency_type_alphamask;
}
/***************************************
* Does the OS support alpha masks *
**************************************/
BOOL psprite_havealphamasks (void)
{ /*If the OS supports an alpha mask in a 2 colour mode, we'll assume
they're supported in all the available colour depths*/
/*A return value of 5 indicates the kernel has used the fallback of 32bpp
indicating the alpha mask isn't supported.*/
return bbc_modevar (0x881680b5u, bbc_Log2BPP) != 5;
}
/***************************************
* Does the OS support alpha channels *
**************************************/
BOOL psprite_havealphachannels (void)
{ /* Check if 32k-colour alpha channels are supported, as 4k colour may be unavailable */
return bbc_modevar (0x78508051u, bbc_Log2BPP) != 5;
}
/********************************
* Calculate sizeof a sprite *
********************************/
......@@ -1392,7 +1446,7 @@ os_error *psprite_plot_scaled_m (int x, int y, main_sprite *sprite,
flags = mode;
table = NULL;
}
/* Opacity, TODO use alpha instead if supported: */
/* Opacity */
flags |= translucency << 8;
ftracef0 ("putting sprite scaled ...\n");
......@@ -2691,6 +2745,7 @@ static void Decode (int *lb_bpp_out, unsigned int *mode_out)
wierdly set multiple icons in the same E S G*/
int x_eig = 0, y_eig = 0, lb_bpp = 0;
unsigned int mode;
BOOL force_mode_word = FALSE;
ftracef0 ("Decode\n");
......@@ -2729,9 +2784,12 @@ static void Decode (int *lb_bpp_out, unsigned int *mode_out)
}
ftracef1 ("y_eig %d\n", y_eig);
force_mode_word = selected_transparency == transparency_type_alphamask ||
selected_transparency == transparency_type_alphachannel;
/*So, is this a mode?*/
mode = ~0u;
if (lb_bpp <= 3)
if (!force_mode_word && lb_bpp <= 3)
{ if (x_eig == 1 && y_eig == 1)
mode = modes_1x1 [lb_bpp];
else if (x_eig == 1 && y_eig == 2)
......@@ -2748,6 +2806,12 @@ static void Decode (int *lb_bpp_out, unsigned int *mode_out)
mode = (10 << 27) | 180u >> y_eig << 14 | 180u >> x_eig << 1 | 1u;
else
mode = lb_bpp + 1 << 27 | 180u >> y_eig << 14 | 180u >> x_eig << 1 | 1u;
/* Do we want an alpha mask? */
if (selected_transparency == transparency_type_alphamask)
{ ftracef0 ("Alpha mask specified.\n");
mode |= 0x80000000;
}
ftracef1 ("mode 0x%X\n", mode);
}
else
......@@ -2812,6 +2876,11 @@ static menu alter_popup_menu(void *handle)
last_menu = palette_menu;
help_register_handler(help_simplehandler, "NEWPALH");
return calculate_menu_position (palette_menu, &m);
case d_Create_Transparency_Button:
last_menu = transparency_menu;
help_register_handler(help_simplehandler, "NEWTRAH");
return calculate_menu_position (transparency_menu, &m);
}
}
......@@ -2834,9 +2903,11 @@ static void create_menu_handler( void *handle, char *items)
{ int lb_bpp;
unsigned mode;
handle = handle;
BOOL extd_16bpp = Have4k64k();
BOOL extd_16bpp = Have4k64k ();
/*Check for Medusa hardware and up (supports colours > 256).*/
BOOL gt256cols = (bbc_modevar (6 << 27 | 1, bbc_Log2BPP) != -1);
BOOL havealphachannels = psprite_havealphachannels ();
BOOL havealphamasks = psprite_havealphamasks ();
/* First make absolutely sure nothing invalid is selected */
if (!extd_16bpp && (selected_colours == 4096 || selected_colours == 65536))
......@@ -2851,6 +2922,18 @@ static void create_menu_handler( void *handle, char *items)
selected_colours = 256;
}
if (!havealphamasks && selected_transparency == transparency_type_alphamask)
{ ftracef1 ("Unsupported transparency selection %d, alpha mask not permitted.\n",
selected_transparency);
selected_transparency = transparency_type_onoffmask;
}
if (!havealphachannels && selected_transparency == transparency_type_alphachannel)
{ ftracef1 ("Unsupported transparency selection %d, alpha channel not permitted.\n",
selected_transparency);
selected_transparency = transparency_type_onoffmask;
}
ftracef0 ("Create_Menu_Handler\n");
if (Create == NULL)
......@@ -2858,7 +2941,7 @@ static void create_menu_handler( void *handle, char *items)
return;
}
if (last_menu == colours_menu || last_menu == palette_menu)
if (last_menu == colours_menu || last_menu == palette_menu || last_menu == transparency_menu)
{ Decode (&lb_bpp, &mode);
int item = items[0] - 1;
if (item > 99)
......@@ -2892,8 +2975,7 @@ static void create_menu_handler( void *handle, char *items)
}
switch (item)
{
case 0:
{ case 0:
dbox_setfield (Create, d_Create_Colours, text);
selected_colours = 2;
break;
......@@ -2932,7 +3014,7 @@ static void create_menu_handler( void *handle, char *items)
menu_setflags(colours_menu, i, item == i - 1, (i > 4 &&
selected_palette != palette_type_none) || (fixed_palette && i != 4));
}
else /* (last_menu == palette_menu) */
else if (last_menu == palette_menu)
{ char tag[9], *text;
sprintf (tag, "NEWPAL%d", item);
text = msgs_lookup (tag);
......@@ -2942,8 +3024,7 @@ static void create_menu_handler( void *handle, char *items)
if (text[0] == '|')
text++;
switch (item)
{
case 0:
{ case 0:
dbox_setfield (Create, d_Create_Palette, text);
selected_palette = palette_type_none;
break;
......@@ -2966,7 +3047,47 @@ static void create_menu_handler( void *handle, char *items)
}
for (int i = 1; i < 6; ++i)
menu_setflags(palette_menu, i, item == i - 1, (i > 3 &&
selected_colours != 256) || (i > 1 && selected_colours > 256));
selected_colours != 256) || (i > 1 && selected_colours > 256) ||
(i > 1 && selected_transparency == transparency_type_alphachannel));
}
else /* (last_menu == transparency_menu) */
{ char tag[9], *text;
sprintf (tag, "NEWTRA%d", item);
text = msgs_lookup (tag);
if (!text || strcmp (text, tag) == 0)
return;
/* Prevent unsupported selections */
if (!havealphamasks && item == 2)
{ ftracef1 ("Unsupported transparency item %d, alpha masks not permitted.\n", item);
item = 1;
}
else if (!havealphachannels && item == 3)
{ ftracef1 ("Unsupported transparency item %d, alpha channels not permitted.\n", item);
item = 1;
}
switch (item)
{ case 0:
dbox_setfield (Create, d_Create_Transparency, text);
selected_transparency = transparency_type_none;
break;
case 1:
dbox_setfield (Create, d_Create_Transparency, text);
selected_transparency = transparency_type_onoffmask;
break;
case 2:
dbox_setfield (Create, d_Create_Transparency, text);
selected_transparency = transparency_type_alphamask;
break;
case 3:
dbox_setfield (Create, d_Create_Transparency, text);
selected_transparency = transparency_type_alphachannel;
break;
}
for (int i = 1; i < 5; ++i)
menu_setflags(transparency_menu, i, item == i - 1, (i == 4 &&
(selected_palette != palette_type_none || selected_colours < 4096 || selected_colours == 65536)));
}
/* Update palette menu flags */
......@@ -2978,11 +3099,15 @@ static void create_menu_handler( void *handle, char *items)
}
/* Enable the 64 & 16 entry fixed options for 256 colours only */
menu_setflags(palette_menu, 4, selected_palette == palette_type_64fixed, selected_colours != 256);
menu_setflags(palette_menu, 5, selected_palette == palette_type_16fixed, selected_colours != 256);
/* Disable all palette types (except None) for > 256 colours */
menu_setflags(palette_menu, 2, selected_palette == palette_type_colour, selected_colours > 256);
menu_setflags(palette_menu, 3, selected_palette == palette_type_greyscale, selected_colours > 256);
menu_setflags(palette_menu, 4, selected_palette == palette_type_64fixed, (selected_colours != 256) ||
(selected_transparency == transparency_type_alphachannel));
menu_setflags(palette_menu, 5, selected_palette == palette_type_16fixed, (selected_colours != 256) ||
(selected_transparency == transparency_type_alphachannel));
/* Disable all palette types (except None) for > 256 colours and for alpha channel */
menu_setflags(palette_menu, 2, selected_palette == palette_type_colour, (selected_colours > 256) ||
(selected_transparency == transparency_type_alphachannel));
menu_setflags(palette_menu, 3, selected_palette == palette_type_greyscale, (selected_colours > 256) ||
(selected_transparency == transparency_type_alphachannel));
/* Update colours menu flags */
......@@ -3036,6 +3161,37 @@ static void create_menu_handler( void *handle, char *items)
menu_setflags(colours_menu, 7, selected_colours == 65536, 1);
menu_setflags(colours_menu, 8, selected_colours == 16777216, 1);
}
/* Update transparency menu flags */
if (selected_palette != palette_type_none || selected_colours < 4096 ||
selected_colours == 65536)
{ /* Fade alpha channel if there's a palette or colours are not 4k, 32k or 16M */
menu_setflags(transparency_menu, 4, FALSE, 1);
}
else
{ /* Otherwise enable alpha channel, if supported */
menu_setflags(transparency_menu, 4, selected_transparency ==
transparency_type_alphachannel, !havealphachannels);
}
/* Fade alpha masks if they're not supported */
menu_setflags(transparency_menu, 3, selected_transparency ==
transparency_type_alphamask, !havealphamasks);
if (selected_transparency == transparency_type_alphachannel)
{ /* Fade all colours except 4k, 32k and 16M */
menu_setflags(colours_menu, 1, selected_colours == 2, 1);
menu_setflags(colours_menu, 2, selected_colours == 4, 1);
menu_setflags(colours_menu, 3, selected_colours == 16, 1);
menu_setflags(colours_menu, 4, selected_colours == 256, 1);
menu_setflags(colours_menu, 7, selected_colours == 65536, 1);
/* Palette options were already faded higher up */
}
else
{ /* Just need to unfade 256 colours here because that wasn't disabled or enabled in any of the other logic */
menu_setflags(colours_menu, 4, selected_colours == 256, 0);
}
}
}
......@@ -3043,6 +3199,7 @@ static void make_popup_menus (void)
{ char init_colours[255];
char init_palette[255];
char init_transparency[255];
char *msg;
int i = 0, len;
......@@ -3054,9 +3211,13 @@ static void make_popup_menus (void)
if (palette_menu != NULL)
menu_dispose (&palette_menu, 0);
if (transparency_menu != NULL)
menu_dispose (&transparency_menu, 0);
/* Read menu text from Messages */
strcpy (init_colours, "");
strcpy (init_palette, "");
strcpy (init_transparency, "");
i = 0;
len = 0;
......@@ -3107,13 +3268,43 @@ static void make_popup_menus (void)
if (len > 0)
init_palette[len - 1] = '\0';
i = 0;
len = 0;
while (i < 100)
{ char tag[9];
sprintf (tag, "NEWTRA%d", i);
msg = msgs_lookup (tag);
if (!msg || strcmp (msg, tag) == 0)
break;
/* Allow a | separator. Remove comma, if found */
if (msg[0] == '|' && len > 0)
{ init_transparency[len - 1] = '\0';
len--;
}
len += strlen (msg) + 1;
if (len >= 255)
{ werr (TRUE, "Transparency menu items text too long.");
break;
}
strcat (init_transparency, msg);
strcat (init_transparency, ",");
i++;
}
/* Remove the trailing comma */
if (len > 0)
init_transparency[len - 1] = '\0';
ftracef1 ("Colours menu string: '%s'.\n", init_colours);
ftracef1 ("Palette menu string: '%s'.\n", init_palette);
ftracef1 ("Transparency menu string: '%s'.\n", init_transparency);
colours_menu = menu_new (msgs_lookup ("PntWC"), init_colours);
palette_menu = menu_new (msgs_lookup ("PntG5"), init_palette);
transparency_menu = menu_new (msgs_lookup ("PntG2"), init_transparency);
/* We don't set menu flags here. They will be handled afterwards. */
event_attachmenumaker (dbox_syshandle (Create), alter_popup_menu, create_menu_handler, NULL);
......@@ -3212,7 +3403,7 @@ static void create_create_sprite (dbox d, main_window *window)
int width, height, sprite_size, white /*GCOL for white pixels*/,
lb_bpp, *new_pal, e = 0, which_pal;
unsigned mode;
BOOL want_palette, want_mask, mono;
BOOL want_palette, want_mask, mono, want_alpha_mask, want_alpha_chan;
sprite_id sid;
main_sprite *sprite;
psprite_info info;
......@@ -3242,7 +3433,9 @@ static void create_create_sprite (dbox d, main_window *window)
want_palette = lb_bpp <= 3 && selected_palette != palette_type_none;
mono = want_palette && selected_palette == palette_type_greyscale;
want_mask = !dbox_getnumeric (d, d_Create_Mask);
want_mask = selected_transparency != transparency_type_none;
want_alpha_mask = selected_transparency == transparency_type_alphamask;
want_alpha_chan = selected_transparency == transparency_type_alphachannel;
#if TRACE
ftracef (__FILE__, __LINE__,
......@@ -3270,6 +3463,9 @@ static void create_create_sprite (dbox d, main_window *window)
goto finish;
}
/* Unset wide mask bit as alpha mask will be added separately */
mode &= ~0x80000000;
ftracef0 ("creating sprite\n");
if ((error = sprite_create_rp (*sarea, name, sprite_nopalette, width,
height, mode, (sprite_ptr *) &addr)) != NULL)
......@@ -3277,6 +3473,10 @@ static void create_create_sprite (dbox d, main_window *window)
goto finish;
}
/* Put wide mask bit back */
if (want_alpha_mask)
addr->mode |= 0x80000000;
/*Just checking ...*/
ftracef1 ("new sprite mode is %d\n", addr->mode);
......@@ -3340,13 +3540,41 @@ static void create_create_sprite (dbox d, main_window *window)
#endif
}
sid.s.name = name;
sid.tag = sprite_id_name;
if (want_mask)
if ((error = sprite_create_mask (*sarea, &sid)) != NULL)
/* Add an alpha channel or alpha mask if wanted and available */
if (/*want_alpha_mask || Now handled by sprite_create_mask() else SprExtend crashes!*/ want_alpha_chan)
{ int flags = 0;
int space_needed = 0;
if (want_alpha_mask)
{ /* Convert to 8bpp alpha mask */
ftracef0 ("About to convert to alpha mask.\n");
flags = 0x80000000;
}
else if (want_alpha_chan)
{ /*Convert to RISC OS 5-compatible alpha channel*/
ftracef0 ("About to convert to alpha channel.\n");
flags = 0xC0000000;
}
error = os_swix4r (OS_SpriteOp, SpriteReason_CreateRemoveAlpha | 0x200, (int)*sarea, (int)addr, flags,
NULL, NULL, NULL, &space_needed);
if (error != NULL)
{ /* If r3 > 0 it will give amount of extra space needed in area */
if (space_needed > 0)
ftracef1("More space needed for alpha, %d.", space_needed);
msg = error->errmess;
goto finish;
}
ftracef0 ("Alpha conversion succeeded.\n");
}
else if (want_mask)
{ sid.s.name = name;
sid.tag = sprite_id_name;
error = sprite_create_mask (*sarea, &sid);
if (error != NULL)
{ msg = error->errmess;
goto finish;
}
}
/* Now shrink the sprite area to be exactly the right size */
menus_ensure_size (sarea, 0);
......@@ -3590,6 +3818,8 @@ static void Create_Handler (wimp_eventstr *e, void *handle)
{ case d_Create_Colours_Button:
/* Fall through */
case d_Create_Palette_Button:
/* Fall through */
case d_Create_Transparency_Button:
{ wimp_eventstr fake;
fake.e = wimp_EBUT;
fake.data.but.m.bbits = wimp_BMID;
......@@ -3623,6 +3853,10 @@ void psprite_create_show (main_window *window, BOOL auto_open,
{ menu_dispose (&palette_menu, 1);
palette_menu = NULL;
}
if (transparency_menu)
{ menu_dispose (&transparency_menu, 1);
transparency_menu = NULL;
}
}
if ((Create = dbox_new ("create")) == NULL)
......@@ -3654,7 +3888,6 @@ void psprite_create_show (main_window *window, BOOL auto_open,
x_eig, y_eig, lb_bpp, grey_scale? "TRUE": "FALSE");
dbox_setfield (Create, d_Create_Name, sprite_name);
dbox_setnumeric (Create, d_Create_Mask, FALSE);
dbox_setnumeric (Create, d_Create_Width, width);
dbox_setnumeric (Create, d_Create_XEIG_2, x_eig == 2);
dbox_setnumeric (Create, d_Create_XEIG_1, x_eig == 1);
......@@ -3763,6 +3996,11 @@ void psprite_create_show (main_window *window, BOOL auto_open,
menu_setflags(colours_menu, 7, 0, 1); /*64k*/
}
/* Simple mask */
selected_transparency = transparency_type_onoffmask;
dbox_setfield (Create, d_Create_Transparency, msgs_lookup ("NEWTRA1"));
menu_setflags(transparency_menu, 2, 1, 0);
Decode (&lb_bpp, &Mode);
if ((unsigned) Mode < 256u)
......@@ -3797,6 +4035,12 @@ void psprite_create_show (main_window *window, BOOL auto_open,
menu_setflags(colours_menu, 8, 0, 1);
}
/* Disable any unavailable transparency options here */
if (!psprite_havealphamasks ())
menu_setflags(transparency_menu, 3, 0, 1);
if (!psprite_havealphachannels ())
menu_setflags(transparency_menu, 4, 0, 1);
if (auto_open == -1) /* open from menu branch */
{ BOOL open;
......@@ -3902,3 +4146,18 @@ BOOL HaveBlendTable (void)
}
return haveit != 0;
}
/* Returns true if the version of SpriteExtend present has a bug *
* that requires additional space in the sprite area to make an *
* alpha mask */
BOOL psprite_need_convertalpha_fix (void)
{ static int needfix = -1;
if (needfix == -1)
{ /* TODO Update the version number below when the bug has been fixed in SpriteExtend */
os_error *err = os_cli("RMEnsure SpriteExtend 1.86");
needfix = (err != NULL);
}
return needfix != 0;
}
......@@ -103,6 +103,7 @@ static main_sprite *brushpane_icon_sprites [tools_MAX_BRUSH_ICONS];
static int num_user_brushes = 0;
static int num_brushpane_icons = 0;
static int selected_brushpane_icon = -1;
static int number_of_builtin_brushes = -1;
static char *onlydigits = "A0-9;Kt";
static char *allkeys = "Kndt";
......@@ -425,14 +426,21 @@ static int get_number_of_builtin_brushes (void)
{ /* Messages tells us how many built-in brush sprites are supplied in
the Paint:Sprites file, capped at a maximum of
tools_MAX_BUILTIN_BRUSHES for the brush menu */
int num_sys_brushes;
char *