Posts: 10
Threads: 2
Joined: Sep 2022
Reputation:
0
Gimp version:
Operating system(s): Windows 10
Hello.
I sometimes use a set of grunge brushes which tile seamlessly. Very good for game textures and other 3d scenes. To fit them to the image currently requires a couple of steps which I'd prefer to simplify:
1. Manually set brush size to image size (this usually applies to square images so the size value on its own is usually enough).
2. Create horizontal and vertical guides at 50% each (or zoom in and be careful). I recently installed a guides plugin which makes this a little easier.
Another option I read about was to set up the grid so that its lines cross at the centre, but that's quite a few steps and some people might want the grid for other uses.
The above may only be 2 steps but I may have several layers with different grunge brushes, and/or be making several images, so those 2 steps can multiply quite quickly. Some repetition will be needed if, between adding grunge brushes, I change the brush size for other things (e.g. eraser, painting, cloning etc).
Does anyone know if there are any addons that could simplify the above process? If not, I'd be happy to raise a feature request on the GNome page for 3.2 (I'm aware that 3.0 is now having just it's existing features tweaked, which makes sense).
Posts: 6,515
Threads: 284
Joined: Oct 2016
Reputation:
572
Gimp version:
Operating system(s): Linux
(01-14-2024, 01:21 PM)R Soul Wrote: Hello.
I sometimes use a set of grunge brushes which tile seamlessly. Very good for game textures and other 3d scenes. To fit them to the image currently requires a couple of steps which I'd prefer to simplify:
1. Manually set brush size to image size (this usually applies to square images so the size value on its own is usually enough).
Which image? The target image or the image used as a brush? if the former, you are using a single brush stroke in the whole image (then why a brush?), if the later, there is a reset button in the Tool options to reset the brush to its native size (and if you switch the brush, the size is reset to the native size of the new brush).
(01-14-2024, 01:21 PM)R Soul Wrote: 2. Create horizontal and vertical guides at 50% each (or zoom in and be careful). I recently installed a guides plugin which makes this a little easier.
Another option I read about was to set up the grid so that its lines cross at the centre, but that's quite a few steps and some people might want the grid for other uses.
The above may only be 2 steps but I may have several layers with different grunge brushes, and/or be making several images, so those 2 steps can multiply quite quickly. Some repetition will be needed if, between adding grunge brushes, I change the brush size for other things (e.g. eraser, painting, cloning etc).
Does anyone know if there are any addons that could simplify the above process? If not, I'd be happy to raise a feature request on the GNome page for 3.2 (I'm aware that 3.0 is now having just it's existing features tweaked, which makes sense).
Not very hard to make a script that makes an appropriately centered grid that matches the current brush size... (which IMHO is more practical than guides, because you would have to remove all existing guides each time you change a brush and these do have other purposes). For extra credit: should the grid be aligned with the image or with the active layer?
Posts: 10
Threads: 2
Joined: Sep 2022
Reputation:
0
Gimp version:
Operating system(s): Windows 10
(01-14-2024, 02:10 PM)Ofnuts Wrote: Which image? The target image or the image used as a brush? if the former, you are using a single brush stroke in the whole image (then why a brush?), if the later, there is a reset button in the Tool options to reset the brush to its native size (and if you switch the brush, the size is reset to the native size of the new brush).
The target image. The reason for using a brush/single stroke is that these things are already brushes. See here for their website: https://www.deviantart.com/redheadstock/...s-65840762
Quote:Not very hard to make a script that makes an appropriately centered grid that matches the current brush size... (which IMHO is more practical than guides, because you would have to remove all existing guides each time you change a brush and these do have other purposes). For extra credit: should the grid be aligned with the image or with the active layer?
It might be easier to show what I'm currently doing by posting screenshots:
1. Load some base image, could be any size but I'd usually make them sqaure or nearst power of 2 for typical game requirements:
2. Choose the desired grunge brush. Note it's 1000x1000. Some base images will be a lot bigger or smaller, so resizing them to match the brush isn't always ideal.
3. Resize the brush to match the image size, and add guides:
4. Paint. Sometimes multiple clicks/strokes to get more boldness (or edit layer colours/alpha afterwards):
Posts: 6,515
Threads: 284
Joined: Oct 2016
Reputation:
572
Gimp version:
Operating system(s): Linux
OK, so what you really want is something that takes a brush, scales it to the size of a layer/image, and then strokes it on the current layer?
Posts: 10
Threads: 2
Joined: Sep 2022
Reputation:
0
Gimp version:
Operating system(s): Windows 10
That's right. Initially my expectation was that the user would stroke the brush, but it makes sense to include one stroke in the script as it removes the need for adding guidelines. If there's nothing out there I could have a go at writing my own. I've written some python addons for Blender but my Gimp scripting experience is quite low.
Posts: 6,515
Threads: 284
Joined: Oct 2016
Reputation:
572
Gimp version:
Operating system(s): Linux
Already done. See ofn-brush-to-layer just uploaded here.
Posts: 10
Threads: 2
Joined: Sep 2022
Reputation:
0
Gimp version:
Operating system(s): Windows 10
I've got a barebones bit of python code - it sets the brush size and works out the centre coords.
I'v highlighted in red where I'm stuck.
Aspect ratio: not sure what the maths is here
Drawing/stroking: not sure what are valid parameters, correct syntax etc.
Menu location: similarly, not sure of correct menu placement code.
from gimpfu import *
def add_scaled_brush(img, layer):
# Set up an undo group, so the operation will be undone in one step.
pdb.gimp_undo_push_group_start(img)
# Do stuff here.
xCentre = img.width/2
yCentre = img.height/2
brushSize = float(img.width)
pdb.gimp_context_set_brush_size(brushSize)
aspectRatio = 0.0 #TO DO - calculate based on image height
pdb.gimp_context_set_brush_aspect_ratio(aspectRatio)
newLayer = pdb.gimp_layer_new(img, img.width, img.height, RGBA_IMAGE, "Scaled Brush", 100.0, LAYER_MODE_NORMAL)
pdb.gimp_image_insert_layer(img, newLayer, None, 0)
#draw with the current brush
#something to do with pdb.gimp_paintbrush
# Close the undo group.
pdb.gimp_undo_push_group_end(img)
register(
"add_scaled_brush", #Name
"Add Scaled Brush to Image", #Blurb
"Scale current brush to image, paint once to new layer.", #help
"R Soul", #Author
"CC Licence", #Copyright
"2024", #Date
"<Image>/Image/Add Scaled Brush", #Menu path, needs to go somewhere else
"", # image types
[ # No Gui Params
],
[], # Results
add_scaled_brush) #function
main()
Posts: 6,515
Threads: 284
Joined: Oct 2016
Reputation:
572
Gimp version:
Operating system(s): Linux
See post above yours for the crib sheet.
Posts: 10
Threads: 2
Joined: Sep 2022
Reputation:
0
Gimp version:
Operating system(s): Windows 10
01-15-2024, 09:16 PM
(This post was last modified: 01-15-2024, 09:16 PM by R Soul.)
Many thanks for your help. I see where I'd gone the wrong way with some of my values. I decided that I still want it to create a new layer automatically, so I modified your code to have it draw on a new layer which is based on the current selection and offset (changes occur after the code to set the brush size):
Code:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# GIMP plugin to ifll the current layer with a stroke of a brush
# (c) Ofnuts 2023
#
# History:
#
# v0.0: 2024-01-14: Initial version
#
# 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.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
import sys,os,os.path,traceback,random
from gimpfu import *
import gimpcolor
debug='OFN_DEBUG' in os.environ
def trace(format,*args):
if debug:
print format % args
def strokeBrushOnLayer(image,drawable):
#pdb.gimp_plugin_enable_precision()
image.undo_group_start()
gimp.context_push()
try:
# Get brush data
_, _, _, _, bw, bh, _, _ = pdb.gimp_brushes_get_brush_data("") # Data for current brush
lw,lh=drawable.width,drawable.height
ox,oy=drawable.offsets
scaleX=float(lw)/bw # required for horizontal fill
scaleY=float(lh)/bh # required for vertical fill
scale=max(scaleX,scaleY)
size=int(round(max(bw,bh)*scale)) # Brush size is size of biggest dimension
trace("Brush is %dx%d, sized to %d (%5.2f)",bw,bh,size,scale)
pdb.gimp_context_set_brush_size(size)
newLayer = pdb.gimp_layer_new(image, lw, lh, RGBA_IMAGE, "Scaled Brush", 100.0, LAYER_MODE_NORMAL)
pdb.gimp_layer_set_offsets(newLayer, ox, oy)
pdb.gimp_image_insert_layer(image, newLayer, None, 0)
pdb.gimp_paintbrush_default(newLayer, 2, [lw/2,lh/2])
except Exception as e:
print e.args[0]
gimp.message(e.args[0])
print traceback.format_exc()
gimp.context_pop()
image.undo_group_end()
gimp.displays_flush()
### Registration
author='Ofnuts'
copyrightYear='2024'
desc='Fill layer with scaled current brush'
register(
'ofn-brush-to-layer',
desc,desc,author,author,copyrightYear,desc+'...',
'RGB*',
[
(PF_IMAGE, 'image', 'Input image', None),
(PF_DRAWABLE, 'layer', 'Input layer', None),
],
[],
strokeBrushOnLayer,
menu='<Image>/Test' if debug else '<Image>/Filters/Render/'
)
main()
Posts: 10
Threads: 2
Joined: Sep 2022
Reputation:
0
Gimp version:
Operating system(s): Windows 10
02-16-2025, 09:39 PM
(This post was last modified: 02-16-2025, 09:40 PM by R Soul.)
Bump!
This may be better in its own thread but it could be such a niche thing that it's fine here:
I've been updating the above for Gimp 3 (currently on RC3)
Got as far as setting the brush parameters and creating a new layer. I browsed from Gimp's Python console, but each thing seemed to need an awful lot of lines. The brush codes work, but for the layer code I may have been using wrong parameters as that didn't work. The only thing that did work was finding and copying how it was done in spyro-plus.py (just two lines of code...)
Here's what I have so far (started with the python example with used the GEGL colour invert function):
Code:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# 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 3 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.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
import gi
gi.require_version('Gimp', '3.0')
from gi.repository import Gimp
gi.require_version('GimpUi', '3.0')
from gi.repository import GimpUi
gi.require_version('Gegl', '0.4')
from gi.repository import Gegl
from gi.repository import GObject
from gi.repository import GLib
from gi.repository import Gio
import os
import sys
def N_(message): return message
def _(message): return GLib.dgettext(None, message)
def set_brush_value(procedure_name, property_name, property_value):
procedure = Gimp.get_pdb().lookup_procedure(procedure_name)
config = procedure.create_config()
config.set_property(property_name, property_value)
result = procedure.run(config)
success = result.index(0)
def set_brush_state(width, height):
set_brush_value('gimp-context-set-brush-aspect-ratio', 'aspect', 0)
set_brush_value('gimp-context-set-brush-angle', 'angle', 0)
set_brush_value('gimp-context-set-brush-hardness', 'hardness', 1) #values are 0 to 1, not 0 to 100
set_brush_value('gimp-context-set-brush-size', 'size', max(width, height))
def draw_brush(canvas, width, height):
procedure = Gimp.get_pdb().lookup_procedure('gimp-paintbrush-default')
config = procedure.create_config()
config.set_property('drawable', canvas)
config.set_property('strokes', [width/2, height/2])
result = procedure.run(config)
success = result.index(0)
class AddScaledBrush (Gimp.PlugIn):
## GimpPlugIn virtual methods ##
def do_query_procedures(self):
return [ "add-scaled-brush" ]
def do_create_procedure(self, name):
procedure = Gimp.ImageProcedure.new(self, name,
Gimp.PDBProcType.PLUGIN,
self.run, None)
procedure.set_image_types("*")
procedure.set_sensitivity_mask (Gimp.ProcedureSensitivityMask.DRAWABLE)
procedure.set_menu_label(_("Add Scaled Brush (WIP)"))
procedure.set_icon_name(GimpUi.ICON_GEGL)
procedure.add_menu_path('<Image>/Image/')
procedure.set_attribution("Ofnuts", "Ofnuts", "2023")
return procedure
def run(self, procedure, run_mode, image, drawables, config, run_data):
if len(drawables) != 1:
msg = _("Procedure '{}' only works with one drawable.").format(procedure.get_name())
error = GLib.Error.new_literal(Gimp.PlugIn.error_quark(), msg, 0)
return procedure.new_return_values(Gimp.PDBStatusType.CALLING_ERROR, error)
else:
drawable = drawables[0]
if run_mode == Gimp.RunMode.INTERACTIVE:
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
gi.require_version('Gdk', '3.0')
from gi.repository import Gdk
GimpUi.init("add-scaled-brush.py")
intersect, x, y, width, height = drawable.mask_intersect()
set_brush_state(width, height)
if intersect:
## Test Section ##
new_layer = Gimp.Layer.new(image, "Scaled Brush", width, height, Gimp.ImageType.RGBA_IMAGE, 100, Gimp.LayerMode.NORMAL)
image.insert_layer(new_layer, drawable.get_parent(), image.get_item_position(drawable))
draw_brush(drawable, width, height)
## End of Test Section ##
return procedure.new_return_values(Gimp.PDBStatusType.SUCCESS, GLib.Error())
Gimp.main(AddScaledBrush.__gtype__, sys.argv)
Notes:
The set_brush_value function was copied from Gimp's Python browser and works fine (note it gets called 4 times from set_brush_sate due to wanting to be thorough about which brush parameters are being set)
The draw_brush function does not work, and there's probably something much simpler, but I've no idea where to look. It'll be Gimp.something but apart from that I'm stumped.
|