Skip to content

Instantly share code, notes, and snippets.

@ZenulAbidin
Last active April 27, 2023 11:11
Show Gist options
  • Save ZenulAbidin/a655ba6ace9e05670f59aa8a08f00334 to your computer and use it in GitHub Desktop.
Save ZenulAbidin/a655ba6ace9e05670f59aa8a08f00334 to your computer and use it in GitHub Desktop.
Render duotone button plugin in GIMP
# Copyright 2021 Ali Sherief
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#!/usr/bin/python
from gimpfu import *
def button_render(image, threshold, x, y, is_nonrectangular):
pdb.gimp_image_undo_group_start(image)
# This plugin expects there be exactly 3 layers - no more, no less.
if len(image.layers) != 3:
raise RuntimeError("This plugin only supports 3 layers")
layer3 = image.layers[0]
layer2 = image.layers[1]
layer1 = image.layers[2]
# Save the old threshold and criterion here so we can restore them
old_threshold = pdb.gimp_context_get_sample_threshold_int()
old_crit = pdb.gimp_context_get_sample_criterion()
old_fgcolor = pdb.gimp_context_get_foreground()
old_opacity = pdb.gimp_context_get_opacity()
old_brush = pdb.gimp_context_get_brush()
pdb.gimp_context_set_sample_threshold_int(threshold)
pdb.gimp_context_set_sample_criterion(SELECT_CRITERION_V)
pdb.gimp_context_set_foreground((255,255,255))
pdb.gimp_context_set_opacity(100)
# Preserve as much as the old brush state as we can. The options
# "Apply Jitter", "Incremental", "Hard Edge", "Smooth Stroke" and
# "Lock Brush To View" cannot be programmatically changed, but
# these should all be off for the effect to work!
old_brush_size = pdb.gimp_context_get_brush_size()
old_brush_dynamics = pdb.gimp_context_get_dynamics()
old_brush_force = pdb.gimp_context_get_brush_force()
new_brush = pdb.gimp_brush_duplicate('2. Block 03')
pdb.gimp_context_set_brush(new_brush)
pdb.gimp_brush_set_aspect_ratio(new_brush, 20.0)
pdb.gimp_brush_set_angle(new_brush, 0.0)
pdb.gimp_brush_set_hardness(new_brush, 95.0)
pdb.gimp_brush_set_spacing(new_brush, 10.0)
pdb.gimp_brush_set_shape(new_brush, BRUSH_GENERATED_CIRCLE)
pdb.gimp_context_set_brush_size(180.0)
pdb.gimp_context_set_dynamics("Dynamics Off")
pdb.gimp_context_set_brush_force(0.5)
# Get dimentions of this image so we can create two new images of the same size.
# It also helps with creating channels as the width and height are needed there.
ch = pdb.gimp_channel_new(image, image.width, image.height, "_button_render_highlights", 100, (0,0,0)) # black
image.add_channel(ch)
# Now we make the selection...
pdb.gimp_image_select_contiguous_color(image, CHANNEL_OP_REPLACE, layer1, x, y)
pdb.gimp_selection_invert(image)
# ...Then we fill the selection inside the channel completely white...
pdb.gimp_edit_bucket_fill(ch, BUCKET_FILL_FG, LAYER_MODE_NORMAL, 100, 0, False, 0, 0)
# ...And then we slightly expand the selection to half-select the immediately surrounding pixels...
pdb.gimp_selection_grow(image, 1)
pdb.gimp_edit_bucket_fill(ch, BUCKET_FILL_FG, LAYER_MODE_NORMAL, 50, 0, False, 0, 0)
# ...And we do this a second time.
pdb.gimp_selection_grow(image, 1)
pdb.gimp_edit_bucket_fill(ch, BUCKET_FILL_FG, LAYER_MODE_NORMAL, 50, 0, False, 0, 0)
# And now our mask is ready!
# But first we make a bevel around the three layers.
pdb.gimp_selection_none(image)
pdb.gimp_image_set_active_layer(image, layer1)
pdb.script_fu_add_bevel(image, layer1, 5, False, False)
pdb.gimp_image_set_active_layer(image, layer2)
pdb.script_fu_add_bevel(image, layer2, 5, False, False)
pdb.gimp_image_set_active_layer(image, layer3)
pdb.script_fu_add_bevel(image, layer3, 5, False, False)
# If the button has transparent parts then we need to take
# that into account when creating the border.
# Otherwise we can do a simple select all followed by shrink
# to get the border selection.
pdb.gimp_selection_all(image)
pdb.gimp_selection_shrink(image, 1)
pdb.gimp_selection_invert(image)
# Create a channel and fill it with the selection.
ch_rect = pdb.gimp_channel_new(image, image.width, image.height, "_button_border_rect", 100, (0,0,0)) # black
image.add_channel(ch_rect)
pdb.gimp_edit_bucket_fill(ch_rect, BUCKET_FILL_FG, LAYER_MODE_NORMAL, 100, 0, False, 0, 0)
if is_nonrectangular:
# Non-rectangular buttons need a little more work to select the bounded parts.
pdb.gimp_image_select_item(image, CHANNEL_OP_REPLACE, layer1)
ch_work = pdb.gimp_channel_new(image, image.width, image.height, "_button_border_work", 100, (0,0,0)) # black
image.add_channel(ch_work)
pdb.gimp_edit_bucket_fill(ch_work, BUCKET_FILL_FG, LAYER_MODE_NORMAL, 100, 0, False, 0, 0)
pdb.gimp_selection_invert(image)
pdb.gimp_selection_grow(image, 1)
pdb.gimp_image_select_item(image, CHANNEL_OP_INTERSECT, layer1)
ch_round = pdb.gimp_channel_new(image, image.width, image.height, "_button_border_round", 100, (0,0,0)) # black
image.add_channel(ch_round)
pdb.gimp_edit_bucket_fill(ch_round, BUCKET_FILL_FG, LAYER_MODE_NORMAL, 100, 0, False, 0, 0)
pdb.gimp_image_select_item(image, CHANNEL_OP_REPLACE, ch_work)
pdb.gimp_image_select_item(image, CHANNEL_OP_INTERSECT, ch_rect)
pdb.gimp_image_select_item(image, CHANNEL_OP_ADD, ch_round)
ch_border = pdb.gimp_channel_new(image, image.width, image.height, "_button_border", 100, (0,0,0)) # black
image.add_channel(ch_border)
pdb.gimp_edit_bucket_fill(ch_border, BUCKET_FILL_FG, LAYER_MODE_NORMAL, 100, 0, False, 0, 0)
# Make the background
pdb.gimp_image_select_item(image, CHANNEL_OP_REPLACE, ch_border)
pdb.gimp_context_set_foreground((204, 204, 204)) #cccccc
pdb.gimp_edit_bucket_fill(layer1, BUCKET_FILL_FG, LAYER_MODE_NORMAL, 100, 0, False, 0, 0)
pdb.gimp_context_set_foreground((230, 230, 230)) #e6e6e6
pdb.gimp_edit_bucket_fill(layer2, BUCKET_FILL_FG, LAYER_MODE_NORMAL, 100, 0, False, 0, 0)
pdb.gimp_context_set_foreground((255, 255, 255)) #ffffff
pdb.gimp_edit_bucket_fill(layer3, BUCKET_FILL_FG, LAYER_MODE_NORMAL, 100, 0, False, 0, 0)
# Make a copy of the first layer as a new image
pdb.gimp_selection_all(image)
pdb.gimp_edit_copy(layer1)
image_1 = pdb.gimp_edit_paste_as_new_image()
display_1 = pdb.gimp_display_new(image_1)
# Now we apply Dodge effect on all the layers using the selection
# For some reason, image.height only dodges the top half of the image.
# Not sure if this is also true with image.width (left-half part) but multiplying
# by two just in case (the dodge brush should be centered on the x-axis i.e. half of width).
pdb.gimp_image_select_item(image, CHANNEL_OP_REPLACE, ch)
alist = []
for i in range(1, image.height*2):
alist += [image.width, i]
pdb.gimp_image_set_active_layer(image, layer1)
pdb.gimp_context_set_opacity(30)
pdb.gimp_dodgeburn(layer1, 50, DODGE_BURN_TYPE_DODGE, TRANSFER_MIDTONES, image.height*2, alist)
pdb.gimp_image_set_active_layer(image, layer2)
pdb.gimp_context_set_opacity(50)
pdb.gimp_dodgeburn(layer2, 50, DODGE_BURN_TYPE_DODGE, TRANSFER_MIDTONES, image.height*2, alist)
pdb.gimp_image_set_active_layer(image, layer3)
pdb.gimp_context_set_opacity(70)
pdb.gimp_dodgeburn(layer3, 50, DODGE_BURN_TYPE_DODGE, TRANSFER_MIDTONES, image.height*2, alist)
# Now make a copy of the third layer as a new image
pdb.gimp_selection_all(image) # Remove this to select and copy the button labels.
pdb.gimp_edit_copy(layer3)
image_3 = pdb.gimp_edit_paste_as_new_image()
display_3 = pdb.gimp_display_new(image_3)
pdb.gimp_selection_none(image)
# Hide the channels
if is_nonrectangular:
pdb.gimp_item_set_visible(ch_work, False)
pdb.gimp_item_set_visible(ch_round, False)
pdb.gimp_item_set_visible(ch, False)
pdb.gimp_item_set_visible(ch_rect, False)
pdb.gimp_item_set_visible(ch_border, False)
# Restore the state
pdb.gimp_context_set_sample_threshold_int(old_threshold)
pdb.gimp_context_set_sample_criterion(old_crit)
pdb.gimp_context_set_foreground(old_fgcolor)
pdb.gimp_context_set_opacity(old_opacity)
pdb.gimp_context_set_brush(old_brush)
pdb.gimp_context_set_brush_size(old_brush_size)
pdb.gimp_context_set_dynamics(old_brush_dynamics)
pdb.gimp_context_set_brush_force(old_brush_force)
pdb.gimp_image_undo_group_end(image)
register(
"button_render",
"Render a duotone button, making unpressed, pressed, and animated click images",
"Render a duotone button, making unpressed, pressed, and animated click images",
"Ali Sherief <ali@notatether.com>",
"Ali Sherief <ali@notatether.com>",
"2021",
"Button Render",
"RGB", # Adjust for other types when code is more flexible
[
(PF_INT, "threshold", "Select Color Threshold", 12),
(PF_INT, "x", "X Sample Coordinate For Background Detection", 1),
(PF_INT, "y", "Y Sample Coordinate For Background Detection", 1),
(PF_BOOL, "is_nonrectangular", "Non-rectangular button", False)
],
[],
button_render,
menu="<Image>")
#gimp.image_list() gets list of all open images
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment