Skip to content

Commit

Permalink
Dual Bloom 2 based on GEGL's existing bloom.
Browse files Browse the repository at this point in the history
https://gitlab.gnome.org/GNOME/gegl/-/tree/master/operations
There's some undocumented operations there, including an existing bloom
used in GIMP already, so I made a copy of the dualbloom plugin using the
new operation. It's basically identical except the widgets and gegl
function are set up for "gegl:bloom" instead, lowering complexity
slightly. Next will be either non-linear sliders or a new script
involving per-pixel math depending on which is less painful.
  • Loading branch information
Beinsezii committed Apr 23, 2020
1 parent 06e71d7 commit e25cdb7
Show file tree
Hide file tree
Showing 9 changed files with 351 additions and 3 deletions.
16 changes: 15 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,22 @@ Currently at the "I *think* I understand this now" phase.
Needs the bszgw.py file from https://github.com/Beinsezii/BSZGW at the root. Will be bundled with release tags.

## Current Plugins
### Dual Bloom 2
Produces both a light and dark bloom, based on gimp/gegl's existing bloom. Need to figure out a better slider system, so right now entries are hard instead of soft-capped.

<img width=300 src="./bsz-dualbloom2/during.png" />
<table class="img-compare">
<tr>
<th><img width=200 src="./bsz-dualbloom2/before.png" alt="Before" /></th>
<th><img width=200 src="./bsz-dualbloom2/after.png" alt="After" /></th>
</tr>
<tr>
<td>Before</td>
<td>After</td>
</tr>
</table>
### Dual Bloom
Produces both a light and a dark bloom based on thresholds, with as many config options as I can squeeze into GEGL. Best explanation is to try it.
Produces both a light and a dark bloom based on thresholds, with as many config options as I can squeeze into GEGL. This preceeded Dual Bloom 2, and the results are similar but not exact.

<img width=300 src="./bsz-dualbloom/during.png" />
<table class="img-compare">
Expand Down
Binary file modified bsz-dualbloom/after.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified bsz-dualbloom/before.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions bsz-dualbloom/bsz-dualbloom.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,8 @@ def do_create_procedure(self, name):
# Help text. First set is in-menu, second is PDB
procedure.set_documentation(
"Provides light and dark bloom using thresholds. See tooltips",
"Provides light and dark bloom using thresholds. \
Only usable in interactive mode right now.",
"Provides light and dark bloom using thresholds."
"Only usable in interactive mode right now.",
name
)
# Me
Expand Down
Binary file modified bsz-dualbloom/during.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added bsz-dualbloom2/after.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added bsz-dualbloom2/before.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
334 changes: 334 additions & 0 deletions bsz-dualbloom2/bsz-dualbloom2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,334 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
Use these for reference on gegl operations and their properties/inputs/outputs.
http://www.gegl.org/operations/
https://gitlab.gnome.org/GNOME/gegl/-/tree/master/operations
Also build the .gir files using g-ir-doc-tool for additional insight.
If the docs don't have a description on something like class methods,
run python's help() on it to view it in the terminal.
Requires having a debug term open in gimp obvs
"""

# Uncomment as needed.
# I don't actually know if it's faster to not import some of the gi repo stuff
# since it probably gets imported later anyway ...right?
import gi
gi.require_version('Gimp', '3.0')
from gi.repository import Gimp
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 sys
import os
sys.path.append(os.path.dirname(os.path.realpath(__file__)) + '/../')
import bszgw
# from bsz_gimp_lib import GEGL_COMPOSITORS


class DualBloom2(Gimp.PlugIn):
# GimpPlugIn virtual methods
# Not completely sure how they work
# Why do they have 'do_' in front when it's never mentioned in the gir docs
def do_query_procedures(self):
# This section can also be used to provide translations,
# but I have no idea how it works or know any other languages
# so I'm going to ignore that for now.

# script name as it shows up in the PDB
return ["bsz-dualbloom2"]

def do_create_procedure(self, name):
# Will almost always be ImageProcedure using PLUGIN proctype
procedure = Gimp.ImageProcedure.new(
self, name,
Gimp.PDBProcType.PLUGIN,
# name of function if something other than 'run'
self.run, None
)
# Supported colorspaces
procedure.set_image_types("RGB*, GRAY*")
# Name in menu
procedure.set_menu_label("Dual Bloom 2")
# Icon. See Gimp-3.0.gir docs and gimp's icon folder for others
# Historically plugins that use the new Gegl operations use ICON_GEGL
# while the rest use ICON_SYSTEM_RUN
procedure.set_icon_name(Gimp.ICON_GEGL)
# Location in the top menu, with <Image> being root
procedure.add_menu_path('<Image>/Beinsezii/')
# Help text. First set is in-menu, second is PDB
procedure.set_documentation(
"Provides light and dark bloom using thresholds.\n"
"Based on GIMP/GEGL's existing bloom.",
"Provides light and dark bloom using thresholds.\n"
"Only usable in interactive mode right now.",
name
)
# Me
procedure.set_attribution("Beinsezii", "Beinsezii", "2020")
return procedure

def dualbloom2(self, drawable, amount_high, amount_low,
softness_high, softness_low, radius_high, radius_low,
strength_high, strength_low):

# Fairly certain mask_intersect() is the current selection mask
intersect, x, y, width, height = drawable.mask_intersect()
if intersect:
# start Gegl
Gegl.init(None)
# fetch main buffer
buff = drawable.get_buffer()
# fetch shadow aka "temp" buffer
shadow = drawable.get_shadow_buffer()
# create a new node tree/graph
tree = Gegl.Node()

# Input buffer node using main buffer
Input = tree.create_child("gegl:buffer-source")
Input.set_property("buffer", buff)

Bloom_High = tree.create_child("gegl:bloom")
Bloom_High.set_property("amount", amount_high)
Bloom_High.set_property("softness", softness_high)
Bloom_High.set_property("radius", radius_high)
Bloom_High.set_property("strength", strength_high)
Sub_High = tree.create_child("gegl:subtract")
Add_High = tree.create_child("gegl:add")

Invert_Low = tree.create_child("gegl:invert-gamma")
Bloom_Low = tree.create_child("gegl:bloom")
Bloom_Low.set_property("amount", amount_low)
Bloom_Low.set_property("softness", softness_low)
Bloom_Low.set_property("radius", radius_low)
Bloom_Low.set_property("strength", strength_low)
Invert_Low2 = tree.create_child("gegl:invert-gamma")

# Output buffer node using temp buffer
Output = tree.create_child("gegl:write-buffer")
Output.set_property("buffer", shadow)

# base image linked to the thresholds and Comp_Low
Input.link(Bloom_High)
Input.connect_to("output", Sub_High, "aux")
Input.link(Invert_Low)

# High bloom nodes
Bloom_High.link(Sub_High)
Sub_High.connect_to("output", Add_High, "aux")

# Low bloom nodes
Invert_Low.link(Bloom_Low)
Bloom_Low.link(Invert_Low2)
Invert_Low2.link(Add_High)

# Combine
Add_High.link(Output)

# Run the node tree
Output.process()

# Flush shadow buffer and combine it with main drawable
shadow.flush()
drawable.merge_shadow(True)

# Update everything.
drawable.update(x, y, width, height)
Gimp.displays_flush()

# I decided to name the function called by the PDB procedure 'run'
def run(self, procedure, run_mode, image, drawable, args, run_data):

# run_mode 'NONINTERACTIVE' is if another plugin calls it through PDB
# I don't understand the __gproperties__ things yet so am ignoring.
if run_mode == Gimp.RunMode.NONINTERACTIVE:
return "Non-interactive not supported."

# run_mode 'WITH_LAST_VALS' is when you use Ctrl-F aka 'Repeat'
# seems the gimp shelf isn't implemented yet, so kinda useless
if run_mode == Gimp.RunMode.WITH_LAST_VALS:
bszgw.Message("Repeat not supported yet.")
run_mode = Gimp.RunMode.INTERACTIVE

# run_mode 'INTERACTIVE' means clicked in the menu
if run_mode == Gimp.RunMode.INTERACTIVE:
# import the ui junk
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk # noqa: F401
gi.require_version('Gdk', '3.0')
from gi.repository import Gdk

def_blur = round((drawable.width() + drawable.height()) / 2 * 0.01)

# Creating all the UI widgets using my own BSZGW
am_tt = "Amount of {} bloom. Acts like a threshold."
amount_high = bszgw.Adjuster("High Amount",
15, 0, 100, 1, 5, decimals=2,
tooltip=am_tt.format('light')
)
amount_low = bszgw.Adjuster("Low Amount",
35, 0, 100, 1, 5, decimals=2,
tooltip=am_tt.format('dark')
)

sf_tt = "Softness of {} bloom threshold selection"
softness_high = bszgw.Adjuster("High Softness",
25, 0, 100, 1, 5, decimals=2,
tooltip=sf_tt.format('light'))
softness_low = bszgw.Adjuster("Low Softness",
25, 0, 100, 1, 5, decimals=2,
tooltip=sf_tt.format('dark'))

rd_tt = "Size of {} blur. Initial value set based on image size."
radius_high = bszgw.Adjuster("High Radius",
def_blur, 0, 100, 1, 5, decimals=2,
tooltip=rd_tt.format('light'))
radius_low = bszgw.Adjuster("Low Radius",
def_blur, 0, 100, 1, 5, decimals=2,
tooltip=rd_tt.format('dark'))

st_tt = "Strength of {} bloom."
strength_high = bszgw.Adjuster("High Strength",
50, 0, 100, 1, 5, decimals=2,
tooltip=st_tt.format('light'))
strength_low = bszgw.Adjuster("Low Strength",
50, 0, 100, 1, 5, decimals=2,
tooltip=st_tt.format('dark'))

# save the adjustments so they don't get lost when linking
s_l_adj = softness_low.adjustment
r_l_adj = radius_low.adjustment

def soft_chain_fn(widget):
if widget.get_active():
softness_low.adjustment = softness_high.adjustment
else:
softness_low.adjustment = s_l_adj
softness_low.value = softness_high.value
# Gimp.ChainButton() icon is busted for some reason
soft_chain = bszgw.CheckBox("Link", True)
soft_chain.connect("toggled", soft_chain_fn)
soft_chain_fn(soft_chain)

def radius_chain_fn(widget):
if widget.get_active():
radius_low.adjustment = radius_high.adjustment
else:
radius_low.adjustment = r_l_adj
radius_low.value = radius_high.value
radius_chain = bszgw.CheckBox("Link", True)
radius_chain.connect("toggled", radius_chain_fn)
radius_chain_fn(radius_chain)

# I'm using an oldschool dupe layer preview since that
# cool live preview doesn't seem to be usable in gir yet,
# at least that I've been able to find.
def preview(*args):
clear_preview()
if preview_check.value:
image.undo_freeze()
self.preview_layer = drawable.copy()
image.insert_layer(self.preview_layer, None, 0)
ui_run(self.preview_layer)
preview_check = bszgw.CheckBox(
"\"Live\" Preview", True,
tooltip="Preview updates after clicking a slider this button,"
" or resetting vals.\n"
"I can't do the cool truly live preview in stock GIMP."
)
# bound to self to avoid capturing
self.preview_layer = None

# # don't call self.widget.value directly in execute so you can run
# # the program without a UI.
# to avoid retyping for updating the preview and regular runs
def ui_run(drawable2):
self.dualbloom2(drawable2,
amount_high.value, amount_low.value,
softness_high.value, softness_low.value,
radius_high.value, radius_low.value,
strength_high.value, strength_low.value)

# deletes self.preview_layer and thaws undo
def clear_preview(*args):
if self.preview_layer:
image.remove_layer(self.preview_layer)
self.preview_layer = None
image.undo_thaw()

# clear, run, close window.
def run_button_fn(widget):
clear_preview()
ui_run(drawable)
app.destroy()
run_button = bszgw.Button("Run", run_button_fn)

def test_button_fn(widget):
clear_preview()
self.dualbloom2(drawable,
amount_high.value, amount_low.value,
softness_high.value, softness_low.value,
radius_high.value, radius_low.value,
strength_high.value, strength_low.value)

# sets all vals to default. Maybe I should implement .reset() in
# BSZGW so I can just `for widget in list: widget.reset()`
def reset_button_fn(widget):
amount_high.value = 50
amount_low.value = 50
softness_high.value = 25
softness_low.value = 25
radius_high.value = def_blur
radius_low.value = def_blur
strength_high.value = 50
strength_low.value = 50
reset_button = bszgw.Button("Reset Vals", reset_button_fn)

# Connections for live preview. GEGL's fast enough this
# works better than a dedicated 'preview' button.
for widget in [amount_high, amount_low,
softness_high, softness_low,
radius_high, radius_low,
strength_high, strength_low]:
widget.slider.connect("button-release-event", preview)
for widget in [reset_button, preview_check]:
widget.connect("clicked", preview)

# widgets will be boxed together visually as shown
box = bszgw.AutoBox([
[amount_high, amount_low],
[softness_high, (soft_chain, False, False, 0), softness_low],
[radius_high, (radius_chain, False, False, 0), radius_low],
[strength_high, strength_low],
[(preview_check, False, False, 0), reset_button, run_button],
])
# create the app window with the box
app = bszgw.App(
"BSZ Dual Bloom 2", box, 800, 400,
# hints it as a pop-up instead of a full window.
hint=Gdk.WindowTypeHint.DIALOG,
)
# Keep the aspect ratio reasonable.
app_geometry = Gdk.Geometry()
app_geometry.max_aspect = 3.0
app_geometry.min_aspect = 2.0
app.set_geometry_hints(None, app_geometry, Gdk.WindowHints.ASPECT)
# Clear if you quit without running anything
app.connect("destroy", clear_preview)

# gen a first preview and launch gui
preview()
app.launch()

# Don't actually really know what this does but seems important
return procedure.new_return_values(
Gimp.PDBStatusType.SUCCESS, GLib.Error()
)


# load plugin into gimp
Gimp.main(DualBloom2.__gtype__, sys.argv)
Binary file added bsz-dualbloom2/during.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit e25cdb7

Please sign in to comment.