Creating a new 50% Gray Layer is a hit or miss

Moderator: Kathy_9

migf1
Posts: 510
Joined: Fri Apr 03, 2020 3:09 pm
operating_system: Windows 8.1
System_Drive: C
32bit or 64bit: 64 Bit

Creating a new 50% Gray Layer is a hit or miss

Post by migf1 »

Hi guys,

I've a recorded script for creating a new raster layer and flood filling it with #808080 (I'm putting it in a code tag below). My problem is that it only works about 70% of the times, or even less (the rest of the time it leaves the layer empty). Any idea why?

Code: Select all

from PSPApp import *

def ScriptProperties():
    return {
        'Author': u'',
        'Copyright': u'',
        'Description': u'',
        'Host': u'PaintShop Pro',
        'Host Version': u'22.00'
        }

def Do(Environment):
    # EnableOptimizedScriptUndo
    App.Do( Environment, 'EnableOptimizedScriptUndo', {
            'GeneralSettings': {
                'ExecutionMode': App.Constants.ExecutionMode.Default, 
                'AutoActionMode': App.Constants.AutoActionMode.Match, 
                'Version': ((22,0,0),1)
                }
            })

    # New Raster Layer
    App.Do( Environment, 'NewRasterLayer', {
            'General': {
                'Opacity': 100, 
                'Name': u'Raster 2', 
                'IsVisible': True, 
                'IsTransparencyLocked': False, 
                'LinkSet': 0, 
                'UseHighlight': False, 
                'PaletteHighlightColor': (255,255,64), 
                'GroupLink': True, 
                'BlendMode': App.Constants.BlendMode.Normal
                }, 
            'BlendRanges': {
                'BlendRangeGreen': (0,0,255,255,0,0,255,255), 
                'BlendRangeRed': (0,0,255,255,0,0,255,255), 
                'BlendRangeBlue': (0,0,255,255,0,0,255,255), 
                'BlendRangeGrey': (0,0,255,255,0,0,255,255)
                }, 
            'GeneralSettings': {
                'ExecutionMode': App.Constants.ExecutionMode.Default, 
                'AutoActionMode': App.Constants.AutoActionMode.Match, 
                'Version': ((22,0,0),1)
                }
            })

    # Fill
    App.Do( Environment, 'Fill', {
            'BlendMode': App.Constants.BlendMode.Normal, 
            'MatchMode': App.Constants.MatchMode.None, 
            'Material': {
                'Color': (128,128,128), 
                'Pattern': None, 
                'Gradient': None, 
                'Texture': None, 
                'Art': None
                }, 
            'UseForeground': True, 
            'Opacity': 100, 
            'Point': (1021.5,335.5), 
            'SampleMerged': False, 
            'Tolerance': 0, 
            'GeneralSettings': {
                'ExecutionMode': App.Constants.ExecutionMode.Default, 
                'AutoActionMode': App.Constants.AutoActionMode.Match, 
                'Version': ((22,0,0),1)
                }
            })

    # Layer Properties
    App.Do( Environment, 'LayerProperties', {
            'General': {
                'Opacity': None, 
                'Name': u'50% Gray', 
                'IsVisible': None, 
                'IsTransparencyLocked': None, 
                'LinkSet': None, 
                'UseHighlight': None, 
                'PaletteHighlightColor': None, 
                'GroupLink': None, 
                'BlendMode': None
                }, 
            'BlendRanges': None, 
            'Path': (0,0,[],False), 
            'ArtMediaTexture': None, 
            'Effects': None, 
            'BrightnessContrast': None, 
            'ChannelMixer': None, 
            'ColorBalance': None, 
            'CurveParams': None, 
            'HSL': None, 
            'Threshold': None, 
            'Levels': None, 
            'Posterize': None, 
            'Vibrancy': None, 
            'Overlay': None, 
            'LocalToneMapping': None, 
            'Invert': None, 
            'HistogramAdjustment': None, 
            'FillLightClarity': None, 
            'GeneralSettings': {
                'ExecutionMode': App.Constants.ExecutionMode.Silent, 
                'AutoActionMode': App.Constants.AutoActionMode.Default, 
                'Version': ((22,0,0),1)
                }
            })
