Gimp-Forum.net

Full Version: Python Fu Image Creation - Slight Issues
You're currently viewing a stripped down version of our content. View the full version with proper formatting.
Pages: 1 2
Hi all,

I decided to finally start learning Python-Fu (I'm already familiar with Python), and I've been getting a feel for it by converting some of my older Script-Fu scripts to Python-Fu. It's going well so far, and I've been able to convert a few. Currently, I'm working on one of my scripts to create a preview image template. Formerly, I had a series of scripts that I could use to create different sized previews. I've streamlined it to one single new Python-Fu script with two sliders that allow me to customize how big I want the preview image canvas. The sliders determine the number of rows and columns, and then the image and guides are generated from that. The script works well, but there are two issues:
  1. When I don't have any images open, and I run this script, the dialog for it appears, and there are two dropdowns at the top for "Input Image" and "Input Drawable", above the sliders for number of rows and columns. Both say "(Empty)," and that's the only option. If I already have another image open, these dropdowns don't appear. Is this normal? Is there any way to prevent these from appearing? 
  2. The image, when created, is black. The older script-Fu scripts (that essentially had the same coding) would create a white image. I'm sure there are many ways to make the image white, but how do I default to that?
Here's the Python-Fu code:
Code:
#!/usr/bin/env python
# -*- coding: utf-8 -*-

from gimpfu import*

def multiSkinPreviewAny (theImage, theLayer, columns, rows):
   # Establish the dimensions of the image based on the number of rows and columns
   theImageWidth = 543 * columns
   theImageHeight = 1080 * rows
   # Create the image
   theImage = pdb.gimp_image_new(theImageWidth, theImageHeight, 0)
   # Create the main layer
   theLayer = pdb.gimp_layer_new(theImage, theImageWidth, theImageHeight, 0, "Background", 100, 28)
   # Add the layer to the image
   pdb.gimp_image_add_layer(theImage, theLayer, 0)
   # Add guides at the top left and bottom right
   guide = pdb.gimp_image_add_vguide(theImage, 0)
   guide = pdb.gimp_image_add_hguide(theImage, 0)
   guide = pdb.gimp_image_add_vguide(theImage, theImageWidth)
   guide = pdb.gimp_image_add_hguide(theImage, theImageHeight)
   i = 0
   # Add 1 vertical guide for every column
   while i < columns:
       xcoord = i * 543
       guide = pdb.gimp_image_add_vguide(theImage, xcoord)
       i += 1
   # Re-initiate the counter
   i = 0
   # Add 1 horizontal guide for every row
   while i < rows:
       ycoord = i * 1080
       guide = pdb.gimp_image_add_hguide(theImage, ycoord)
       i += 1
   # Display the new image
   display = pdb.gimp_display_new(theImage)

register(
   "python_fu_marvelmods_common_multiskinpreviewany",
   "Create template grid for skin preview images.",
   "Create template grid for skin preview images.",
   "BaconWizard17",
   "BaconWizard17",
   "December 2022",
   "<Image>/Marvel Mods/Skin Previews/Multi Skin Showcase/Custom Size Skin Preview",
   "",
   [
       (PF_SLIDER, "columns", "Number of Columns:", 2, (1, 8, 1)),
       (PF_SLIDER, "rows", "Number of Rows:", 2, (1, 8, 1))
   ],
   [],
   multiSkinPreviewAny)

main()

And here's one of the Script-Fu scripts for comparison:
Code:
; 2x2 grid skin preview for XML2/MUA1 skins
(define (script-fu-mua-xml2-2x2-preview)
    (let*
        (
            ; define our local variables
            ; create a new image:
            (theImageWidth  1086)
            (theImageHeight 2160)
            (theImage
                (car
                    (gimp-image-new
                        theImageWidth
                        theImageHeight
                        RGB
                    )
                )
            )
            ; background layer
            (theLayer
                (car
                    (gimp-layer-new
                        theImage
                        theImageWidth
                        theImageHeight
                        RGBA-IMAGE
                        "Background"
                        100
                        LAYER-MODE-NORMAL
                    )
                )
            )
        ) ;end of our local variables
        ; add the layers
        (gimp-image-add-layer theImage theLayer 0)
        ; add the guides
        (gimp-image-add-hguide theImage 1080)
        (gimp-image-add-vguide theImage 543)
        ; show the new image on the screen
        (gimp-display-new theImage)
   )
)
; populate script registration information
(script-fu-register
   "script-fu-mua-xml2-2x2-preview"
   "2x2 Skin Preview"
   "Creates an image for a 2x2 grid preview for 4 skins."
   "BaconWizard17"
   "BaconWizard17"
   "September 2021"
   ""
)
; register the script within gimp menu
(script-fu-menu-register "script-fu-mua-xml2-2x2-preview" "<Image>/Marvel Mods/Skin Previews/Multi Skin Showcase")

Any input is appreciated!

  1. You register the plugin as taking width, height parameters, but then attach it to a function that takes image, layer, width, height (where image, layer are not used). I'm even surprised that it works at all.
  2. See pdb.gimp_drawable_fill(drawable, fill_type); The doc says: "Its main purpose is to fill a newly created drawable before adding it to the image." (which implies that creates layers aren't filled, Black shows up because under the hood it's all zeroes.

PS: Using LAYER_MODE_NORMAL is much better than 28.
If I remove the image and layer inputs from the function, I get the following error when running the script:
Quote:TypeError: multiSkinPreviewAny() takes
exactly 2 arguments (4 given)

I think I read somewhere that python scripts automatically assigns an image and layer variable or something along those lines. I don't need the image and layer as inputs to the function because I'm creating a new image, but I can't get it to work without those there. What's the best way to fix that?

As for LAYER_MODE_NORMAL vs 28, is that not the same thing? I checked and it does the same with either, but I'm curious why one would be preferred over the other. Are the text equivalents always better for these types of variables?

Edit: I just realized that my old script didn't fill the image with white. It left the image transparent. I changed the layer type from RGB to RGBA.
(12-31-2022, 04:21 PM)BaconWizard17 Wrote: [ -> ]As for LAYER_MODE_NORMAL vs 28, is that not the same thing? I checked and it does the same with either, but I'm curious why one would be preferred over the other. Are the text equivalents always better for these types of variables?

At the moment the constant declaration LAYER_MODE_NORMAL is set to 28 but if that ever changed (perhaps another mode was inserted into the list before this one) you could spend a long time looking for the problem. Using LAYER_MODE_NORMAL all you have to worry about is the name changing (as it did recently from NORMAL_LAYER_MODE if I remember correctly) and that would be easy to sort.

Apart from that, reading LAYER_MODE_NORMAL conveys information - 28 doesn't tell you anything just by looking at it.
(12-31-2022, 04:42 PM)programmer_ceds Wrote: [ -> ]
(12-31-2022, 04:21 PM)BaconWizard17 Wrote: [ -> ]As for LAYER_MODE_NORMAL vs 28, is that not the same thing? I checked and it does the same with either, but I'm curious why one would be preferred over the other. Are the text equivalents always better for these types of variables?

At the moment the constant declaration LAYER_MODE_NORMAL is set to 28 but if that ever changed (perhaps another mode was inserted into the list before this one) you could spend a long time looking for the problem. Using LAYER_MODE_NORMAL all you have to worry about is the name changing (as it did recently from NORMAL_LAYER_MODE if I remember correctly) and that would be easy to sort.

Apart from that, reading LAYER_MODE_NORMAL conveys information - 28 doesn't tell you anything just by looking at it.

That makes sense. Thank you! I'll keep that in mind going forward
(12-31-2022, 04:21 PM)BaconWizard17 Wrote: [ -> ]If I remove the image and layer inputs from the function, I get the following error when running the script:
Quote:TypeError: multiSkinPreviewAny() takes
exactly 2 arguments (4 given)

I think I read somewhere that python scripts automatically assigns an image and layer variable or something along those lines. I don't need the image and layer as inputs to the function because I'm creating a new image, but I can't get it to work without those there. What's the best way to fix that?

Did you restart Gimp so that it re-scans the plugins. What is the definition like in the pluginrc file?

AFAIK, if the first arguments in the registration are an image and optionally a layer(*) (in that order) when when the plugin is called, the code that auto-generates the plugin considers that these are implicitly the current image and layer, and doesn't generate widgets to select them in the dialog. In some conditions,  you can see them  in the dialog (from memory when re-executing the scripts, perhaps if the original layer has disappeared).
Sorry for the delayed response. Hectic start to the year. 

Quote:Did you restart Gimp so that it re-scans the plugins. What is the definition like in the pluginrc file?

Yep, I always restart Gimp when making changes to the plugins. Here's the definition in pluginrc:
Code:
(plug-in-def "${gimp_dir}\\plug-ins\\MarvelMods-Common-MultiSkinPreviewAny.py" 1672505354
   (proc-def "python_fu_marvelmods_common_multiskinpreviewany" 1
        "Create template grid for skin preview images."
        "Create template grid for skin preview images."
        "BaconWizard17"
        "BaconWizard17"
        "December 2022"
        "Create Multi Skin Preview"
        1
       (menu-path "<Image>/Marvel Mods/Skin Previews/Skin Showcase")
       (icon icon-name -1 "")
        ""
        5 0
       (proc-arg 0 "run-mode" "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }")
       (proc-arg 13 "image" "Input image")
       (proc-arg 16 "drawable" "Input drawable")
       (proc-arg 3 "columns" "Number of Columns:")
       (proc-arg 3 "rows" "Number of Rows:")))

This looks the same regardless of whether I have the image and layer variables or not for my function.

I'm going to include some screenshots of what I'm seeing. This is how the dialog looks when I run the script with no image open. The dialog is the same regardless of whether or not I have the image and layer variables in my function:
[Image: nvphwrm.png]

This is the same dialog box if I already have an image open. Again, it doesn't change if the layer and image variables are there:
[Image: PZQXNhv.png]

If the layer and image variables are not part of my function (so def multiSkinPreviewAny (columns, rows):), I get this error:
[Image: faR4NSc.png]

Quote:AFAIK, if the first arguments in the registration are an image and optionally a layer(*) (in that order) when when the plugin is called

The issue is that I don't think I have these in my registration. I think they're being included automatically. This is the register function again:
Code:
register(
   "python_fu_marvelmods_common_multiskinpreviewany",
   "Create template grid for skin preview images.",
   "Create template grid for skin preview images.",
   "BaconWizard17",
   "BaconWizard17",
   "December 2022",
   "<Image>/Marvel Mods/Skin Previews/Skin Showcase/Create Multi Skin Preview",
   "",
   [
       (PF_SLIDER, "columns", "Number of Columns:", 2, (1, 8, 1)),
       (PF_SLIDER, "rows", "Number of Rows:", 2, (1, 8, 1))
   ],
   [],
   multiSkinPreviewAny)


Is there something wrong with that? I just want to set this up without the image or layer variables so that others can use this script and similar ones without any confusion.
Looks like you are using the deprecated registration form, where the 7th argument is a full path for the menu, so you get an old behavior. In the current registration form, the 7th argument is only the menu label, and the location is passed in a named menu= argument. With this newer form, Gimp will not add parameters and will always call the plugin with the parameters declared in the registration. The only implicit behavior is that if the first parameters can be inferred from the way the plugin is called (typically, active image, layer, path...) they won't be part of the auto-generated dialog.

So, trying with this code (that uses the current registration form):

Code:
#!/usr/bin/env python
# -*- coding: utf-8 -*-

from gimpfu import *

### Plugin body
def plugin(*args):
    gimp.message('Called with %r' % (args,))

### Registration
register(
        "plugin1","Test plugin parms","Test plugin parms",
        "","","",
        "Test plugin parms1: no image, full args",
        "", # Accepted image type
        [
        (PF_IMAGE, "image", "Input image", None),
        (PF_OPTION,"option","Option",0,("Option1","Option2")),
        ],
        [],
    plugin,
    menu="<Image>/Test",
)

register(
        "plugin2","Test plugin parms","Test plugin parms",
        "","","",
        "Test plugin parms2: required image, full args",
        "*", # Accepted image type
        [
        (PF_IMAGE, "image", "Input image", None),
        (PF_OPTION,"option","Option",0,("Option1","Option2")),
        ],
        [],
    plugin,
    menu="<Image>/Test",
)

register(
        "plugin3","Test plugin parms","Test plugin parms",
        "","","",
        "Test plugin parms3: no image, minimal args",
        "", # Accepted image type
        [
        (PF_OPTION,"option","Option",0,("Option1","Option2")),
        ],
        [],
    plugin,
    menu="<Image>/Test",
)

register(
        "plugin4","Test plugin parms","Test plugin parms",
        "","","",
        "Test plugin parms4: required image, minimal args",
        "*", # Accepted image type
        [
        (PF_OPTION,"option","Option",0,("Option1","Option2")),
        ],
        [],
    plugin,
    menu="<Image>/Test",
)

main()

The plugin itself is written in way that makes it accept any number of parameters (this is for debug purposes only, not a good idea for a real plugin). The only purpose of the plugin code is to display the parameters received.
It is registered as four different Gimp plugins, each with a different registration:
  • With or without an explicit PF_IMAGE first argument
  • With an image type of  "" or "*", to require an image or not.This is an important parameter: with an empty string "", the plugin doesn't work on a specific image, and so can work even if there are no images (typically if the plugin creates an image), and when its defined ("*" means image of any type) the plugin will only be enabled if there is an image and it is one of the allowed types.
The results:

Called without any image opened in Gimp:
  1. Dialog shows Image +  Option, args are (None, 0)
  2. <Menu disabled>
  3. Dialog only shows option, args are (0,)
  4. <Menu disabled>
Called with an image opened:
  1. Dialog only shows option, args are (<gimp.Image '[Untitled]'>, 0)
  2. Dialog only shows option, args are (<gimp.Image '[Untitled]'>, 0)
  3. Dialog only shows option, args are (0,)
  4. Dialog only shows option, args are (0,)
So:
  • Registration type #1 (no image required and explicit PF_IMAGE) doesn't make much sense, because the plugin will be called and can require an input that the user cannot provide.
  • Registration type #2 (image required and explicit PF_IMAGE) is the canonical way to register a plugin that works on an existing image.
  • Registration type #3 (no image required and no PF_IMAGE) is the canonical way to register a plugin that creates an image.
  • Registration type #4 (image required and no PF_IMAGE) doesn't makes much senses either, because the plugin can only be called if there are images, but the specific image will not be passed to the plugin (unless there are additional PF_IMAGE parameters after the PF_OPTION)
This worked perfectly, thank you! I appreciate the help. Just out of curiosity, is this documented anywhere? I haven't been able to find much about python fu on the official Gimp site. 

And a question that I thought of while testing (hopefully my last one for a while): Is it possible to change the title of this window when input is being collected? Currently it just displays the name from the registration, so I wasn't sure if that could be changed.
[Image: PZQXNhv.png]
Gimp python doc is here: https://www.gimp.org/docs/python/

For the title bar content, not as far as I know, but I never tried to find out.
Pages: 1 2