Author Topic: 2 scripts for review (brush-rotate brush-transform)  (Read 7330 times)

jgsack

  • Lives here ;-)
  • ***
  • Posts: 192
    • View Profile
2 scripts for review (brush-rotate brush-transform)
« on: April 02, 2010, 04:01:11 am »
I have jumped into the script-fu waters (scheme) because I wanted to modify a brush rotation script to permit an incremenal (cumulative) option. I ended up writing both a new script (brush-rotate) as well as fixing and improving an existing script -- brush transform is my offering as a follow-on for  rotateBrush-2.6.scm  which is a follow-on for resizebrush.scm . These can be  seen at the plugin registry and elsewhere.

I know that brush rotation is coming in Gimp 2.8, but in the meantime, perhaps my brush-rotate may be found useful.

I am interested in feedback on usability as well as code review by script-fu meisters in the vicinity. I have tested these myself, but of course I may have missed something or caused a regression in most recent changes, so do not hesitate to complain about problems of any type.

I am also wondering how to go about proposing my brush-transform as an update/replacement to the existing script. Is there some conventional protocol for this? should I contact the last (and other previous?) author? Should I just upload it to the Gimp registry with an explanation? I don't want to step too hard on toes. :-)

Despite my inexperience with scheme, I have a fair amount of programming experience, and over time and of course for this work itself, I've spent some time with scheme and script-fu, so I also offer my code as possible examples of reasonable coding style. I'd appreciate any +/- critique on that.

~jim

EDIT: typo correction in subject --monoceros84
« Last Edit: April 06, 2010, 07:53:55 am by monoceros84 »

saulgoode

  • Lives here ;-)
  • ***
  • Posts: 130
    • View Profile
Re: 2 scripts for review (bush-rotate brush-transform)
« Reply #1 on: April 02, 2010, 09:39:46 am »
The script should not require that an image be open since you are merely editing brushes (it appears the original image passed to the script is ignored). I would recommend registering the new commands to the <Brushes> menu; this permits the user to right-click on a brush in the Brushes dialog and execute the command. If you do place the commands in the <Image> menu hierarchy, I'd suggest not creating a new, top-level menu (e.g., "Script-Fu").

Code: [Select]
(script-fu-register "script-fu-brush-rotate"
  _"Rotate Brush Incremental"
  _"Rotate the current brush\n(incrementally on repeat)"
  "James G. Sack"
  "jgsack"
  "2010"
  ""
  SF-ADJUSTMENT _"Rotate Degrees" '(0 -180 180 1 10 0 0)
)
(script-fu-menu-register "script-fu-brush-rotate"
  _"<Brushes>/BrushUtil")

Note that the SF-IMAGE and SF-DRAWABLE arguments must also be removed from the 'brush-rotate' and 'brush-transform' declarations.

The code itself appears to be bug-free, but I would note that the possible return values that get assigned to the variable 'changed?' are not truly boolean, but instead are lists containing a boolean. This is not a problem as they will always be "(#t)" which gets treated as boolean true. However, if the PDB functions were to return boolean false, it would still be treated as true because it is wrapped within a list (the functions you call will not return false, instead generating an error).

I'm not particularly keen on some of your coding style, but that is basically just a matter of personal preference.

PhotoComix

  • Lives here ;-)
  • ***
  • Posts: 128
    • View Profile
Re: 2 scripts for review (bush-rotate brush-transform)
« Reply #2 on: April 02, 2010, 02:45:43 pm »
From your descrition brush transform already include a option to rotate

so why another separate script to rotate only ?

Quote
I am also wondering how to go about proposing my brush-transform as an update/replacement to the existing script. Is there some conventional protocol for this? should I contact the last (and other previous?) author? Should I just upload it to the Gimp registry with an explanation? I don't want to step too hard on toes. :-)

Contact the last author seems always a good practice, about the previous may be nice ..but without need to trace back till Adam and Eve.

About uploading to registry there is no burocracy involved, just if the script or plugin is a update/replacement of something else, quote it and add the link in the comment of your script/plugin

mramshaw

  • Lives here ;-)
  • ***
  • Posts: 547
    • View Profile
Re: 2 scripts for review (bush-rotate brush-transform)
« Reply #3 on: April 02, 2010, 05:16:14 pm »
Some good comments from Saul and @PhotoComix, a few from me below.

Contact the last author seems always a good practice

Exactly. You are not trying to pretend you wrote their code, but still a courtesy.

About uploading to registry there is no burocracy involved

Exactly. Quote the previous versions, explain why you wrote a new one. Presumably
you wanted the new one to do something different than the old one, which is why
you have chosen to share it.

As Saul says, the 'script-fu' menu has been dead for a while. Some prefer to put
their stuff in a unique folder (so they know it's from an add-in script) while others
choose to try to follow the existing structure. Personal preference. It might be
nice to add this menu location to your registry notes, plus the script comments.

I believe the underscore is meant to indicate a short-cut (Alt-T type thing) but
seems to be applied inconsistently (if at all). As there are possibilities for collision
due to limited letters of the alphabet, probably best not to use these for add-in
scripts.

As to your code, it looks pretty good to me. Lots of useful comments and a healthy
respect for the work of others. Very professional.

jgsack

  • Lives here ;-)
  • ***
  • Posts: 192
    • View Profile
Re: 2 scripts for review (bush-rotate brush-transform)
« Reply #4 on: April 02, 2010, 05:24:52 pm »
@saulgoode
Thanks for taking the time to look at the code and for the reply.
Quote
The script should not require that an image be open since you are merely editing brushes (it appears the original image passed to the script is ignored). I would recommend registering the new commands to the <Brushes> menu; this permits the user to right-click on a brush in the Brushes dialog and execute the command. If you do place the commands in the <Image> menu hierarchy, I'd suggest not creating a new, top-level menu (e.g., "Script-Fu").
Ahh yes, good points, good suggestions. I copied old code without thinking too much about it. I myself do not like the practice of "littering" the top menu item, so I was violating my own principles. :)

I notice that using the <Brushes> suggestion requires changing the applicable image types from "*" to "", and unfortunately, that removes the "Repeat Last"/"Reshow Last" (Ctrl+F/Shift+Ctrl+F) from the Filters menu. (sigh..) Getting those hotkeys working was one of my objectives. (/sigh..)

I'll have to think about that some more.

Quote
return values that get assigned to .. 'changed?' .. not truly boolean, but instead are lists
Good catch; sloppy me.

Quote
I'm not particularly keen on some of your coding style, but that is basically just a matter of personal preference.
Would you be so kind as to describe your views here. As I said, I have no real experience writing scheme, and would appreciate comments on style (and construction details) so I may learn.

@PhotoComix
Quote
why another separate script to rotate only?
The brush-rotate script is "incremental" -- each re-execution (eg, via Ctrl+F) adds additional rotation thus  producing a cumulative effect. In the comments , I note that I thought it best to have a separate script in order to avoid mixing such an incremental operation with the other operations which would not make much sense in an incremental mode.

I am open to suggestions for better words to use than "incremental" and "cumulative".

And thanks for your other comments.

(edit: added)
@mramshaw
Thanks for the remarks.

The use of the Script-Fu top menu entry has already been discussed in my reply to saulgoode. I know that others like having a "personal" menu, but my thought is that that is not too appropriate for distributed scripts, because what if i installed a bunch of scripts with such extra menu items from separate but like-minded script authors -- the menu would get somewhat unwieldy, eh? I hope no one takes offense at this expression of my personal preferences.

Knowing where to put the script in the menu system is admittedly a vexing question.

The suggestion to reflect some of my comments in the registry submission is a good one. Thanks

Quote
I believe the underscore is meant to indicate a short-cut (Alt-T type thing)
Ahhh! Menu (accelerator) hotkeys. I think I'll yank those out altogether, then.

~jim
« Last Edit: April 02, 2010, 08:00:16 pm by jgsack »

saulgoode

  • Lives here ;-)
  • ***
  • Posts: 130
    • View Profile
Re: 2 scripts for review (bush-rotate brush-transform)
« Reply #5 on: April 02, 2010, 06:25:03 pm »
I believe the underscore is meant to indicate a short-cut (Alt-T type thing) but seems to be applied inconsistently (if at all). As there are possibilities for collision due to limited letters of the alphabet, probably best not to use these for add-in scripts.

Just to clarify this issue, an underscore which appears before a quoted string (e.g., _"hello") marks that string for potential language translation. If a non-English locale is specified -- and a translation is available in the localization database -- the translation will be presented in the dialog window. As jgsack pointed out, there is no convenient means provided by Script-fu alone to add translations to the database; for third-party scripts this functionality is not currently of much use.

When an underscore appears within the quoted string that specifies the menu command (e.g., "Fill with _FG Color"), the subsequent letter becomes the "mnemonic" key for that menu command; i.e., ALT+mnemonic highlights the associated menu command, but does not execute it until ENTER is pressed. (Note that the mnemonic keys are entirely unrelated to the keyboard shortcuts assigned to commands.)

Personally, I never use the mnemonics and have them hidden in the menus (this done under "Edit->Preferences->Interface") but there is no harm in assigning them for scripts (multiple uses of the same letter within a menu merely require the user to hit the mnemonic additional times to move to the next command).

saulgoode

  • Lives here ;-)
  • ***
  • Posts: 130
    • View Profile
Re: 2 scripts for review (bush-rotate brush-transform)
« Reply #6 on: April 02, 2010, 06:27:18 pm »
I notice that using the <Brushes> suggestion requires changing the applicable image types from "*" to "", and unfortunately, that removes the "Repeat Last"/"Reshow Last" (Ctrl+F/Shift+Ctrl+F) from the Filters menu. (sigh..) Getting those hotkeys working was one of my objectives. (/sigh..)

Interesting point about the Repeat/Reshow Last, perhaps might even be considered a bug.

It is still permitted to use "*" and have the command in the Brushes menu; it just means that the command will be inaccessible (grayed out) if there is no image open. I would still recommend relocating the command even if you use "*". The reasons for this are twofold (though again mostly personal preference): first, it helps mitigate the "population explosion" of menu commands, and second, it provides a more direct correlation between the command and the currently active brush (or the brush upon which right-click is performed).

Quote
Would you be so kind as to describe your views here. As I said, I have no real experience writing scheme, and would appreciate comments on style (and construction details) so I may learn.

I will get back to you on this. :)

jgsack

  • Lives here ;-)
  • ***
  • Posts: 192
    • View Profile
Re: 2 scripts for review (bush-rotate brush-transform)
« Reply #7 on: April 02, 2010, 08:12:52 pm »
@saulgoode
Thanks for the good explanation of the underscores.

On the difference between "" and "*", I am now getting inconsistent results. I will do more testing. Perhaps I am misinterpreting the docs or commentary I have seen somewhere. At the moment I cannot get Repeat/Reshow Last to work on recent edits.  

[update]
Getting Repeat/Reshow Last (on the Filters menu) to work requires
1) having something in the valid image types registration field ("*") works
2) passing an image parm, SF-IMAGE ( SF_DRAWABLE is not required)

However: having anything other than "" in  valid image types, seems to prevent using a <BRUSHES>/BrushUtil menu location! Pity, eh?

I'm leaning toward using "<Image>/Filters/X"  where X is BrushUtil or Misc or Other

~jim
« Last Edit: April 02, 2010, 09:22:56 pm by jgsack »

PhotoComix

  • Lives here ;-)
  • ***
  • Posts: 128
    • View Profile
Re: 2 scripts for review (bush-rotate brush-transform)
« Reply #8 on: April 02, 2010, 10:40:03 pm »
about  the script be registered in the brush menu i have..mixed feelings:

For sure is the best location for who know exactly what is the script for and take care to look in the script for the registration path..., or know about the plugin browser ...or at least read all the comment before installing it

But the others , and they may be the large majority

they will just freak out, looking in all menu without luck, and then they will give up, or at best send messages asking "...were the heck is the script "

I had same problems myself for "gradient" scripts that were registered under <gradient>...no doubt that was the best place for who may guess that a script may be called by right clicking on a gradient

For me was a treasure hunt, and even open the script with the notepad
did not give me a sufficent hint

So shortly <Brush> is for me the best place

 but with the serious drawback that only geeks will think to look in the brush conext menu, even if is the most logic and handy location
« Last Edit: April 02, 2010, 10:46:38 pm by PhotoComix »

saulgoode

  • Lives here ;-)
  • ***
  • Posts: 130
    • View Profile
Re: 2 scripts for review (bush-rotate brush-transform)
« Reply #9 on: April 03, 2010, 06:06:10 pm »
Would you be so kind as to describe your views here. As I said, I have no real experience writing scheme, and would appreciate comments on style (and construction details) so I may learn.