Good Unofficial PaintShop Pro Tutorials: Creation CasselMake Shop ProHEC Image EditingLeviFiction PSP Basics
(plus my own Gimp & Stuff)
LeviFiction
Advisor
Posts: 6774
Joined: Thu Oct 02, 2008 1:07 pm
operating_system: Windows 10
System_Drive: C
32bit or 64bit: 64 Bit
motherboard: Alienware M17xR4
processor: Intel Core i7-3630QM CPU - 2_40GH
ram: 6 GB
Video Card: NVIDIA GeForce GTX 660M
sound_card: Sound Blaster Recon3Di
Hard_Drive_Capacity: 500GB
Corel programs: PSP: 8-2023
Location: USA

Re: Creating a new 50% Gray Layer is a hit or miss

Post by LeviFiction »

The only thing I can think of looking at the code is in the Fill command the Point parameter is set to (1021.5,335.5)

So if that point ever doesn't exist it won't fill the layer. Set this to (1,1) so that it is guaranteed to try filling from the top left corner of the image, a place guaranteed to exist.
migf1
Posts: 510
Joined: Fri Apr 03, 2020 3:09 pm
operating_system: Windows 8.1
System_Drive: C
32bit or 64bit: 64 Bit

Re: Creating a new 50% Gray Layer is a hit or miss

Post by migf1 »

LeviFiction wrote: Thu Mar 04, 2021 4:15 pm The only thing I can think of looking at the code is in the Fill command the Point parameter is set to (1021.5,335.5)

So if that point ever doesn't exist it won't fill the layer. Set this to (1,1) so that it is guaranteed to try filling from the top left corner of the image, a place guaranteed to exist.
That was really fast Levi, thank you very much! I just changed it to 1,1 and now hopefully it will always work! Thanks again!

But now I have a related question :)
Since there is such a parameter in the Fill command, I guess there is no way for a recorded script to work with any image, if we wanted to fill just the currently selected area, instead of the whole layer, right?
Good Unofficial PaintShop Pro Tutorials: Creation CasselMake Shop ProHEC Image EditingLeviFiction PSP Basics
(plus my own Gimp & Stuff)
LeviFiction
Advisor
Posts: 6774
Joined: Thu Oct 02, 2008 1:07 pm
operating_system: Windows 10
System_Drive: C
32bit or 64bit: 64 Bit
motherboard: Alienware M17xR4
processor: Intel Core i7-3630QM CPU - 2_40GH
ram: 6 GB
Video Card: NVIDIA GeForce GTX 660M
sound_card: Sound Blaster Recon3Di
Hard_Drive_Capacity: 500GB
Corel programs: PSP: 8-2023
Location: USA

Re: Creating a new 50% Gray Layer is a hit or miss

Post by LeviFiction »

Not anymore no. Once upon a time the Fill Command would fill a selection no matter where you clicked on the layer. I don't remember when, but at some point they changed this and you had to click within the selection to fill it. This complaint has been registered with Corel for years and they've never seen fit to change it back.

So, this requires the script to be a slight bit more intelligent. You can't just record it, you have to edit it a little. Cassel's scripts, for example, use this clever trick where they get the selection rectangle which defines the boundary of the selection. Then she modifies the selection so that it's only one pixel tall. Grabbing the selection rectangle again the top-left corner of the rectangle matches the starting point of the selection. And that can be used as the Point parameter of the Fill command. She then restores the original selection I believe by doing Undo.

I don't like using Undo as PSP has been shown to occasionally be unstable when using Undo. So I use the JascUtils library to save the selection first, then perform those same steps. I put it into a python function which I can place at the bottom of the script and then call with a single line. Here's the function

Code: Select all

import JascUtils # <- must be placed after "from PSPApp import *"

#... The rest of the script goes here

def GetSelectionPoint(Environment):
    # JascUtils.SaveSelection - has annoying habit of removing the selection after saving so restore it
    sel = JascUtils.SaveSelection(Environment, App.TargetDocument)
    sel.RestoreSelection()

    SelResult = App.Do( Environment, 'GetRasterSelectionRect' )
    if SelResult[ 'Type' ] == App.Constants.SelectionType.None:
        return False
    SelRect = SelResult['Rect']
    start = (SelRect[0][0], SelRect[0][1]+1) #start one pixel from the top.
    end = (SelRect[1]+SelRect[0][0], SelRect[2] + SelRect[0][1])
   
    # Selection
    App.Do( Environment, 'Selection', { 'General': { 'Mode': 2, 'Antialias': False,  'Feather': 0}, 'SelectionShape': 0, 'Start': start, 'End': end, 'SelectionStyle': 0,})
    SelResult = App.Do( Environment, 'GetRasterSelectionRect' )
    if SelResult[ 'Type' ] == App.Constants.SelectionType.None:
        return False
    sel.RestoreSelection()
    return SelResult['Rect'][0]
With that code out of the way I just need to add this to the line to your script to get it to work.

Code: Select all

    point = GetSelectionPoint(Environment)
    if not point:
        point = (1,1)
If you wanted to you could edit the function to return (1,1) on failure instead of False. It would eliminate the need to check the value after calling the function. I personally don't want to receive a point if none exists which is why I have it return False.

As an example of usage here is how your whole script would look put together.

Code: Select all

from PSPApp import *
import JascUtils

def ScriptProperties():
    return {
        'Author': u'',
        'Copyright': u'',
        'Description': u'',
        'Host': u'PaintShop Pro',
        'Host Version': u'22.00'
        }

def Do(Environment):
    # EnableOptimizedScriptUndo
    App.Do( Environment, 'EnableOptimizedScriptUndo', {
            'GeneralSettings': {
                'ExecutionMode': App.Constants.ExecutionMode.Default, 
                'AutoActionMode': App.Constants.AutoActionMode.Match, 
                'Version': ((22,0,0),1)
                }
            })

    # New Raster Layer
    App.Do( Environment, 'NewRasterLayer', {
            'General': {
                'Opacity': 100, 
                'Name': u'Raster 2', 
                'IsVisible': True, 
                'IsTransparencyLocked': False, 
                'LinkSet': 0, 
                'UseHighlight': False, 
                'PaletteHighlightColor': (255,255,64), 
                'GroupLink': True, 
                'BlendMode': App.Constants.BlendMode.Normal
                }, 
            'BlendRanges': {
                'BlendRangeGreen': (0,0,255,255,0,0,255,255), 
                'BlendRangeRed': (0,0,255,255,0,0,255,255), 
                'BlendRangeBlue': (0,0,255,255,0,0,255,255), 
                'BlendRangeGrey': (0,0,255,255,0,0,255,255)
                }, 
            'GeneralSettings': {
                'ExecutionMode': App.Constants.ExecutionMode.Default, 
                'AutoActionMode': App.Constants.AutoActionMode.Match, 
                'Version': ((22,0,0),1)
                }
            })

    point = GetSelectionPoint(Environment)
    if not point:
        point = (1,1)

    # Fill
    App.Do( Environment, 'Fill', {
            'BlendMode': App.Constants.BlendMode.Normal, 
            'MatchMode': App.Constants.MatchMode.None, 
            'Material': {
                'Color': (128,128,128), 
                'Pattern': None, 
                'Gradient': None, 
                'Texture': None, 
                'Art': None
                }, 
            'UseForeground': True, 
            'Opacity': 100, 
            'Point': point, 
            'SampleMerged': False, 
            'Tolerance': 0, 
            'GeneralSettings': {
                'ExecutionMode': App.Constants.ExecutionMode.Default, 
                'AutoActionMode': App.Constants.AutoActionMode.Match, 
                'Version': ((22,0,0),1)
                }
            })

    # Layer Properties
    App.Do( Environment, 'LayerProperties', {
            'General': {
                'Opacity': None, 
                'Name': u'50% Gray', 
                'IsVisible': None, 
                'IsTransparencyLocked': None, 
                'LinkSet': None, 
                'UseHighlight': None, 
                'PaletteHighlightColor': None, 
                'GroupLink': None, 
                'BlendMode': None
                }, 
            'BlendRanges': None, 
            'Path': (0,0,[],False), 
            'ArtMediaTexture': None, 
            'Effects': None, 
            'BrightnessContrast': None, 
            'ChannelMixer': None, 
            'ColorBalance': None, 
            'CurveParams': None, 
            'HSL': None, 
            'Threshold': None, 
            'Levels': None, 
            'Posterize': None, 
            'Vibrancy': None, 
            'Overlay': None, 
            'LocalToneMapping': None, 
            'Invert': None, 
            'HistogramAdjustment': None, 
            'FillLightClarity': None, 
            'GeneralSettings': {
                'ExecutionMode': App.Constants.ExecutionMode.Silent, 
                'AutoActionMode': App.Constants.AutoActionMode.Default, 
                'Version': ((22,0,0),1)
                }
            })

