gimp script-fu: drawing a simple line - scheme

I'm trying to draw a line using gimp scripting. In the following script: I create a new image 512x512 and a I try to draw a diagonal (0,0)->(512,512).
But the image (tmp.xcf) remains transparent.
What am i doing wrong ?
(define (drawdiagonal W H)
(let* (
(img 0)
(bg 0)
(points (cons-array 4 'double) )
)
(set! img (car (gimp-image-new W H 0)))
(gimp-image-undo-group-start img)
(gimp-context-push)
(set! bg (car (gimp-layer-new img W H RGBA-IMAGE "background" 100 NORMAL-MODE)))
(gimp-image-add-layer img bg 0)
(gimp-drawable-set-visible bg TRUE)
(gimp-image-set-active-layer img bg)
(gimp-context-set-brush-size 10.0)
(gimp-context-set-opacity 100)
(gimp-context-set-paint-mode NORMAL-MODE)
(gimp-context-set-foreground '(255 127 0))
(gimp-selection-all img)
(aset points 0 0)
(aset points 1 0)
(aset points 2 W)
(aset points 3 H)
(gimp-paintbrush-default bg 4 points)
(gimp-context-pop)
(gimp-image-undo-group-end img)
(gimp-xcf-save 1 img img "tmp.xcf" "tmp.xcf")
(display "DONE")
)
)
(drawdiagonal 512 512) (gimp-quit 0)
usage:
cat test.scm | gimp -i -b -

It seems you are mostly missing:
(gimp-context-set-brush "Some brush")
Additionally, the 3rd argument there (2nd img) should be a drawable:
(gimp-xcf-save 1 img img "tmp.xcf" "tmp.xcf")
PS: do yourself a favor and write your script in Python. See here for an example.
Edit post-Gimp 2.10: there is now an API to stroke paths in "Line" mode which is the best way to obtain clean results. The relevant calls are:
pdb.gimp_context_set_stroke_method(STROKE_LINE)
pdb.gimp_context_set_line_cap_style(...)
pdb.gimp_context_set_line_join_style(...)
pdb.gimp_context_set_line_miter_limit(...)
pdb.gimp_context_set_line_width(width)
pdb.gimp_drawable_edit_stroke_item(drawable, path)

For those who may come along later, based on xenoid's hints, I made this using gimp-pencil.
[I found no documentation for the 'item/path' of gimp_drawable_edit_stroke_item]
Could be tweaked to use gimp-brush...
(define (util-draw-path drawable args . path)
;; Draw a zig-zag line:
;; (util-draw-path layer `((width 10) (color ,RED)) 10 50 40 80 70 30 110 90 150 80)
(let ((save (util-assq 'save args) #t)
(color (util-assq 'color args)) ; else use context value
(width (util-assq 'width args)) ; else use context value
(miter (util-assq 'miter args)) ; else use context value
(join (util-assq 'join args)) ; else use context value
(cap (util-assq 'cap args)) ; else use context value
(stroke (util-assq 'stroke args)) ; else use context value
)
(and save (gimp-context-push))
(and miter (gimp-context-set-line-miter-limit miter)) ; default: 10, default mitre up to 60 pixels
(and stroke (gimp-context-set-stroke-method stroke)) ; default STROKE-PAINT-METHOD
(and cap (gimp-context-set-line-cap-style cap)) ; CAP-ROUND, CAP-SQUARE
(and join (gimp-context-set-line-join-style join)) ; JOIN-MITER, JOIN-ROUND, JOIN-BEVEL
(and color (gimp-context-set-foreground color)) ;
(and width (gimp-context-set-line-width width)) ; default: 6
(let ((vec (apply vector path)))
(gimp-pencil drawable (vector-length vec) vec))
(and save (gimp-context-pop))
))
;;; (util-assq key list [default]) -> value or [default or #f]
(macro (util-assq form)
(let ((key (cadr form)) (lis (caddr form))
(els (if (pair? (cdddr form)) (cadddr form) #f)))
`(let ((v (assq ,key ,lis)))
(if v (cadr v) ,els))))

Related

GIMP Scheme Error: ( : 1) eval: unbound variable: strbreakup

I found this wonderful script on github and used it successfully with GIMP on Windows 7. I recently upgraded to Windows 10, and now it will not work. I get the following error:
Error while executing script-fu-batch-smart-resizer:
Error: ( : 1) eval: unbound variable: strbreakup
Here is the code:
; https://github.com/per1234/batch-smart-resize
(define (script-fu-batch-smart-resize sourcePath destinationPath filenameModifier outputType outputQuality maxWidth maxHeight pad padColor . JPEGDCT)
(define (smart-resize fileCount sourceFiles)
(let*
(
(filename (car sourceFiles))
(image (car (gimp-file-load RUN-NONINTERACTIVE filename filename)))
)
(gimp-image-undo-disable image)
;crop to mask if one exists
(if (not (= (car (gimp-layer-get-mask (car (gimp-image-get-active-layer image)))) -1)) (plug-in-autocrop RUN-NONINTERACTIVE image (car (gimp-layer-get-mask (car (gimp-image-get-active-layer image))))))
;image manipulation
(let*
(
;get cropped source image dimensions
(sourceWidth (car (gimp-image-width image)))
(sourceHeight (car (gimp-image-height image)))
;don't resize image to larger than original dimensions
(outputMaxWidth (if (< sourceWidth maxWidth) sourceWidth maxWidth))
(outputMaxHeight (if (< sourceHeight maxHeight) sourceHeight maxHeight))
(outputWidth (if (< (/ sourceWidth sourceHeight) (/ outputMaxWidth outputMaxHeight)) (* (/ outputMaxHeight sourceHeight) sourceWidth) outputMaxWidth))
(outputHeight (if (> (/ sourceWidth sourceHeight) (/ outputMaxWidth outputMaxHeight)) (* (/ outputMaxWidth sourceWidth) sourceHeight) outputMaxHeight))
)
(gimp-image-scale image outputWidth outputHeight) ;scale image to the output dimensions
;pad
(if (= pad TRUE)
(begin
(gimp-image-resize image maxWidth maxHeight (/ (- maxWidth outputWidth) 2) (/ (- maxHeight outputHeight) 2)) ;resize canvas to to maximum dimensions and center the image
;add background layer
(let*
(
(backgroundLayer (car (gimp-layer-new image maxWidth maxHeight RGB-IMAGE "Background Layer" 100 NORMAL-MODE))) ;create background layer
)
(let*
(
(backgroundColor (car (gimp-context-get-background))) ;save the current background color so it can be reset after the padding is finished
)
(gimp-context-set-background padColor) ;set background color to the padColor
(gimp-drawable-fill backgroundLayer 1) ;Fill the background layer with the background color. I have to use 1 instead of FILL-BACKGROUND because GIMP 2.8 uses BACKGROUND-FILL.
(gimp-context-set-background backgroundColor) ;reset the background color to the previous value
)
(gimp-image-insert-layer image backgroundLayer 0 1) ;add background layer to image
)
)
)
)
(gimp-image-flatten image) ;flatten the layers
(let*
(
;format filename - strip source extension(from http://stackoverflow.com/questions/1386293/how-to-parse-out-base-file-name-using-script-fu), add filename modifier and destination path
(outputFilenameNoExtension
(string-append
(string-append destinationPath "/")
(unbreakupstr
(reverse
(cdr
(reverse
(strbreakup
(car
(reverse
(strbreakup filename (if isLinux "/" "\\"))
)
)
"."
)
)
)
)
"."
)
filenameModifier
)
)
)
;save file
(cond
((= outputType 0)
(let*
(
(outputFilename (string-append outputFilenameNoExtension ".png")) ;add the new extension
)
(file-png-save RUN-NONINTERACTIVE image (car (gimp-image-get-active-drawable image)) outputFilename outputFilename FALSE 9 TRUE FALSE FALSE TRUE TRUE)
)
)
((= outputType 1)
(let*
(
(outputFilename (string-append outputFilenameNoExtension ".jpg")) ;add the new extension
)
(file-jpeg-save RUN-NONINTERACTIVE image (car (gimp-image-get-active-drawable image)) outputFilename outputFilename (/ outputQuality 100) 0 TRUE TRUE "" 2 TRUE 0 (if (null? JPEGDCT) 0 (car JPEGDCT)))
)
)
(else
(let*
(
(outputFilename (string-append outputFilenameNoExtension ".gif")) ;add the new extension
)
(gimp-image-convert-indexed image 1 0 256 TRUE TRUE "")
(file-gif-save RUN-NONINTERACTIVE image (car (gimp-image-get-active-drawable image)) outputFilename outputFilename FALSE FALSE 0 0)
)
)
)
)
(gimp-image-delete image)
)
(if (= fileCount 1) 1 (smart-resize (- fileCount 1) (cdr sourceFiles))) ;determine whether to continue the loop
)
;detect OS type(from http://www.gimp.org/tutorials/AutomatedJpgToXcf/)
(define isLinux
(>
(length (strbreakup sourcePath "/" ) ) ;returns the number of pieces the string is broken into
(length (strbreakup sourcePath "\\" ) )
)
)
(define sourceFilesGlob (file-glob (if isLinux (string-append sourcePath "/*.*") (string-append sourcePath "\\*.*")) 0))
(if (pair? (car (cdr sourceFilesGlob))) ;check for valid source folder(if this script is called from another script they may have passed an invalid path and it's much more helpful to return a meaningful error message)
(smart-resize (car sourceFilesGlob) (car (cdr sourceFilesGlob)))
(error (string-append "Invalid Source Folder " sourcePath))
)
)
;dialog
(script-fu-register
"script-fu-batch-smart-resize" ;function name
"batch-smart-resize" ;menu label
"Crop to layer mask, resize within maximum dimensions, and pad to max dimensions(optional)" ;description
"per1234" ;author
"" ;copyright notice
"2015-10-02" ;date created
"" ;image type
SF-DIRNAME "Source Folder" "" ;sourcePath
SF-DIRNAME "Destination Folder" "" ;destinationPath
SF-STRING "Output Filename Modifier(appended)" "" ;filenameModifier
SF-OPTION "Output Type" '("PNG" "JPEG" "GIF") ;outputType
SF-VALUE "Output Quality(JPEG only) 0-100" "90" ;outputQuality
SF-VALUE "Max Width" "1500" ;maxWidth
SF-VALUE "Max Height" "1500" ;maxHeight
SF-TOGGLE "Pad" TRUE ;pad
SF-COLOR "Padding Color" "white" ;padColor
)
(script-fu-menu-register "script-fu-batch-smart-resize"
"<Image>/Tools") ;menu location
I have tried just about everything I could find online, and this is my last resort. Am I missing syntax that was acceptable on the Windows 7 version, that is not so in the Windows 10 version?
Thanks!
strbreakup is defined in script-fu-compat.init in /usr/share/gimp/2.0/scripts (or C:\Program Files\GIMP 2\share\gimp\2.0\scripts for Windows). Is this file present and complete (372 lines in my working version)?
Edit: summary from comments: Gimp didn't look in its standard scripts directory. The directory above should be listed in Edit>Preferences>Folders>Scripts.

.Dynamically changing the clock tick rate in big-bang

The on-tick clause includes an option to change the clock tick rate. I have included my code to dynamically change the value depending on the world state value. The code is not working and I can't understand why. Another issue - how are world programs debugged? The "step" option doesn't work.
; physical constants
(define HEIGHT 300)
(define WIDTH 100)
(define YDELTA 3)
; graphical constants
(define BACKG (empty-scene WIDTH HEIGHT))
(define ROCKET (rectangle 5 30 "solid" "red"))
(define ROCKET-CENTER (/ (image-height ROCKET) 2))
(define ROCKET-XPOS 10)
; LRCD -> LRCD
(define (main1 s)
(big-bang s
[to-draw show]
[on-key launch]
[on-tick fly (clock-rate s)]))
; LRCD -> Image
; renders the state as a resting or flying rocket
(define (show x)
(cond
[(string? x) (rocket-ht HEIGHT)]
[(<= -3 x -1)
(place-image (text (number->string x) 20 "red")
ROCKET-XPOS (* 3/4 WIDTH)
(rocket-ht HEIGHT))]
[(>= x 0)
(rocket-ht x)]))
; LRCD -> image
; positions the rocket at correct height
(define (rocket-ht ht)
(place-image ROCKET ROCKET-XPOS (- ht ROCKET-CENTER) BACKG))
; LRCD KeyEvent -> LRCD
; starts the count-down when space bar is pressed,
; if the rocket is still resting
(define (launch x ke)
(cond
[(string? x) (if (string=? ke " ") -3 x)]
[else x]))
; LRCD -> LRCD
; raises the rocket by YDELTA,
; if it is moving already
(define (fly x)
(cond
[(string? x) x]
[(<= -3 x -1) (if (= x -1) HEIGHT (add1 x))]
[else (- x YDELTA)]))
(define (clock-rate s)
(cond
[(number? s) (if (< s 0) 1 1/28)]
[else 1/28]))

Make a function that makes line segment coordinates centered at point (x y) with slope m

I have a plotting library I'm building source, and want to plot slope lines. I have a function (draw-seg-list device color lst) with the lst arg being a list containing lists with the start and stop cords of a line (x0 y0 x1 y1). I want to make a function (make-slope-seg x y m) that then returns the point list for a line segment centered at (x, y) with slope m.
Example: (make-slope-seg 0 0 0) -> (-.05 0 .05 0) and (make-slope-seg .1 .1 1) -> (.05 .05 .15 .15)
The non-working function I have is:
(define (make-slope-cords x y m)
(list (- x .05)
(* y m -1)
(+ x .05)
(* y m)))
Which returns the incorrect lines. If I use :
;makes graphics window
(define window (make-graphics-device 'win32))
;plots blue line for function y = x^2 with black axis
(make-plot window 'simple-plot (list "white" "black" "blue" (list (range -1 1 .01) square)))
;makes list of lists containing the slope and x y cords that the slope lines
;are supposed to be centered at
(define cords (map (lambda (s x y)
(list s x y))
(map (lambda (x) (* 2 x)) (range -1 1 .1))
(range -1 1 .1)
(map square (range -1 1 .1))))
;plots the line segments generated by mapping make-slope-cords to the coordinate list
(draw-seg-list window "red"
(map (lambda (lst)
(make-slope-cords (car lst) (cadr lst) (caddr lst)))
cords))
It outputs the following:
But I want it to output red lines of width .1 (1 square on the grid in the image) with slope being the slope of the blue line(lambda (x) (square x)) at each point spaced by .1 along the x axis.
NOTE: assume that draw-seg-list works. I just need assistance in making the function make-slope-cords produce a the correct list of cordinates
Well experimenting around I was able to determine the answer.
(define (make-sloped-seg x y m)
(define b (- y (* m x)))
(list (- x .03)
(+ (* m (- x .03)) b)
(+ x .03)
(+ (* m (+ x .03)) b)))
It determines the y-intercept (b) at the beginning of the calculation, and then generates the points using the correct intercept
example:
;makes graphics window
(define window (make-graphics-device 'win32))
;plots blue line for function y = x^2 with black axis
(make-plot window 'simple-plot (list "white" "black" "blue" (list (range -1 1 .01) square)))
;makes list of lists containing the slope and x y cords that the slope lines
;are supposed to be centered at
(define cords (map (lambda (s x y)
(list s x y))
(map (lambda (x) (* 2 x)) (range -1 1 .1))
(range -1 1 .1)
(map square (range -1 1 .1))))
;plots the line segments generated by mapping make-slope-cords to the coordinate list
(draw-seg-list window "red"
(map (lambda (lst)
(make-slope-cords (car lst) (cadr lst) (caddr lst)))
cords))
outputs the following:

In scheme I keep getting "Error: ( : 1) car: argument 1 must be: pair", why?

I found this page explaining that some of the gimp functions won't return values consistently, so I implemented a do while loop to make sure the functions are returning pairs before using car. Still, I get the error Error: ( : 1) car: argument 1 must be: pair, but I'm not sure how that's possible as it should keep running the function until it returns a pair.
(define (script-fu-scratchpad drawable)
(let* ((imgHeight 0)
(imgWidth)
(bpp)
(pixel))
(set! imgHeight (gimp-drawable-height drawable))
(do ()
[(pair? imgHeight)]
(set! imgHeight (gimp-drawable-height drawable)))
(set! imgHeight (car imgHeight))
(set! imgWidth (gimp-drawable-width drawable))
(do ()
[(pair? imgWidth)]
(set! imgWidth (gimp-drawable-width drawable)))
(set! imgWidth (car imgWidth))
(set! bpp (gimp-drawable-bpp drawable))
(do ()
[(pair? bpp)]
(set! bpp (gimp-drawable-bpp drawable)))
(set! bpp (car bpp))
(display bpp) (newline)
(set! pixel (cons-array bpp 'byte))
(aset pixel 0 150)
(aset pixel 1 150)
(aset pixel 2 150)
(aset pixel 3 0)
(gimp-drawable-set-pixel drawable (/ imgHeight 2) (/ imgWidth 2) bpp pixel)
(gimp-context-set-background '(100 100 100))
(define county 0)
(define countx 0)
(do ()
[(= countx imgWidth)]
(do ()
[(= county imgHeight)]
(gimp-drawable-set-pixel drawable county countx bpp pixel)
(set! county (+ county 1)))
(set! countx (+ countx 1)))))
In response to GoZoner, I edited it and received the following error: Error: (:1) car: argument 1 must be: pair
(define
(script-fu-scratchpad drawable)
(let*
(
(imgHeight 0)
(imgWidth 0)
(bpp 0)
(pixel 0)
)
(set! imgHeight (gimp-drawable-height drawable))
(set! imgWidth (gimp-drawable-width drawable))
(set! bpp (gimp-drawable-bpp drawable))
(do ()
[(pair? bpp)]
(set! bpp (gimp-drawable-bpp drawable))
)
(set! bpp (car bpp))
(display bpp) (newline)
(set! pixel (cons-array bpp 'byte))
(aset pixel 0 150)
(aset pixel 1 150)
(aset pixel 2 150)
(aset pixel 3 0)
(gimp-drawable-set-pixel drawable (/ imgHeight 2) (/ imgWidth 2) bpp pixel)
(gimp-context-set-background '(100 100 100))
(define county 0)
(define countx 0)
(do ()
[(= countx imgWidth)]
(do ()
[(= county imgHeight)]
(gimp-drawable-set-pixel drawable county countx bpp pixel)
(set! county (+ county 1))
)
(set! countx (+ countx 1))
)
)
)
A couple of things.
In your highest level let* you should be initializing each of the
variables rather than just imgHeight or none of them. Actual
Scheme requires all to be initialized.
Based on name along, I wouldn't expect (gimp-drawable-height drawable) to return a list/cons; it should return a height as a number. Therefore:
I can't imagine (pair? imgHeight) would ever be true
I would expect (car imgHeight) to fail - and it apparently has based on the error you've reported.
The function aset is presumably acting on a multidimensional ((>= rank 2)) array. Therefore its 'index' argument ought to have more then just a single integer. But, perhaps aset is just simply vector-ref in GIMP's scripting variant.
[EDIT: to be more specific] I've annotated your code
(set! bpp (gimp-drawable-bpp drawable)) ; bpp is NOT a pair
(do ()
[(pair? bpp)] ; bpp is NOT a pair
(set! bpp (gimp-drawable-bpp drawable)))
(set! bpp (car bpp)) ; bpp is NOT a pair => ERROR

Why can't I make two make function calls in function body?

So I'm going through the first chapter of How To Design Programs 2nd Edition. I believe I made pretty good progress. But there's a "suggestion" to add another graphic to the grid. Every time I try I get an error. At this point, I'm stuck. Below is the code and the error.
Note: the ROCKET image is in the Chapter 1. I just copy and pasted it into the IDE.
Note: The "suggestion" is: How would change the program so that the rocket lands on a flat rock bed that is 10 pixels higher than the bottom of the scene? Don’t forget to change the scenery, too.
HTDP Chapter 1
Here's code that works.
(define BOARDWIDTH 200)
(define BOARDHEIGHT 200)
(define STARTPOSITION 50)
(define BOARDBKGR "blue")
(define GAMEBOARD (empty-scene BOARDWIDTH BOARDHEIGHT BOARDBKGR))
(define ROCKET .)
(define UFO (overlay (circle 10 "solid" "red")
(rectangle 40 4 "solid" "green")))
(define FLATBED (rectangle 60 10 "outline" "black"))
(define (SPACESHIP option)
(cond
[(= option 1) ROCKET]
[(= option 2) UFO]))
(define SHOWNSHIP (SPACESHIP 1))
(define V 20) ;Velocity
(define A 1) ;Acceleration
(define (distance t) ;t = Time
(- (* V t) (* 1/2 A (sqr t))))
(define SPACESHIP-BOTTOM (- BOARDHEIGHT (/ (image-height SHOWNSHIP) 2)))
(define (render-shownship x y)
(place-image SHOWNSHIP x y GAMEBOARD))
(define (create-rocket-scene.v7 t)
(cond
[(<= (distance t) SPACESHIP-BOTTOM)
(render-shownship STARTPOSITION (distance t))]
[(> (distance t) SPACESHIP-BOTTOM)
(render-shownship STARTPOSITION SPACESHIP-BOTTOM)]))
Here's the code that doesn't work:
(define BOARDWIDTH 200)
(define BOARDHEIGHT 200)
(define STARTPOSITION 50)
(define BOARDBKGR "blue")
(define GAMEBOARD (empty-scene BOARDWIDTH BOARDHEIGHT BOARDBKGR))
(define ROCKET .)
(define UFO (overlay (circle 10 "solid" "red")
(rectangle 40 4 "solid" "green")))
(define FLATBED (rectangle 60 10 "outline" "black"))
(define (SPACESHIP option)
(cond
[(= option 1) ROCKET]
[(= option 2) UFO]))
(define SHOWNSHIP (SPACESHIP 1))
(define V 20) ;Velocity
(define A 1) ;Acceleration
(define (distance t) ;t = Time
(- (* V t) (* 1/2 A (sqr t))))
(define SPACESHIP-BOTTOM (- BOARDHEIGHT (/ (image-height SHOWNSHIP) 2)))
(define (render-shownship x y)
(place-image SHOWNSHIP x y GAMEBOARD)
(place-image FLATBED STARTPOSITION 195 GAMEBOARD)) ;offender
(define (create-rocket-scene.v7 t)
(cond
[(<= (distance t) SPACESHIP-BOTTOM)
(render-shownship STARTPOSITION (distance t))]
[(> (distance t) SPACESHIP-BOTTOM)
(render-shownship STARTPOSITION SPACESHIP-BOTTOM)]))
And the error I get is:
define: expected only one expression for the function body, but found
1 extra part
place-image always takes 4 arguments - the image to be placed, x and y coordinates, and the scene (background) on which to place the image. The problem in your code is that the expression (place-image FLATBED STARTPOSITION 195) is providing only 3 inputs to place-image.
So, back up a little and consider: what does the first expression produce? (place-image SHOWNSHIP x y GAMEBOARD) produces a game board scene with a ship on it, correct? Now on top of that scene you further want to place the FLATBED. So instead of sequencing the place-image function calls, instead consider composing them - i.e. what do you think the missing piece is in (place-image FLATBED STARTPOSITION 195 ____)? upon what scene do you want to place the FLATBED? (Hint: we just answered that above). What expression produces that scene? (hint: you already have that expression).
If you understand the idea, you see that to place multiple images on a scene, you compose or nest the function calls (instead of sequencing them as you are attempting):
(place-image img1 x1 y1 (place-image img2 x2 y2 ...))

Resources