OK, here is some style feedback. Mind, these are just my opinions and though I will try to present some logic to support them, there are certainly valid reasons to do things in different manners. I want to emphasize that it is not my intent to criticize your choices -- indeed your script is better written than much of the Script-fu code extant, including some of my own -- but to just offer some of the things I consider when writing my own scripts.

I don't understand the reasoning behind the "HACK". Your script needs to define at least one global function, and any definitions made within that global function are local to that function. What does wrapping a definition such as (define suffix2 "#Rotated") within a let accomplish that wouldn't be accomplished by placing it inside a global define?

The naming scheme for the variables and procedures seems a somewhat haphazard mixture employing full words, abbreviations, CamelCase, and punctuation. My personal preference is for lowercase-only, full words separated with hyphens when necessary (and trying to be consistent with the naming of Scheme functions and PDB procedures); but regardless of approach, more consistency should improve your code's readability. For what it's worth, I myself am not particularly proficient at naming things and I certainly have made some horrendous choices in my own scripts.

Here is a smattering of some of your choices with my opinion on them:

  • image, layer, spacing : excellent choices. As much as reasonable, I try to use argument names as they are provided by the PDB documentation. This has a (minor) added benefit when using the "Apply" button from within the Script-fu Console's "Browse" dialog.

  • basebrush, workbrush : my preference would be to separate the "adjective" (or "attribute") from the "object" using hyphens (base-brush, work-brush). This separation is sometimes not perfectly clear. For example, by convention 'filename' (or 'layername', or 'brushname' etc) seem acceptable as "objects" in and of themselves, and so they do not feel that objectionable; whereas cases such as a layer's width call for more distinction  ('layer-width' versus 'layerwidth').

  • brushWidth, centerX : my preference would be to keep things lowercase, and use hyphens for separation (brush-width, center-x). I don't find the capitalization of X and Y to be that objectionable, but tend to keep things lowercase for consistency. FWIW, I do not object to camelcase in general -- I actually consider it an elegant approach -- but its employment in Scheme code is not at all common and using it in Script-fu scripts leads to inconsistency (as would any usage of underscores).

  • binfo, bwidth, bspacing : I would probably expand these out to make it clear that they are brush attributes. If it is clear that the attribute is associated with the object, I might omit the object (e.g., just use 'spacing' instead of 'brush-spacing'). Likewise, since the width and height are associated with not only the brush but with the image and layer dimensions, I see no confusion arising from just using unadorned versions ('width', 'height').

  • bbase, bsave : these are not that descriptive of what they are (text strings/names).

  • brush-isgray? : good use of the eroteme to indicate a function returning a boolean per Scheme convention. I would probably just go with 'brush-gray?' as it feels more consistent with other, similar tests ('zero?', 'pair?', 'integer?'). Basically the question mark indicates the nature of the function.

  • rotate!, flip! : I imagine that your goal in using the exclamation mark was to follow Scheme convention for procedures that may result in modification of the arguments (a la 'set!'). If this is so, I don't really agree that the arguments are being modified. In these functions, the modification is moreso to the graphical contents of the image or layer, and not to their "handles" (identification value) as manipulated by the function. In the case of 'rotate!', the layer's handle does get modified (destroyed) but this is not really the purpose of the function.
    ------------------------------------

    Moving on, I find your indentation style quite good and very consistent with the preferred Scheme conventions. As for myself, I tend to deviate from convention somewhat significantly in a couple of regards. I am not recommending that you adopt my unconventional conventions but present them nonetheless as thought food.

    First, I tend to forgo the "indent to align" practice and just tab in one level (two spaces). The reasoning behind this is that the function names typically appearing in Script-fu are much longer than might traditionally appear in Scheme code. It is all well and good to indent past a function such as 'write' or 'cons' but doing so for a function named 'mathmap-distorts-inverse-lambert-azimuthal-projection' or somesuch quickly forces all subsequent lines to be crammed against the right edge of your text editor (possibly requiring horizontal scrolling to see the entirety of the code block).

    I admit that my approach creates almost as many problems as it resolves but since I have a decent text editor that highlights the vertical alignment of matching parenthesis, I prefer it to using long lines of text and having the majority of my text screen blank. I also deviate from my own indentation style (and thus more closely follow Scheme convention) in the case of functions which have many arguments as shown in the following code block:

    Code: [Select]
     (gimp-drawable-transform-rotate-default layer
                                              (* new-cumulation (/ *pi* 180))
                                              TRUE
                                              0 0
                                              TRUE
                                              TRANSFORM-RESIZE-ADJUST)
                                                   
    While I'm thinking of it, I would also highly recommend that you use the Script-fu enumerated constants (TRUE, FALSE, RGB-MODE, etc) whenever you are able as it is quite helpful in making the code more readable and in preventing silly mistakes when later revising your scripts. It also serves as good educational rote which aids in learning the functionality and interface of the various PDB functions.

    The second area where I often deviate from conventional Scheme style guidelines is in accumulating nested right parentheses together on the last line of a code block. I generally keep these separate:

    Code: [Select]
     ;; My approach
      ;
      (while (not done?)
        (if changed?
          (begin
            (save file)
            (delete image)
            (set! count (+ count 1))
            )
          (begin
            (delete image)
            )
          )
        )

      ;; Conventional Scheme
      ;
      (while (not done?)
        (if changed?
          (begin
            (save file)
            (delete image)
            (set! count (+ count 1)))
          (begin
            (delete image))))

    I do this not because I consider my approach more readable, but because it permits me to easily copy-n-paste my script's code from the text editor to GIMP's Script-fu Console to test code fragments (line-by-line) during development. The conventional Scheme style would require that I select portions of a line (positioning the cursor at the correct location within the string of right parentheses) whereas my approach generally permits selecting entire lines or groups of lines. I actually greatly prefer the readability of the conventional approach -- and will sometimes convert my fully-vetted scripts to use it -- but given the rather limited debugging tools available with Script-fu, the advantages while developing my script justify my unconventional method.

    I have only one more major comment which I will make in a subsequent post, but for now I will wrap up by addressing two trivial issues.

    First, in your comments you raise the question as to why conversion to grayscale and flattening must come *after* the layer rotation. The answer is that rotating a layer non-orthogonally results in an alpha channel being added to the layer.

    Second, when you are flattening the image, you first save the foreground and background so as to later restore them. Ignoring the fact that there is no need to save the foreground color, this preservation of the original background can more readily be accomplished with a 'gimp-context-push' / 'gimp-context-pop' wrapping. It is possibly owing to the example provided in some of my scripts wherein you acquired the less-than-optimal approach. I originally adopted my bad habit back during GIMP 2.2 because of a bug in 'gimp-context-push/pop' failing to record the FG and BG colors. There is no longer any reason to avoid using 'gimp-context-push/pop' (and using it saves a few lines a code and additional variables).

    In a future installment I shall cover factoring of procedures.
    « Last Edit: April 03, 2010, 08:05:50 pm by saulgoode »

    saulgoode

    • Lives here ;-)
    • ***
    • Posts: 130
      • View Profile
    Re: 2 scripts for review (bush-rotate brush-transform)
    « Reply #10 on: April 03, 2010, 08:19:50 pm »
    [update]
    Getting Repeat/Reshow Last (on the Filters menu) to work requires
    1) having something in the valid image types registration field ("*") works
    2) passing an image parm, SF-IMAGE ( SF_DRAWABLE is not required)

    However: having anything other than "" in  valid image types, seems to prevent using a <BRUSHES>/BrushUtil menu location! Pity, eh?
    I would consider this conflict to be a bug, or at minimum an enhancement request.

    I'm leaning toward using "<Image>/Filters/X"  where X is BrushUtil or Misc or Other

    I would still lean towards placing it in the <Brushes>, and assign keyboard shortcuts to the commands in lieu of Repeat/Show Last.

    jgsack

    • Lives here ;-)
    • ***
    • Posts: 192
      • View Profile
    Re: 2 scripts for review (brush-rotate brush-transform)
    « Reply #11 on: April 07, 2010, 10:37:09 pm »
    @saulgoode
    Thanks very much to take the time for your detailed observations.

    I find your suggestions very well taken. Some of the remarks about names can be "explained" ;) by trying to minimize style changes on the update to an existing program. But, of course, the poor names in the brand new program are for the most part laziness. :[

    The enumerated constants usage is something I planned myself but never got around to -- laziness again.

    On my rant leading to my ugly hack, the problem with (any) global defines, is the possibility of collision with other scheme scripts.  As I understand it, there seems to be only one global namespace, so to avoid such collisions, you have to protect utility functions and defines by embedding them in a top-level local function or let-block. Of course, that top-level local function itself will be a system-global.

    I actually got bit by this problem during this little project, and it took me a bit of head-scratching before I realized I was assuming a "file-level scoping" behavior, which was a wrong assumption. :(

    The "hack", I think, came about because I just got carried away with my disappointment over scheme scoping. It would be better to simply enclose the utility functions and defines within and at the top of my main function. The minor disadvantage of that is that the function parameter list is far removed from where the function's main processing code begins. Oh well. Maybe I'll just move to python for future gimp scripting. Although it's the first I've used it, I did find scheme to be fun.

    Indentation indeed often comes down to personal preferences, but I believe I mostly agree with you. For the indent-to-align, I tend to accept what my editor (vim) produces except for cases you describe (everything too far right). Probably I haven't noticed it so much because I tend to use shorter function and variable names. Perhaps I will evolve my style toward longer names, at which time, indent-to-align may become the exception.

    On the end-parens, I started out as you describe, but even though I agree that collecting them together makes editing annoyingly harder, I'm presently thinking that the compactness makes the code easier to read. Without editor highlighting of matching parens, I might go back to aligning parens.  It is somewhat amazing that the parens tend to disappear from consciousness, as I've read in lisp commentary before.

    Well, thanks again for the feedback and opportunity to exchange thoughts.

    ~jim

    saulgoode

    • Lives here ;-)
    • ***
    • Posts: 130
      • View Profile
    Re: 2 scripts for review (brush-rotate brush-transform)
    « Reply #12 on: April 09, 2010, 08:51:38 pm »
    The "hack", I think, came about because I just got carried away with my disappointment over scheme scoping. It would be better to simply enclose the utility functions and defines within and at the top of my main function. The minor disadvantage of that is that the function parameter list is far removed from where the function's main processing code begins. Oh well.

    I see. I was thinking that your approach may have stemmed from assimilating some old code from back when GIMP used the original SIOD-based Script-fu (though I'm not sure it would have accomplished the scoping desired even still).

    Maybe I'll just move to python for future gimp scripting. Although it's the first I've used it, I did find scheme to be fun.

    Gah! I hope my comments don't send you scudding over to the Dark Side. Unless you are editing your image at the individual pixel level (in which case a C plug-in is probably the best choice), or you need a preview of the filter's effect, Script-fu holds some significant advantages.

    I'm somewhat reluctant to continue with my critique else I push you further towards that evil  reptilian seductress; however, I don't wish to go back on my word and it is generally beneficial to review and justify one's own coding habits (if you can't explain why you're doing something a certain way, maybe you shouldn't be doing it that way).

    The only other point I'd like to address is that of factoring code into separate functions/procedures -- as was done, for example, with your 'makefilename' procedure. While this practice is certainly the dominant paradigm amongst programmers, and especially encouraged within the Scheme community, I find that it can hinder the readability of a script in cases where the procedure is only invoked once, or the procedure being defined is somewhat trivial in nature.

    Furthermore, the main benefit garnered from factorization -- code reuse across different modules -- is not readily attainable given the way Script-fu scripts are distributed. For example, say I have a subroutine which is employed in all of my scripts and this subroutine really simplifies the resulting code. If I place that subroutine in a separate file then users of one of my scripts should need to load that separate file. It is typically a better solution to just include a copy of the subroutine in each of my scripts that require it.

    Of course, if a subroutine appears several times within the same script file, or if it involves a complicated sequence of operations to accomplish a conceptually uncomplicated task, then its usage can certainly be justified. However, it is my opinion that simple tasks (both conceptually and algorithmically) are best placed "inline" so that when reading a script I do not have to jump around in the code to follow what is happening.

    As an academic exercise, I have produced a revised version of the 'brush-rotate' script which forgoes the factorization you used and also incorporates some of my previously expressed thoughts. I am not promoting this as an altogether "better" version, but merely provide it for comparison in readability.

    I would comment on, though, on the code involving the extraction of the 'base' and 'cumulation' ('b&c'). My approach, while simpler, does not handle the case of brushes which might contain an "@" symbol in the 'base'. It also employs the SIOD compatibility function 'strbreakup' which is technically deprecated (though it is so useful and, indeed, so Scheme-ish that I disagree with it not being part of the official R5RS specification). If this script were not intended for demonstration purposes then I should very likely use a separate procedure (as you did original) to perform this parsing.

    Code: [Select]
    ; This program is free software; you can redistribute it and/or modify it
    ; under the terms of the GNU General Public License as published by the Free
    ; Software Foundation; either version 2 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 MERCHANTABILITY or
    ; FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
    ; more details.

    ; This script is for demonstration purposes only. See J.G. Sack's original
    ; version (http://forum.meetthegimp.org/index.php/topic,987.0.html)
    ; if you are interested in the functionality provided by this script.

    (define (script-fu-brush-rotate angle)
      (set! angle (inexact->exact (round angle)))  ; fix non-integer slider input
      (unless (zero? angle)
        (let* (
            (suffix "#Rotated")  ; used for filename name, also in brush name
            (filename (string-append gimp-directory DIR-SEPARATOR "brushes" DIR-SEPARATOR suffix ".gbr"))
            (brush (car (gimp-context-get-brush))) ; uses format like "bird@045#Rotated" (or just "bird")
            (b&c (strbreakup brush "@"))  
            (base-brush (car b&c)) ; the name of the original brush
            (old-rotation (if (> (length b&c) 1)
                            (string->number (car (strbreakup (car (last b&c)) "#"))) ; previous rotation
                            0
                            )
              )
            (new-rotation (modulo (+ old-rotation angle) 360))
            (prepended (string-append "000" (number->string new-rotation)))
            (new-brush (string-append base-brush
                                      "@"
                                      (substring prepended (- (string-length prepended) 3))
                                      suffix
                                      )
              )
            (spacing (car (gimp-brush-get-spacing base-brush)))
            (brush-info (gimp-brush-get-info base-brush))
            (width (car brush-info))
            (height (cadr brush-info))
            (image (car (gimp-image-new width height RGB)))
            (layer 0)
            )
          (gimp-image-undo-disable image)
          (gimp-context-push)
          (gimp-context-set-brush base-brush)
          (set! layer (car (gimp-layer-new image width height RGBA-IMAGE "layer 1" 100 NORMAL-MODE)))
          (gimp-image-add-layer image layer 0)
          (gimp-drawable-fill layer TRANSPARENT-FILL)
          (gimp-paintbrush-default layer 2 (vector (/ width 2) (/ height 2))) ;; "stamp" brush on center of layer

          (unless (zero? new-rotation) ;; don't rotate if angle is "0"
            (case new-rotation
              ( (-90)      (gimp-drawable-transform-rotate-simple layer ROTATE-270 TRUE 0 0 FALSE))
              ( (90)       (gimp-drawable-transform-rotate-simple layer ROTATE-90  TRUE 0 0 FALSE))
              ( (180 -180) (gimp-drawable-transform-rotate-simple layer ROTATE-180 TRUE 0 0 FALSE))
              (else  ;; non-orthogonal rotation
                (gimp-drawable-transform-rotate-default layer
                                                        (* new-rotation (/ *pi* 180))
                                                        TRUE
                                                        0 0
                                                        TRUE
                                                        TRANSFORM-RESIZE-ADJUST)
                (plug-in-autocrop-layer RUN-NONINTERACTIVE image layer)
                (gimp-image-resize-to-layers image)
                )
              )
            )
          ;; remove alpha channel if base-brush was gray (using white BG)
          (when (zero? (cadddr (gimp-brush-get-info base-brush)))
            (gimp-context-set-background '(255 255 255))
            (gimp-layer-flatten layer)
            )
          (file-gbr-save RUN-NONINTERACTIVE image layer filename filename spacing new-brush)
          (gimp-image-delete image)
          (gimp-brushes-refresh)
          (gimp-context-pop)
          (gimp-context-set-brush new-brush)
          (gimp-progress-end)
          )
        )
      )

    (script-fu-register "script-fu-brush-rotate"
      _"Rotate Brush Incremental"
      _"Rotate the current brush\n(incrementally on repeat)"
      "James G. Sack"
      "jgsack"
      "2010"
      ""
      SF-ADJUSTMENT _"Rotate Degrees" '(0 -180 180 1 10 0 0)
    )
    (script-fu-menu-register "script-fu-brush-rotate"
      _"<Brushes>/Utilities")

    « Last Edit: April 09, 2010, 08:55:44 pm by saulgoode »