def GetSelectionPoint(Environment):
    # JascUtils.SaveSelection - has annoying habit of removing the selection after saving so restore it
    sel = JascUtils.SaveSelection(Environment, App.TargetDocument)
    sel.RestoreSelection()

    SelResult = App.Do( Environment, 'GetRasterSelectionRect' )
    if SelResult[ 'Type' ] == App.Constants.SelectionType.None:
        return False
    SelRect = SelResult['Rect']
    start = (SelRect[0][0], SelRect[0][1]+1) #start one pixel from the top.
    end = (SelRect[1]+SelRect[0][0], SelRect[2] + SelRect[0][1])
   
    # Modify selection to a single line
    App.Do( Environment, 'Selection', { 'General': { 'Mode': 2, 'Antialias': False,  'Feather': 0}, 'SelectionShape': 0, 'Start': start, 'End': end, 'SelectionStyle': 0,})
    SelResult = App.Do( Environment, 'GetRasterSelectionRect' )
    if SelResult[ 'Type' ] == 0:
        return False
    sel.RestoreSelection()
    return SelResult['Rect'][0]
So any time you wanted to make sure your script will fill an area regardless of whether or not there was a selection, you just need to add this pre-made function, and call it inside your script. Everything else can be recorded as per normal.
migf1
Posts: 510
Joined: Fri Apr 03, 2020 3:09 pm
operating_system: Windows 8.1
System_Drive: C
32bit or 64bit: 64 Bit

Re: Creating a new 50% Gray Layer is a hit or miss

Post by migf1 »

Levi thank you so very much!

To be honest, I didn't quite get how (and why) this works, but I did understand how I can use the function. Many thanks again!

PS. I'll have to re-read the function with fresh eyes tomorrow, hoping it will then make sense (I speak no python, but I speak C lol).
Good Unofficial PaintShop Pro Tutorials: Creation CasselMake Shop ProHEC Image EditingLeviFiction PSP Basics
(plus my own Gimp & Stuff)
migf1
Posts: 510
Joined: Fri Apr 03, 2020 3:09 pm
operating_system: Windows 8.1
System_Drive: C
32bit or 64bit: 64 Bit

Re: Creating a new 50% Gray Layer is a hit or miss

Post by migf1 »

LeviFiction wrote: Thu Mar 04, 2021 5:38 pm [snip]Cassel's scripts, for example, use this clever trick where they get the selection rectangle which defines the boundary of the selection. Then she modifies the selection so that it's only one pixel tall. Grabbing the selection rectangle again the top-left corner of the rectangle matches the starting point of the selection. And that can be used as the Point parameter of the Fill command. She then restores the original selection I believe by doing Undo.
[snip]
That's the part I don't understand Levi. Specifically, why making the selection 1 pixel tall and then re-grabbing gives the correct x,y top-left coordinate of the selection, while the original selection does not?

Ps. Something semi-relevant: is it possible for a recorded script to open the color-picker interactively, so the user can pick their own color before the script proceeds further?
Good Unofficial PaintShop Pro Tutorials: Creation CasselMake Shop ProHEC Image EditingLeviFiction PSP Basics
(plus my own Gimp & Stuff)
LeviFiction
Advisor
Posts: 6774
Joined: Thu Oct 02, 2008 1:07 pm
operating_system: Windows 10
System_Drive: C
32bit or 64bit: 64 Bit
motherboard: Alienware M17xR4
processor: Intel Core i7-3630QM CPU - 2_40GH
ram: 6 GB
Video Card: NVIDIA GeForce GTX 660M
sound_card: Sound Blaster Recon3Di
Hard_Drive_Capacity: 500GB
Corel programs: PSP: 8-2023
Location: USA

Re: Creating a new 50% Gray Layer is a hit or miss

Post by LeviFiction »

1) Because what we do when we "GetRasterSelectionRect" is we get the bounding box of the selection. And the bounding box is represented by ((x,y),width,height). If that selection is a perfect rectangle, no problem. We can grab the (x,y) portion and have a point we know is inside the selection. But we don't always know if the selection we're working with is a perfect rectangle. It could be an ellipse or some weird blob the shape of a specific item. The rectangle could have curved corners, etc. By making the selection exactly 1 pixel high we basically guarantee that the (x,y) location will always map to a spot inside the selection. Because pixels themselves are square. Doesn't matter how long the resulting selection is because it'll always start at the first pixel that is selected on that row.

Video Demonstration:

2) It is possible, yes. I don't think you can record it that way but you can add the command and make it interactive yes. The command is called 'GetMaterial' and it takes 1 parameter. "IsPrimary" either False (0) or True (1). This lets you decide if the user is selecting the Foreground (Primary) or Background color.

You can read the details in the API documents - http://help.corel.com/paintshop-pro/v22 ... erial.html

Setting "ExecutionMode" to "App.Constants.ExecutionMode.Interactive" forces it to be interactive regardless of whether the interactive mode is set on the Script Toolbar. If you run it in "Silent" mode rather than "Interactive" mode it'll return the currently selected color.

Code: Select all

    # GetMaterial
    color = App.Do( Environment, 'GetMaterial', {
            'IsPrimary': App.Constants.Boolean.true,
            'GeneralSettings': {
                'ExecutionMode': App.Constants.ExecutionMode.Interactive, 
                'AutoActionMode': App.Constants.AutoActionMode.Match, 
                'Version': ((22,0,0),1)
                }
            })


The return value is a dictionary letting you know what colors, pattern, gradient, and texture were selected by the user. The whole dictionary can be used where a command uses the full material settings, or you can target a specific portion using the key and target a specific parameter. For example with the Fill tool it only takes a tuple representing RGB values. So you can grab that by using the "Color" key of the dictionary we put into the variable "color".

Here are two examples, the First one I replace only the color parameter. The second I replace the entire "Material" parameter.

Code: Select all

# Fill
    App.Do( Environment, 'Fill', {
            'BlendMode': App.Constants.BlendMode.Normal, 
            'MatchMode': App.Constants.MatchMode.None, 
            'Material': {
                'Color': color['Color'], 
                'Pattern': None, 
                'Gradient': None, 
                'Texture': None, 
                'Art': None
                }, 
            'UseForeground': True, 
            'Opacity': 100, 
            'Point': point, 
            'SampleMerged': False, 
            'Tolerance': 0, 
            'GeneralSettings': {
                'ExecutionMode': App.Constants.ExecutionMode.Default, 
                'AutoActionMode': App.Constants.AutoActionMode.Match, 
                'Version': ((22,0,0),1)
                }
            })

By replacing the whole "Material" parameter the user can select gradients, patterns, and textures. If you only use the color value like above, it'll only use a solid color and nothing else.

Code: Select all

# Fill
    App.Do( Environment, 'Fill', {
            'BlendMode': App.Constants.BlendMode.Normal, 
            'MatchMode': App.Constants.MatchMode.None, 
            'Material': color, 
            'UseForeground': True, 
            'Opacity': 100, 
            'Point': point, 
            'SampleMerged': False, 
            'Tolerance': 0, 
            'GeneralSettings': {
                'ExecutionMode': App.Constants.ExecutionMode.Default, 
                'AutoActionMode': App.Constants.AutoActionMode.Match, 
                'Version': ((22,0,0),1)
                }
            })

migf1
Posts: 510
Joined: Fri Apr 03, 2020 3:09 pm
operating_system: Windows 8.1
System_Drive: C
32bit or 64bit: 64 Bit

Re: Creating a new 50% Gray Layer is a hit or miss

Post by migf1 »

Great info once again Levi (and I finally understood why the 1 pixel-tall trick works). Thank you very much!
Good Unofficial PaintShop Pro Tutorials: Creation CasselMake Shop ProHEC Image EditingLeviFiction PSP Basics
(plus my own Gimp & Stuff)