1
0
Fork 0

Adding upstream version 3.0.4.

Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
This commit is contained in:
Daniel Baumann 2025-06-23 00:14:50 +02:00
parent 1c8b56a4f5
commit 554424e00a
Signed by: daniel.baumann
GPG key ID: BCC918A2ABD66424
6822 changed files with 5440542 additions and 0 deletions

View file

@ -0,0 +1,23 @@
This file contains a list of changes that can/must be done when
we break API/ABI for 3.x.
- Move GIMP_REPEAT_TRUNCATE to the start of the enum and rename it
to NONE. Rename the current NONE to EXTEND or something.
- Add LOTS of padding to all public class structs.
- Have private pointers in all public instance structs, not just
GET_PRIVATE() macros, in order to inspect the private structs
easily in the debugger.
- Remove compat values from all enums.
- Add user_data to all functions passed to gimp_widgets_init()
- Preferably make gimp_widgets_init() take a vtable with padding.
- Change gimp_prop_foo_new() to use the nick as label, or find some
other way to use the nick.
- Pass the plug-in protocol version on the plug-in command line.

View file

@ -0,0 +1,272 @@
# API changes in libgimp and the PDB for resources
This explains changes to the GIMP API from v2 to v3,
concerning resources.
The audience is plugin authors, and GIMP developers.
### Resources
A resource is a chunk of data that can be installed with GIMP
and is used by painting tools or for other rendering tasks.
Usually known as brush, font, palette, pattern, gradient and so forth.
### Resources are now first class objects
GimpResource is now a class in libgimp.
It has subclasses:
- Brush
- Font
- Gradient
- Palette
- Pattern
Formerly, the GIMP API had functions operating on resources by name.
Now, there are methods on resource objects.
Methods take an instance of the object as the first argument,
often called "self."
This means that where you formerly used a string name to refer to a resource object,
now you usually should pass an instance of an object.
### Changes to reference documents
#### libgimp API reference
Shows classes Brush, Font, and so forth.
The classes have instance methods taking the instance as the first argument.
Example:
```
gboolean gboolean gimp_brush_delete(gcharray) => gboolean gimp_brush_delete ( GimpBrush*)
```
The classes may also have class methods still taking string names.
Example:
```
gboolean gimp_brush_id_is_valid (const gchar* id)
```
Is a class method (in the "Functions" section of the class) taking the ID
(same as the name) to test whether such a brush is installed in Gimp core.
#### PDB Browser
Remember the PDB Browser shows the C API. You must mentally convert
to the API in bound languages.
Shows some procedures that now take type e.g. GimpBrush
where formerly they took type gcharray i.e. strings.
Shows some procedures that take a string name of a brush.
These are usually class methods.
#### Other changes to the API
Many of the Gimp functions dealing with the context
now take or return an instance of a resource.
Example:
```
gcharray* gimp_context_get_brush (void) => GimpBrush* gimp_context_get_brush (void)
```
A few functions have even more changed signature:
```
gint gimp_palette_get_info (gcharray) =>
gint gimp_palette_get_color_count (GimpPalette*)
```
The name and description of this function are changed
to accurately describe that the function only returns an integer
(formerly, the description said it also returned the name of the palette.)
### New resource objects
FUTURE
Formerly there were no methods in the libgimp API or the PDB for objects:
- Dynamics
- ColorProfile
- ToolPreset
These classes exist primarily so that plugins can let a user choose an instance,
and pass the instance on to other procedures.
### Traits
Informally, resources can have these traits:
- Nameable
- Creatable/Deleable
- Cloneable (Duplicatable)
- Editable
Some resource subclasses don't have all traits.
### ID's and names
The ID and name of a resource are currently the same.
(Some documents and method names may use either word, inconsistently.)
You usually use resource instances instead of their IDs.
This will insulate your code from changes to GIMP re ID versus name.
A plugin should not use a resource's ID.
A plugin should not show the ID/name to a user.
The GIMP app shows the names of resources as a convenience to users,
but usually shows resources visually, that is, iconically.
An ID is opaque, that is, used internally by GIMP.
FUTURE: the ID and name of a resource are distinct.
Different resource instances may have the same name.
Methods returning lists of resources or resource names may return
lists having duplicate names.
### Resource instances are references to underlying data
A resource instance is a proxy, or reference, to the underlying data.
Methods on the instance act on the underlying data.
The underlying data is in GIMP's store of resources.
It is possible for a resource instance to be "invalid"
that is, referring to underlying data that does not exist,
usually when a user uninstalls the thing.
### Creating Resources
Installing a resource is distinct from creating a resource.
GIMP lets you create some resources.
You can't create fonts in GIMP, you can only install them.
For those resources that GIMP lets you create,
the act of creating it also installs it.
For resources that you can create in GIMP:
- some you create using menu items
- some you can create using the API
The API does not let you create a raster brush.
The API does let you create a parametric brush.
For example, in Python:
```
brush = Gimp.Brush.new("Foo")
```
creates a new parametric brush.
Note that the passed name is a proposed name.
If the name is already in use,
the new brush will have a different name.
The brush instance will always be valid.
### Getting Resources by ID
Currently, you usually ask the user to interactively choose a resource.
If you must get a reference to a resource for which you know the ID,
you can new() the resource class and set it's ID property.
See below.
FUTURE Resource classes have get_by_id() methods.
If such a named resource is currently installed,
get_by_id() returns a valid instance of the resource class.
If such a named resource is not currently installed,
the method returns an error.
### Uninitialized or invalid resource instances
You can create an instance of a resource class that is invalid.
For example, in Python:
```
brush = Gimp.Brush()
brush.set_property("id", "Foo")
```
creates an instance that is invalid because there is no underlying data in the GIMP store
(assuming a brush named "Foo" is not installed.)
Ordinarily, you would not use such a construct.
Instead, you should use the new() method
(for resource classes where it is defined)
which creates, installs, and returns a valid instance except in dire circumstances (out of memory.)
### Invalid resource instances due to uninstalls
A plugin may have a resource as a parameter.
An interactive plugin may show a chooser widget to let a user choose a resource.
The user's choices may be saved in settings.
In the same session of GIMP, or in a subsequent session,
a user may invoke the plugin again.
Then the saved settings are displayed in the plugin's dialog
(when the second invocation is also interactive).
When, in the meantime (between invocations of the plugin)
a user has uninstalled the reference resource,
the resource, as a reference, is invalid.
A well-written plugin should handle this case.
Resource classes have:
- is_valid() instance method
- id_is_valid(char * name) class method
Well-written plugins should use these methods to ensure
that saved (deserialized) resource instances
are valid before subsequently using them.
### Naming and renaming
As mentioned above, currently names must be unique.
For some resources, the method rename(char * name) changes the name.
The method fails if the new name is already used.
When the instance is invalid to start with
(it has an ID that does not refer to any installed data)
renaming it can succeed and then it creates a valid instance.
### Duplicating
Duplicating a resource creates and installs the underlying data,
under a new, generated name.
The duplicate() method on an instance returns a new instance.
### Deleting
You can delete some resources. This uninstalls them.
You can delete some brushes, palettes, and gradients,
when they are writeable i.e. editable,
which usually means that a user previously created them.
You can't delete fonts and patterns.
You can delete using the delete() instance method
When you delete a resource, the instance (the proxy in a variable) continues to exist, but is invalid.
### Resource lists
Some functions in GIMP return lists of resource names,
representing the set of resources installed.
For example: gimp_brushes_get_list.
This returns a list of strings, the ID's of the resources.
The list will have no duplicates.
FUTURE: this will return a list of resource instances, and their names may have duplicates.

View file

@ -0,0 +1,15 @@
Here you'll find documentation useful for porting older GIMP
plug-ins, especially Python ones, to the GIMP 3.0 APIs.
Files:
- [classes.md:](classes.md)
A list of some of the important classes and modules in GIMP 3.0.
- [pdb-calls.md:](pdb-calls.md)
An incomplete list of old PDB functions and their equivalents,
using Python classes.
- [removed_functions.md:](removed_functions.md)
Functions that have been removed from GIMP, and their replacements.

View file

@ -0,0 +1,82 @@
# Useful Modules/Classes in GIMP 3.0+
Here's a guide to the modules you're likely to need.
It's a work in progress: feel free to add to it.
Online documentation for our libraries can be found at:
https://developer.gimp.org/api/3.0/
You can also get some information in GIMP's Python console with
*help(module)* or *help(object)*, and you can get a list of functions
with *dir(object)*.
## Gimp
The base module: almost everything is under Gimp.
## Gimp.Image
The image object.
Some operations that used to be PDB calls, like
```
pdb.gimp_selection_layer_alpha(layer)
```
are now in the Image object, e.g.
```
img.select_item(Gimp.ChannelOps.REPLACE, layer)
```
## Gimp.Layer
The layer object.
```
fog = Gimp.Layer.new(image, name,
drawable.width(), drawable.height(), type, opacity,
Gimp.LayerMode.NORMAL)
```
## Gimp.Selection
Selection operations that used to be in the PDB, e.g.
```
pdb.gimp_selection_none(img)
```
are now in the Gimp.Selection module, e.g.
```
Gimp.Selection.none(img)
```
## Gimp.ImageType
A home for image types like RGBA, GRAY, etc:
```
Gimp.ImageType.RGBA_IMAGE
```
## Gimp.FillType
e.g. Gimp.FillType.TRANSPARENT, Gimp.FillType.BACKGROUND
## Gimp.ChannelOps
The old channel op definitions in the gimpfu module, like
```
CHANNEL_OP_REPLACE
```
are now in their own module:
```
Gimp.ChannelOps.REPLACE
```
## Gegl.Color
In legacy plug-ins you could pass a simple list of integers, like (0, 0, 0).
In 3.0+, create a Gegl.Color object:
```
c = Gegl.Color.new("black")
c.set_rgba(0.94, 0.71, 0.27, 1.0)
```

View file

@ -0,0 +1,76 @@
# PDB equivalence
A table of old PDB calls, and their equivalents in the GIMP 3.0+ world.
This document is a work in progress. Feel free to add to it.
## Undo/Context
| Removed function | Replacement |
| -------------------------------- | ----------------------------
| gimp_undo_push_group_start | image.undo_group_start() |
| gimp_undo_push_group_end | image.undo_group_end() |
| gimp.context_push() | Gimp.context_push() |
| gimp.context_push() | Gimp.context_push() |
| gimp_context_get_background | Gimp.context_get_background
| gimp_context_set_background | Gimp.context_set_background
## File load/save
| Removed function | Replacement |
| -------------------------------- | ----------------------------
| gimp_file_load | Gimp.file_load |
| gimp_file_save | Gimp.file_save |
## Selection operations
Selection operations are now in the Gimp.Selection class (except
a few in the Image class). E.g.
| Removed function | Replacement |
| -------------------------------- | ----------------------------
| pdb.gimp_selection_invert(img) | Gimp.Selection.invert(img) |
| pdb.gimp_selection_none(img) | Gimp.Selection.none(img) |
| pdb.gimp_selection_layer_alpha(layer) | img.select_item(Gimp.ChannelOps.REPLACE, layer) |
| gimp_image_select_item | img.select_item(channel_op, layer) |
## Filling and Masks
| Removed function | Replacement |
| -------------------------------- | ----------------------------
| Gimp.drawable_fill() | layer.fill() |
| pdb.gimp_edit_fill(FILL_BACKGROUND) | layer.edit_fill(Gimp.FillType.BACKGROUND) |
| gimp_layer_add_mask | layer.add_mask
| gimp_layer_remove_mask | layer.remove_mask
## Miscellaneous and Non-PDB Calls
| Removed function | Replacement |
| -------------------------------- | ----------------------------
| gimp_displays_flush | Gimp.displays_flush
| gimp_image_insert_layer | image.insert_layer
## Plug-ins
Calling other plug-ins is trickier than before. The old
```
pdb.script_fu_drop_shadow(img, layer, -3, -3, blur,
(0, 0, 0), 80.0, False)
```
becomes
```
c = Gegl.Color.new("black")
c.set_rgba(0.94, 0.71, 0.27, 1.0)
Gimp.get_pdb().run_procedure('script-fu-drop-shadow',
[ Gimp.RunMode.NONINTERACTIVE,
GObject.Value(Gimp.Image, img),
GObject.Value(Gimp.Drawable, layer),
GObject.Value(GObject.TYPE_DOUBLE, -3),
GObject.Value(GObject.TYPE_DOUBLE, -3),
GObject.Value(GObject.TYPE_DOUBLE,blur),
c,
GObject.Value(GObject.TYPE_DOUBLE, 80.0),
GObject.Value(GObject.TYPE_BOOLEAN, False)
])
```

View file

@ -0,0 +1,226 @@
## Removed Functions
These functions have been removed from GIMP 3. Most of them were deprecated
since GIMP 2.10.x or older versions. As we bump the major version, it is time
to start with a clean slate.
Below is a correspondence table with replacement function. The replacement is
not necessarily a direct search-and-replace equivalent. Some may have different
parameters, and in some case, it may require to think a bit about how things
work to reproduce the same functionality. Nevertheless everything which was
possible in the previous API is obviously still possible.
| Removed function | Replacement |
| ----------------------------------------------- | ------------------------------------------------- |
| `gimp_attach_new_parasite()` | `gimp_attach_parasite()` |
| `gimp_brightness_contrast()` | `gimp_drawable_brightness_contrast()` |
| `gimp_brushes_get_brush()` | `gimp_context_get_brush()` |
| `gimp_brushes_get_brush_data()` | `gimp_brush_get_pixels()` |
| `gimp_brushes_get_spacing()` | `gimp_brush_get_spacing()` |
| `gimp_brushes_set_spacing()` | `gimp_brush_set_spacing()` |
| `gimp_by_color_select()` | `gimp_image_select_color()` |
| `gimp_by_color_select_full()` | `gimp_image_select_color()` |
| `gimp_channel_menu_new()` | `gimp_channel_combo_box_new()` |
| `gimp_checks_get_shades()` | `gimp_checks_get_colors()` |
| `gimp_color_balance()` | `gimp_drawable_color_color_balance()` |
| `gimp_color_display_convert()` | `gimp_color_display_convert_buffer()` |
| `gimp_color_display_convert_surface()` | `gimp_color_display_convert_buffer()` |
| `gimp_color_display_stack_convert()` | `gimp_color_display_stack_convert_buffer()` |
| `gimp_color_display_stack_convert_surface()` | `gimp_color_display_stack_convert_buffer()` |
| `gimp_color_profile_combo_box_add()` | `gimp_color_profile_combo_box_add_file()` |
| `gimp_color_profile_combo_box_get_active()` | `gimp_color_profile_combo_box_get_active_file()` |
| `gimp_color_profile_combo_box_set_active()` | `gimp_color_profile_combo_box_set_active_file()` |
| `gimp_color_profile_store_add()` | `gimp_color_profile_store_add_file()` |
| `gimp_colorize()` | `gimp_drawable_colorize_hsl()` |
| `gimp_context_get_transform_recursion()` | *N/A* |
| `gimp_context_set_transform_recursion()` | *N/A* |
| `gimp_curves_explicit()` | `gimp_drawable_curves_explicit()` |
| `gimp_curves_spline()` | `gimp_drawable_curves_spline()` |
| `gimp_desaturate()` | `gimp_drawable_desaturate()` |
| `gimp_desaturate_full()` | `gimp_drawable_desaturate()` |
| `gimp_drawable_attach_new_parasite()` | `gimp_item_attach_parasite()` |
| `gimp_drawable_bpp()` | `gimp_drawable_get_bpp()` |
| `gimp_drawable_delete()` | `gimp_item_delete()` |
| `gimp_drawable_get_image()` | `gimp_item_get_image()` |
| `gimp_drawable_get_linked()` | *N/A* |
| `gimp_drawable_get_name()` | `gimp_item_get_name()` |
| `gimp_drawable_get_tattoo()` | `gimp_item_get_tattoo()` |
| `gimp_drawable_get_visible()` | `gimp_item_get_visible()` |
| `gimp_drawable_height()` | `gimp_drawable_get_height()` |
| `gimp_drawable_is_channel()` | `gimp_item_is_channel()` |
| `gimp_drawable_is_layer()` | `gimp_item_is_layer()` |
| `gimp_drawable_is_layer_mask()` | `gimp_item_is_layer_mask()` |
| `gimp_drawable_is_text_layer()` | `gimp_item_is_text_layer()` |
| `gimp_drawable_is_valid()` | `gimp_item_is_valid()` |
| `gimp_drawable_menu_new()` | `gimp_drawable_combo_box_new()` |
| `gimp_drawable_offsets()` | `gimp_drawable_get_offsets()` |
| `gimp_drawable_parasite_attach()` | `gimp_item_attach_parasite()` |
| `gimp_drawable_parasite_detach()` | `gimp_item_detach_parasite()` |
| `gimp_drawable_parasite_find()` | `gimp_item_get_parasite()` |
| `gimp_drawable_parasite_list()` | `gimp_item_get_parasite_list()` |
| `gimp_drawable_preview_new()` | `gimp_drawable_preview_new_from_drawable()` |
| `gimp_drawable_preview_new_from_drawable_id()` | `gimp_drawable_preview_new_from_drawable()` |
| `gimp_drawable_set_image()` | *N/A* |
| `gimp_drawable_set_linked()` | *N/A* |
| `gimp_drawable_set_name()` | `gimp_item_set_name()` |
| `gimp_drawable_set_tattoo()` | `gimp_item_set_tattoo()` |
| `gimp_drawable_set_visible()` | `gimp_item_set_visible()` |
| `gimp_drawable_transform_2d()` | `gimp_item_transform_2d()` |
| `gimp_drawable_transform_2d_default()` | `gimp_item_transform_2d()` |
| `gimp_drawable_transform_flip()` | `gimp_item_transform_flip()` |
| `gimp_drawable_transform_flip_default()` | `gimp_item_transform_flip()` |
| `gimp_drawable_transform_flip_simple()` | `gimp_item_transform_flip_simple()` |
| `gimp_drawable_transform_matrix()` | `gimp_item_transform_matrix()` |
| `gimp_drawable_transform_matrix_default()` | `gimp_item_transform_matrix()` |
| `gimp_drawable_transform_perspective()` | `gimp_item_transform_perspective()` |
| `gimp_drawable_transform_perspective_default()` | `gimp_item_transform_perspective()` |
| `gimp_drawable_transform_rotate()` | `gimp_item_transform_rotate()` |
| `gimp_drawable_transform_rotate_default()` | `gimp_item_transform_rotate()` |
| `gimp_drawable_transform_rotate_simple()` | `gimp_item_transform_rotate_simple()` |
| `gimp_drawable_transform_scale()` | `gimp_item_transform_scale()` |
| `gimp_drawable_transform_scale_default()` | `gimp_item_transform_scale()` |
| `gimp_drawable_transform_shear()` | `gimp_item_transform_shear()` |
| `gimp_drawable_transform_shear_default()` | `gimp_item_transform_shear()` |
| `gimp_drawable_width()` | `gimp_drawable_get_width()` |
| `gimp_edit_blend()` | `gimp_drawable_edit_gradient_fill()` |
| `gimp_edit_bucket_fill()` | `gimp_drawable_edit_bucket_fill()` |
| `gimp_edit_bucket_fill_full()` | `gimp_drawable_edit_bucket_fill()` |
| `gimp_edit_clear()` | `gimp_drawable_edit_clear()` |
| `gimp_edit_fill()` | `gimp_drawable_edit_fill()` |
| `gimp_edit_paste_as_new()` | `gimp_edit_paste_as_new_image()` |
| `gimp_edit_named_paste_as_new()` | `gimp_edit_named_paste_as_new_image()` |
| `gimp_edit_stroke()` | `gimp_drawable_edit_stroke_selection()` |
| `gimp_edit_stroke_vectors()` | `gimp_drawable_edit_stroke_item()` |
| `gimp_ellipse_select()` | `gimp_image_select_ellipse()` |
| `gimp_enum_combo_box_set_stock_prefix()` | `gimp_enum_combo_box_set_icon_prefix()` |
| `gimp_enum_stock_box_new()` | `gimp_enum_icon_box_new()` |
| `gimp_enum_stock_box_new_with_range()` | `gimp_enum_icon_box_new_with_range()` |
| `gimp_enum_stock_box_set_child_padding()` | `gimp_enum_icon_box_set_child_padding()` |
| `gimp_enum_store_set_stock_prefix()` | `gimp_enum_store_set_icon_prefix()` |
| `gimp_equalize()` | `gimp_drawable_equalize()` |
| `gimp_flip()` | `gimp_item_transform_flip_simple()` |
| `gimp_floating_sel_relax()` | *N/A* |
| `gimp_floating_sel_rigor()` | *N/A* |
| `gimp_free_select()` | `gimp_image_select_polygon()` |
| `gimp_fuzzy_select()` | `gimp_image_select_contiguous_color()` |
| `gimp_fuzzy_select_full()` | `gimp_image_select_contiguous_color()` |
| `gimp_gamma()` | `gimp_drawable_get_format()` |
| `gimp_get_icon_theme_dir()` | *N/A* |
| `gimp_get_path_by_tattoo()` | `gimp_image_get_path_by_tattoo()` |
| `gimp_get_theme_dir()` | *N/A* |
| `gimp_gradients_get_gradient_data()` | `gimp_gradient_get_uniform_samples()` |
| `gimp_gradients_sample_custom()` | `gimp_gradient_get_custom_samples()` |
| `gimp_gradients_sample_uniform()` | `gimp_gradient_get_uniform_samples()` |
| `gimp_histogram()` | `gimp_drawable_histogram()` |
| `gimp_hue_saturation()` | `gimp_drawable_hue_saturation()` |
| `gimp_image_add_channel()` | `gimp_image_insert_channel()` |
| `gimp_image_add_layer()` | `gimp_image_insert_layer()` |
| `gimp_image_add_vectors()` | `gimp_image_insert_path()` |
| `gimp_image_attach_new_parasite()` | `gimp_image_attach_parasite()` |
| `gimp_image_base_type()` | `gimp_image_get_base_type()` |
| `gimp_image_free_shadow()` | `gimp_drawable_free_shadow()` |
| `gimp_image_get_channel_position()` | `gimp_image_get_item_position()` |
| `gimp_image_get_cmap()` | `gimp_image_get_colormap()` |
| `gimp_image_get_layer_position()` | `gimp_image_get_item_position()` |
| `gimp_image_get_vectors_position()` | `gimp_image_get_item_position()` |
| `gimp_image_height()` | `gimp_image_get_height()` |
| `gimp_image_lower_channel()` | `gimp_image_lower_item()` |
| `gimp_image_lower_layer()` | `gimp_image_lower_item()` |
| `gimp_image_lower_layer_to_bottom()` | `gimp_image_lower_item_to_bottom()` |
| `gimp_image_lower_vectors()` | `gimp_image_lower_item()` |
| `gimp_image_lower_vectors_to_bottom()` | `gimp_image_lower_item_to_bottom()` |
| `gimp_image_menu_new()` | `gimp_image_combo_box_new()` |
| `gimp_image_parasite_attach()` | `gimp_image_attach_parasite()` |
| `gimp_image_parasite_detach()` | `gimp_image_detach_parasite()` |
| `gimp_image_parasite_find()` | `gimp_image_get_parasite()` |
| `gimp_image_parasite_list()` | `gimp_image_get_parasite_list()` |
| `gimp_image_raise_channel()` | `gimp_image_raise_item()` |
| `gimp_image_raise_layer()` | `gimp_image_raise_item()` |
| `gimp_image_raise_layer_to_top()` | `gimp_image_raise_item_to_top()` |
| `gimp_image_raise_vectors()` | `gimp_image_raise_item()` |
| `gimp_image_raise_vectors_to_top()` | `gimp_image_raise_item_to_top()` |
| `gimp_image_scale_full()` | `gimp_image_scale()` |
| `gimp_image_set_cmap()` | `gimp_image_set_colormap()` |
| `gimp_image_width()` | `gimp_image_get_width()` |
| `gimp_install_cmap()` | *N/A* |
| `gimp_invert()` | `gimp_drawable_invert()` |
| `gimp_item_get_linked()` | *N/A* |
| `gimp_item_set_linked()` | *N/A* |
| `gimp_layer_menu_new()` | `gimp_layer_combo_box_new()` |
| `gimp_layer_scale_full()` | `gimp_layer_scale()` |
| `gimp_layer_translate()` | `gimp_item_transform_translate()` |
| `gimp_levels()` | `gimp_drawable_levels()` |
| `gimp_levels_auto()` | `gimp_drawable_levels_stretch()` |
| `gimp_levels_stretch()` | `gimp_drawable_levels_stretch()` |
| `gimp_min_colors()` | *N/A* |
| `gimp_palettes_get_palette()` | `gimp_context_get_palette()` |
| `gimp_palettes_get_palette_entry()` | `gimp_palette_entry_get_color()` |
| `gimp_parasite_attach()` | `gimp_attach_parasite()` |
| `gimp_parasite_data()` | `gimp_parasite_get_data()` |
| `gimp_parasite_data_size()` | `gimp_parasite_get_data()` |
| `gimp_parasite_detach()` | `gimp_detach_parasite()` |
| `gimp_parasite_find()` | `gimp_get_parasite()` |
| `gimp_parasite_flags()` | `gimp_parasite_get_flags()` |
| `gimp_parasite_list()` | `gimp_get_parasite_list()` |
| `gimp_parasite_name()` | `gimp_parasite_get_name()` |
| `gimp_path_delete()` | `gimp_image_remove_path()` |
| `gimp_path_get_current()` | `gimp_image_get_selected_paths()` |
| `gimp_path_get_locked()` | *N/A* |
| `gimp_path_get_points()` | `gimp_path_stroke_get_points()` |
| `gimp_path_get_point_at_dist()` | `gimp_path_stroke_get_point_at_dist()` |
| `gimp_path_get_tattoo()` | `gimp_item_get_tattoo()` |
| `gimp_path_import()` | `gimp_image_import_paths_from_file()` |
| `gimp_path_list()` | `gimp_image_get_paths()` |
| `gimp_path_set_current()` | `gimp_image_set_selected_paths()` |
| `gimp_path_set_locked()` | *N/A* |
| `gimp_path_set_points()` | `gimp_path_stroke_new_from_points()` |
| `gimp_path_set_tattoo()` | `gimp_item_set_tattoo()` |
| `gimp_path_stroke_current()` | `gimp_edit_stroke_vectors()` |
| `gimp_path_to_selection()` | `gimp_image_select_item()` |
| `gimp_patterns_get_pattern()` | `gimp_context_get_pattern()` |
| `gimp_patterns_get_pattern_data()` | `gimp_pattern_get_pixels()` |
| `gimp_perspective()` | `gimp_item_transform_perspective()` |
| `gimp_posterize()` | `gimp_drawable_posterize()` |
| `gimp_prop_enum_stock_box_new()` | `gimp_prop_enum_icon_box_new()` |
| `gimp_prop_stock_image_new()` | `gimp_prop_icon_image_new()` |
| `gimp_prop_unit_menu_new()` | `gimp_prop_unit_combo_box_new()` |
| `gimp_rect_select()` | `gimp_image_select_rectangle()` |
| `gimp_rotate()` | `gimp_item_transform_rotate()` |
| `gimp_round_rect_select()` | `gimp_image_select_round_rectangle()` |
| `gimp_scale()` | `gimp_item_transform_scale()` |
| `gimp_selection_combine()` | `gimp_image_select_item()` |
| `gimp_selection_layer_alpha()` | `gimp_image_select_item()` |
| `gimp_selection_load()` | `gimp_image_select_item()` |
| `gimp_shear()` | `gimp_item_transform_shear()` |
| `gimp_stock_init()` | `gimp_icons_init()` |
| `gimp_text()` | `gimp_text_fontname()` |
| `gimp_text_get_extents()` | `gimp_text_get_extents_fontname()` |
| `gimp_text_layer_get_hinting()` | `gimp_text_layer_get_hint_style()` |
| `gimp_text_layer_set_hinting()` | `gimp_text_layer_set_hint_style()` |
| `gimp_threshold()` | `gimp_drawable_threshold()` |
| `gimp_toggle_button_sensitive_update()` | `g_object_bind_property()` |
| `gimp_transform_2d()` | `gimp_item_transform_2d()` |
| `gimp_unit_menu_update()` | `#GimpUnitComboBox` |
| `gimp_vectors_export_to_file()` | `gimp_image_export_path_to_file()` |
| `gimp_vectors_export_to_string()` | `gimp_image_export_path_to_string()` |
| `gimp_vectors_get_image()` | `gimp_item_get_image()` |
| `gimp_vectors_get_linked()` | *N/A* |
| `gimp_vectors_get_name()` | `gimp_item_get_name()` |
| `gimp_vectors_get_tattoo()` | `gimp_item_get_tattoo()` |
| `gimp_vectors_get_visible()` | `gimp_item_get_visible()` |
| `gimp_vectors_import_from_file()` | `gimp_image_import_paths_from_file()` |
| `gimp_vectors_import_from_string()` | `gimp_image_import_paths_from_string()` |
| `gimp_vectors_is_valid()` | `gimp_item_is_valid()` |
| `gimp_vectors_parasite_attach()` | `gimp_item_attach_parasite()` |
| `gimp_vectors_parasite_detach()` | `gimp_item_detach_parasite()` |
| `gimp_vectors_parasite_find()` | `gimp_item_get_parasite()` |
| `gimp_vectors_parasite_list()` | `gimp_item_get_parasite_list()` |
| `gimp_vectors_set_linked()` | *N/A* |
| `gimp_vectors_set_name()` | `gimp_item_set_name()` |
| `gimp_vectors_set_tattoo()` | `gimp_item_set_tattoo()` |
| `gimp_vectors_set_visible()` | `gimp_item_set_visible()` |
| `gimp_vectors_to_selection()` | `gimp_image_select_item()` |
| `gimp_zoom_preview_get_drawable_id()` | `gimp_zoom_preview_get_drawable()` |
| `gimp_zoom_preview_new()` | `gimp_zoom_preview_new_from_drawable()` |
| `gimp_zoom_preview_new_from_drawable_id()` | `gimp_zoom_preview_new_from_drawable()` |
| `gimp_zoom_preview_new_with_model()` | `gimp_zoom_preview_new_with_model_from_drawable()`|

342
devel-docs/README.md Normal file
View file

@ -0,0 +1,342 @@
---
title: Developers documentation
---
This manual holds information that you will find useful if you
develop a GIMP plug-in or want to contribute to the GIMP core.
People only interested into plug-ins can probably read just the
[Plug-in development](#plug-in-development) section. If you wish to
contribute to all parts of GIMP, the whole documentation is of interest.
[TOC]
## Plug-in and Filters development
All needed information for Plug-in and Filters development is documented on the
[Resource Development](https://developer.gimp.org/resource/)
section of GIMP Developer website, with exception of the following:
### Porting from GIMP 2 plug-ins
Take a look at our [porting guide](GIMP3-plug-in-porting-guide/README.md).
## Custom data
This section list all types of data usable to enhance GIMP
functionalities. If you are interested to contribute default data to
GIMP, be aware that we are looking for a very good base set, not an
unfinite number of data for all possible usage (even the less common
ones).
Furthermore we only accept data on Libre licenses:
* [Free Art License](https://artlibre.org/licence/lal/en/)
* [CC0](https://creativecommons.org/publicdomain/zero/1.0/)
* [CC BY](https://creativecommons.org/licenses/by/4.0/)
* [CC BY-SA](https://creativecommons.org/licenses/by-sa/4.0/)
Of course you are free to share data usable by GIMP on any license you
want on your own. Providing them as third-party GIMP
[extensions](#gimp-extensions-gex) is probably the best idea.
### Brushes
GIMP currently supports the following brush formats:
* GIMP Brush (GBR): format to store pixmap brushes
* GIMP Brush Pipe (GIH): format to store a series of pixmap brushes
* GIMP Generated Brush (VBR): format of "generated" brushes
* GIMP Brush Pixmap (GPB): *OBSOLETE* format to store pixel brushes
* MyPaint brushes v1 (MYB)
* Photoshop ABR Brush
* Paint Shop Pro JBR Brush
We do fully support the GIMP formats obviously, as well as MyPaint
brushes, since we use the official `libmypaint` library. We are not sure
how well we support other third-party formats, especially if they had
recent versions.
If you are interested in brushes from a developer perspective, you are
welcome to read specifications of GIMP formats:
[GBR](https://developer.gimp.org/core/standards/gbr/), [GIH](https://developer.gimp.org/core/standards/gih/),
[VBR](https://developer.gimp.org/core/standards/vbr/) or the obsolete [GPB](https://developer.gimp.org/core/standards/gpb/).
If you want to contribute brushes to the official GIMP, be aware we
would only accept brushes in non-obsolete GIMP formats. All these
formats can be generated by GIMP itself from images.
If you want to contribute MyPaint brushes, we recommend to propose them
to the [MyPaint-brushes](https://github.com/mypaint/mypaint-brushes/)
data project, which is also used by GIMP for its default MyPaint brush
set.
Otherwise, you are welcome to provide brush set in any format as
third-party [extensions](#gimp-extensions-gex).
### Dynamics
GIMP supports the GIMP Paint Dynamics format which can be generated from
within GIMP.
### Patterns
GIMP supports the GIMP Pattern format (PAT, whose
[specification](https://developer.gimp.org/core/standards/pat/) is available for developers).
This format can be exported by GIMP itself.
Alternatively GIMP supports patterns from `GdkPixbuf` (**TODO**: get more
information?).
### Palettes
GIMP supports the GIMP Palette format which can be generated from within
GIMP.
### Gradients
GIMP supports the GIMP Gradient format (GGR, whose
[specification](https://developer.gimp.org/core/standards/ggr/) is available for developers)
which can be generated from within GIMP.
Alternatively GIMP supports the SVG Gradient format.
### Themes
GTK3 uses CSS themes. Don't be fooled though. It's not real CSS in that
it doesn't have all the features of real web CSS, and since it's for
desktop applications, some things are necessarily different. What it
means is mostly that it "looks similar" enough that people used to web
styling should not be too disorientated.
You can start by looking at the [official
documentation](https://docs.gtk.org/gtk3/migrating-themes.html) for
theme migration (from GTK+2 to 3), which gives a good overview, though
it's far from being perfect unfortunately.
Another good idea would be to look at existing well maintained GTK3
themes to get inspiration and see how things work.
Finally you can look at our existing themes, like the [System
theme](https://gitlab.gnome.org/GNOME/gimp/-/blob/master/themes/System/gimp.css).
Note though that this `System` theme is pretty bare, and that's its goal
(try to theme as few as possible over whatever is the current real
GTK system theme).
As a last trick for theme makers, we recommend to work with the
GtkInspector tool, which allows you to test CSS rules live in the `CSS`
tab. You can run the `GtkInspector` by going to the `File > Debug` menu
and selecting `Start GtkInspector` menu item.
It also allows you to find the name of a widget to use in your CSS
rules. To do so:
* Start the `GtkInspector`;
* go on the "Objects" tab;
* click the "target" 🞋 icon on the headerbar's top-left, then pick in
GIMP interface the widget you are interested to style;
* the widget name will be displayed on the top of the information area
of the dialog.
* Feel free to browse the various sections to see the class hierarchy,
CSS nodes and so on.
* The second top-left button (just next to the target icon) allows you
to switch between the details of the selected widget and the widget
hierarchy (container widgets containing other widgets), which is also
very useful information.
Additionally you can quickly switch between the light and dark variant
of a same theme by going to "Visual" tab and switching the "Dark
Variant" button ON or OFF.
### Icon themes
Icon sets (a.k.a. "icon themes") have been separated from themes since
GIMP 2.10 so you can have any icon theme with any theme.
To know about icons, go to [gimp-data/icons](https://gitlab.gnome.org/GNOME/gimp-data/-/blob/main/icons/README.md?ref_type=heads).
### TODO: Tool presets
## TODO: GIMP extensions (*.gex*)
## Core development
When writing code, any core developer is expected to follow:
- GIMP's [coding style](https://developer.gimp.org/core/coding_style/);
- the [directory structure](#directory-structure-of-gimp-source-tree)
- our [header file inclusion policy](includes.txt)
[GIMP's developer site](https://developer.gimp.org/) contain various valuable resources.
Finally the [debugging-tips](https://developer.gimp.org/core/debug/debugging-tips/) file contain many very
useful tricks to help you debugging in various common cases.
### Newcomers
If this is your first time contributing to GIMP, you might be interested
by [build instructions](https://developer.gimp.org/core/setup/).
You might also like to read these instructions on the process of
[submitting patches](https://developer.gimp.org/core/submit-patch/).
### TODO: Core Contributors
As a core dev, you can trigger .appimage, .flatpak standalone packages,
.exe Windows installer or Microsoft Store/.msixbundle to be generated
with the MR code as explained in [gitlab-mr.md](gitlab-mr.md).
### Directory structure of GIMP source tree
GIMP source tree can be divided into the main application, libraries, plug-ins,
data files and some stuff that don't fit into these categories. Here are the
top-level directories:
| Folder | Description |
| --- | --- |
| app/ | Source code of the main GIMP application |
| app-tools/ | Source code of distributed tools |
| build/ | Scripts for creating binary packages |
| data/ | Data files: dynamics, gradients, palettes… |
| desktop/ | Desktop integration files |
| devel-docs/ | Developers documentation |
| docs/ | Users documentation |
| etc/ | Configuration files installed with GIMP |
| extensions/ | Source code of extensions |
| gimp-data/ | Raster or image data files |
| libgimp/ | Library for plug-ins (core does not link against) |
| libgimpbase/ | Basic functions shared by core and plug-ins |
| libgimpcolor/ | Color-related functions shared by core and plug-ins |
| libgimpconfig/ | Config functions shared by core and plug-ins |
| libgimpmath/ | Mathematic operations useful for core and plug-ins |
| libgimpmodule/ | Abstracts dynamic loading of modules (used to implement loadable color selectors and display filters) |
| libgimpthumb/ | Thumbnail functions shared by core and plug-ins |
| libgimpwidgets/ | User interface elements (widgets) and utility functions shared by core and plug-ins |
| menus/ | XML/XSL files used to generate menus |
| modules/ | Color selectors and display filters loadable at run-time |
| pdb/ | Scripts for PDB source code generation |
| plug-ins/ | Source code for plug-ins distributed with GIMP |
| po/ | Translations of strings used in the core application |
| po-libgimp/ | Translations of strings used in libgimp |
| po-plug-ins/ | Translations of strings used in C plug-ins |
| po-python/ | Translations of strings used in Python plug-ins |
| po-script-fu/ | Translations of strings used in Script-Fu scripts |
| po-tags/ | Translations of strings used in tags |
| po-tips/ | Translations of strings used in tips |
| po-windows-installer/ | Translations of strings used in the Windows installer |
| themes/ | Official themes |
| tools/ | Source code for non-distributed GIMP-related tools |
| .gitlab/ | Gitlab-related templates or scripts |
The source code of the main GIMP application is found in the `app/` directory:
| Folder | Description |
| --- | --- |
| app/actions/ | Code of actions (`GimpAction*` defined in `app/widgets/`) (depends: GTK) |
| app/config/ | Config files handling: GimpConfig interface and GimpRc object (depends: GObject) |
| app/core/ | Core of GIMP **core** (depends: GObject) |
| app/dialogs/ | Dialog widgets (depends: GTK) |
| app/display/ | Handles displays (e.g. image windows) (depends: GTK) |
| app/file/ | File handling routines in **core** (depends: GIO) |
| app/file-data/ | GIMP file formats (gbr, gex, gih, pat) support (depends: GIO) |
| app/gegl/ | Wrapper code for babl and GEGL API (depends: babl, GEGL) |
| app/gui/ | Code that puts the user interface together (depends: GTK) |
| app/menus/ | Code for menus (depends: GTK) |
| app/operations/ | Custom GEGL operations (depends: GEGL) |
| app/paint/ | Paint core that provides different ways to paint strokes (depends: GEGL) |
| app/pdb/ | Core side of the Procedural Database, exposes internal functionality |
| app/plug-in/ | Plug-in handling in **core** |
| app/propgui/ | Property widgets generated from config properties (depends: GTK) |
| app/tests/ | Core unit testing framework |
| app/text/ | Text handling in **core** |
| app/tools/ | User interface part of the tools. Actual tool functionality is in core |
| app/vectors/ | Vectors framework in **core** |
| app/widgets/ | Collection of widgets used in the application GUI |
| app/xcf/ | XCF file handling in **core** |
#### Auto-generated Files
Please notice that some files in the source are generated from other
sources. All those files have a short notice about being generated
somewhere at the top. Among them are the files ending in `pdb.[ch]` in
the `libgimp/` directory and the files ending in `cmds.c` in the
`app/pdb/` subdirectory. Those are generated from the respective `.pdb`
files in `pdb/groups`.
Other files are:
* `AUTHORS` from `authors.xml`
You should also check out [gimp-module-dependencies.svg](gimp-module-dependencies.svg).
**TODO**: this SVG file is interesting yet very outdated. It should not
be considered as some kind dependency rule and should be updated.
### Advanced concepts
#### XCF
The `XCF` format is the core image format of GIMP, which mirrors
features made available in GIMP. More than an image format, you may
consider it as a work or project format, as it is not made for finale
presentation of an artwork but for the work-in-progress process.
Developers are welcome to read the [specifications of XCF](https://developer.gimp.org/core/standards/xcf/).
#### Locks
Items in an image can be locked in various ways to prevent different
types of edits.
This is further explained in [the specifications of locks](https://developer.gimp.org/core/specifications/locks/).
#### UI Framework
GIMP has an evolved GUI framework, with a toolbox, dockables, menus…
This document describing how the GIMP UI framework functions and how it
is [implemented](ui-framework.txt) might be of interest.
#### Contexts
GIMP uses a lot a concept of "contexts". We recommend reading more about
[how GimpContexts are used in GIMP](contexts.txt).
#### Undo
GIMP undo system can be challenging at times. This [quick overview of
the undo system](undo.txt) can be of interest as a first introduction.
#### Parasites
GIMP has a concept of "parasite" data which basically correspond to
persistent or semi-persistent data which can be attached to images or
items (layers, channels, paths) within an image. These parasites are
saved in the XCF format.
Parasites can also be attached globally to the GIMP session.
Parasite contents is format-free and you can use any parasite name,
nevertheless GIMP itself uses parasite so you should read the
[descriptions of known parasites](parasites.txt).
#### Metadata
GIMP supports Exif, IPTC and XMP metadata as well as various image
format-specific metadata. The topic is quite huge and complex, if not
overwhelming.
This [old document](https://developer.gimp.org/core/specifications/exif_handling/)
might be of interest (or maybe not, it has not been recently reviewed and might
be widely outdated; in any case, it is not a complete document at all as we
definitely do a lot more nowadays). **TODO**: review this document and delete or
update it depending of whether it still makes sense.
#### Tagging
Various data in GIMP can be tagged across sessions.
This document on [how resource tagging in GIMP works](tagging.txt) may
be of interest.

28
devel-docs/c.vim Normal file
View file

@ -0,0 +1,28 @@
" GIMP coding style for vim "
" To enable these vim rules for GIMP only, add this command to your vimrc:
" autocmd BufNewFile,BufRead /path/to/gimp/*.[ch] source /path/to/gimp/devel-docs/c.vim
"
" Do not use `set exrc` which is a security risk for your system since vim may
" be tricked into running shell commands by .vimrc files hidden in malicious
" projects (`set secure` won't protect you since it is not taken into account
" if the files are owned by you).
" GNU style
setlocal cindent
setlocal cinoptions=>4,n-2,{2,^-2,:2,=2,g0,h2,p5,t0,+2,(0,u0,w1,m1
setlocal shiftwidth=2
setlocal softtabstop=2
setlocal textwidth=79
setlocal fo-=ro fo+=cql
" Tabs are always inserted as spaces.
set expandtab
" But if there are tabs already, show them as 8 columns.
setlocal tabstop=8
" Highlight in red trailing whitespaces and tabs everywhere.
highlight TrailingWhitespace ctermbg=LightRed guibg=LightRed
match TrailingWhitespace /\s\+$/
highlight ForbiddenTabs ctermbg=DarkRed guibg=DarkRed
2match ForbiddenTabs /\t/

89
devel-docs/contexts.txt Normal file
View file

@ -0,0 +1,89 @@
contexts.txt
============
Introduction
------------
This file describes how GimpContexts are used in GIMP.
Overview
--------
One important context is the so called "user context",
gimp_get_user_context(). This context keeps track on what image the
user currently has active, for example. Dock windows have their own
context which does not necessarily mirror the user context. A dock
window can be set to show information for a specific image. Plug-ins
also have their own context.
Communication between contexts
------------------------------
So how do the various contexts synchronize and propagate changes?
This is most easily explained by a sequence diagram. Let's say there
are two image windows with different images opened in GIMP. Call them
A and B. Let's say A is currently active. When the user activates B,
this is the sequence of events from the focus event to the layers
dockable have been updated with the new image. To understand the
diagram, you have to know that the dock window has connected signal
handlers to image changes in the user context (through a dialog
factory getter), and the layer dockable have connected a signal
handler to image changes in the dock window context. The sequence of
events is as follows:
GimpContext GimpContext GimpItemTreeView,
GimpDisplayShell user GimpDockWindow dock window GimpLayerTreeView
| | | | |
focus event | | | |
------->| | | | |
| gimp_context_set_display() | | |
|--------------->|----------+ | | |
| | | | | |
| gimp_context_set_image() | | | |
| |<---------+ | | |
| | | | |
| | "image-changed" | |
| |------------->| | |
| | | gimp_context_set_image() |
| | |------------->| |
| | | | "image-changed" /
| | | | set_image()
| | | |------------>|
| | | | |
In single-window mode, the dockables listen directly to the user
context. When switching between single-window and multi-window modes,
the dockables are updated with their new context, just as when moving
a dockable between different dock windows and thus also different
contexts. The sequence diagram for single-window mode is:
GimpContext GimpItemTreeView
GimpDisplayShell user GimpLayerTreeView
| | |
focus event | |
------->| | |
| gimp_context_set_display() |
|--------------->|----------+ |
| | | |
| gimp_context_set_image() | |
| |<---------+ |
| | |
| | "image-changed" /
| | set_image()
| |------------->|
| | |
| | |
| | |
| | |
| | |
| | |
Parent/child relationships
--------------------------
TODO

View file

@ -0,0 +1,393 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generated by graphviz version 2.26.3 (20100126.1600)
-->
<!-- Title: _anonymous_0 Pages: 1 -->
<svg width="862pt" height="1008pt"
viewBox="0.00 0.00 862.00 1008.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph1" class="graph" transform="scale(1 1) rotate(0) translate(4 1004)">
<title>_anonymous_0</title>
<polygon fill="white" stroke="white" points="-4,5 -4,-1004 859,-1004 859,5 -4,5"/>
<!-- app/plug&#45;in -->
<g id="node1" class="node"><title>app/plug&#45;in</title>
<ellipse fill="lawngreen" stroke="black" cx="128" cy="-981" rx="55.8614" ry="19.0919"/>
<text text-anchor="middle" x="128" y="-977.9" font-family="Times Roman,serif" font-size="14.00">app/plug&#45;in</text>
</g>
<!-- app/composite -->
<g id="node2" class="node"><title>app/composite</title>
<ellipse fill="lawngreen" stroke="black" cx="176" cy="-907" rx="67.8823" ry="19.0919"/>
<text text-anchor="middle" x="176" y="-903.9" font-family="Times Roman,serif" font-size="14.00">app/composite</text>
</g>
<!-- app/plug&#45;in&#45;&gt;app/composite -->
<g id="edge2" class="edge"><title>app/plug&#45;in&#45;&gt;app/composite</title>
<path fill="none" stroke="black" d="M140.112,-962.327C145.585,-953.891 152.16,-943.754 158.178,-934.475"/>
<polygon fill="black" stroke="black" points="161.219,-936.219 163.724,-925.925 155.346,-932.41 161.219,-936.219"/>
</g>
<!-- app/base -->
<g id="node4" class="node"><title>app/base</title>
<ellipse fill="lawngreen" stroke="black" cx="285" cy="-833" rx="44.7575" ry="19.0919"/>
<text text-anchor="middle" x="285" y="-829.9" font-family="Times Roman,serif" font-size="14.00">app/base</text>
</g>
<!-- app/composite&#45;&gt;app/base -->
<g id="edge4" class="edge"><title>app/composite&#45;&gt;app/base</title>
<path fill="none" stroke="black" d="M202.108,-889.275C217.2,-879.029 236.322,-866.047 252.396,-855.135"/>
<polygon fill="black" stroke="black" points="254.66,-857.828 260.968,-849.315 250.728,-852.037 254.66,-857.828"/>
</g>
<!-- app/config -->
<g id="node11" class="node"><title>app/config</title>
<ellipse fill="lawngreen" stroke="black" cx="311" cy="-759" rx="51.8276" ry="19.0919"/>
<text text-anchor="middle" x="311" y="-755.9" font-family="Times Roman,serif" font-size="14.00">app/config</text>
</g>
<!-- app/base&#45;&gt;app/config -->
<g id="edge10" class="edge"><title>app/base&#45;&gt;app/config</title>
<path fill="none" stroke="black" d="M291.696,-813.943C294.516,-805.916 297.859,-796.402 300.964,-787.565"/>
<polygon fill="black" stroke="black" points="304.314,-788.587 304.327,-777.992 297.71,-786.267 304.314,-788.587"/>
</g>
<!-- libgimpcolor -->
<g id="node6" class="node"><title>libgimpcolor</title>
<ellipse fill="#ff7256" stroke="black" cx="418" cy="-167" rx="61.0181" ry="19.0919"/>
<text text-anchor="middle" x="418" y="-163.9" font-family="Times Roman,serif" font-size="14.00">libgimpcolor</text>
</g>
<!-- libgimpmath -->
<g id="node7" class="node"><title>libgimpmath</title>
<ellipse fill="#ff7256" stroke="black" cx="418" cy="-93" rx="61.0181" ry="19.0919"/>
<text text-anchor="middle" x="418" y="-89.9" font-family="Times Roman,serif" font-size="14.00">libgimpmath</text>
</g>
<!-- libgimpcolor&#45;&gt;libgimpmath -->
<g id="edge6" class="edge"><title>libgimpcolor&#45;&gt;libgimpmath</title>
<path fill="none" stroke="black" d="M418,-147.943C418,-140.149 418,-130.954 418,-122.338"/>
<polygon fill="black" stroke="black" points="421.5,-122.249 418,-112.249 414.5,-122.249 421.5,-122.249"/>
</g>
<!-- GLib -->
<g id="node9" class="node"><title>GLib</title>
<ellipse fill="lightblue" stroke="black" cx="418" cy="-19" rx="31.8198" ry="19.0919"/>
<text text-anchor="middle" x="418" y="-15.9" font-family="Times Roman,serif" font-size="14.00">GLib</text>
</g>
<!-- libgimpmath&#45;&gt;GLib -->
<g id="edge8" class="edge"><title>libgimpmath&#45;&gt;GLib</title>
<path fill="none" stroke="black" d="M418,-73.9432C418,-66.1493 418,-56.9538 418,-48.3381"/>
<polygon fill="black" stroke="black" points="421.5,-48.2494 418,-38.2495 414.5,-48.2495 421.5,-48.2494"/>
</g>
<!-- app/core -->
<g id="node14" class="node"><title>app/core</title>
<ellipse fill="lawngreen" stroke="black" cx="336" cy="-685" rx="44.0472" ry="19.0919"/>
<text text-anchor="middle" x="336" y="-681.9" font-family="Times Roman,serif" font-size="14.00">app/core</text>
</g>
<!-- app/config&#45;&gt;app/core -->
<g id="edge26" class="edge"><title>app/config&#45;&gt;app/core</title>
<path fill="none" stroke="black" d="M317.438,-739.943C320.15,-731.916 323.364,-722.402 326.35,-713.565"/>
<polygon fill="black" stroke="black" points="329.699,-714.586 329.584,-703.992 323.067,-712.346 329.699,-714.586"/>
</g>
<!-- GEGL -->
<g id="node13" class="node"><title>GEGL</title>
<ellipse fill="lightblue" stroke="black" cx="339" cy="-315" rx="36.977" ry="19.0919"/>
<text text-anchor="middle" x="339" y="-311.9" font-family="Times Roman,serif" font-size="14.00">GEGL</text>
</g>
<!-- app/pdb -->
<g id="node15" class="node"><title>app/pdb</title>
<ellipse fill="lawngreen" stroke="black" cx="232" cy="-611" rx="41.9273" ry="19.0919"/>
<text text-anchor="middle" x="232" y="-607.9" font-family="Times Roman,serif" font-size="14.00">app/pdb</text>
</g>
<!-- app/core&#45;&gt;app/pdb -->
<g id="edge12" class="edge"><title>app/core&#45;&gt;app/pdb</title>
<path fill="none" stroke="black" d="M312.919,-668.577C298.276,-658.158 279.148,-644.548 263.166,-633.176"/>
<polygon fill="black" stroke="black" points="264.836,-630.068 254.659,-627.122 260.777,-635.772 264.836,-630.068"/>
</g>
<!-- app/gegl -->
<g id="node17" class="node"><title>app/gegl</title>
<ellipse fill="lawngreen" stroke="black" cx="336" cy="-611" rx="44.0472" ry="19.0919"/>
<text text-anchor="middle" x="336" y="-607.9" font-family="Times Roman,serif" font-size="14.00">app/gegl</text>
</g>
<!-- app/core&#45;&gt;app/gegl -->
<g id="edge14" class="edge"><title>app/core&#45;&gt;app/gegl</title>
<path fill="none" stroke="black" d="M330.012,-665.943C329.288,-658.088 329.08,-648.81 329.387,-640.136"/>
<polygon fill="black" stroke="black" points="332.891,-640.19 330.018,-629.992 325.904,-639.756 332.891,-640.19"/>
</g>
<!-- app/xcf -->
<g id="node19" class="node"><title>app/xcf</title>
<ellipse fill="lawngreen" stroke="black" cx="438" cy="-611" rx="39.8075" ry="19.0919"/>
<text text-anchor="middle" x="438" y="-607.9" font-family="Times Roman,serif" font-size="14.00">app/xcf</text>
</g>
<!-- app/core&#45;&gt;app/xcf -->
<g id="edge16" class="edge"><title>app/core&#45;&gt;app/xcf</title>
<path fill="none" stroke="black" d="M358.637,-668.577C372.998,-658.158 391.759,-644.548 407.434,-633.176"/>
<polygon fill="black" stroke="black" points="409.738,-635.828 415.777,-627.122 405.628,-630.162 409.738,-635.828"/>
</g>
<!-- app/file -->
<g id="node21" class="node"><title>app/file</title>
<ellipse fill="lawngreen" stroke="black" cx="80" cy="-537" rx="41.0122" ry="19.0919"/>
<text text-anchor="middle" x="80" y="-533.9" font-family="Times Roman,serif" font-size="14.00">app/file</text>
</g>
<!-- app/pdb&#45;&gt;app/file -->
<g id="edge18" class="edge"><title>app/pdb&#45;&gt;app/file</title>
<path fill="none" stroke="black" d="M203.41,-597.081C179.101,-585.246 144.017,-568.166 117.466,-555.24"/>
<polygon fill="black" stroke="black" points="118.985,-552.087 108.462,-550.856 115.921,-558.381 118.985,-552.087"/>
</g>
<!-- libgimpmodule -->
<g id="node23" class="node"><title>libgimpmodule</title>
<ellipse fill="#ff7256" stroke="black" cx="413" cy="-537" rx="70.9184" ry="19.0919"/>
<text text-anchor="middle" x="413" y="-533.9" font-family="Times Roman,serif" font-size="14.00">libgimpmodule</text>
</g>
<!-- app/pdb&#45;&gt;libgimpmodule -->
<g id="edge20" class="edge"><title>app/pdb&#45;&gt;libgimpmodule</title>
<path fill="none" stroke="black" d="M263.528,-598.11C291.34,-586.739 332.313,-569.988 364.331,-556.898"/>
<polygon fill="black" stroke="black" points="366.024,-559.987 373.955,-552.963 363.375,-553.508 366.024,-559.987"/>
</g>
<!-- app/gegl&#45;&gt;app/core -->
<g id="edge22" class="edge"><title>app/gegl&#45;&gt;app/core</title>
<path fill="none" stroke="black" d="M341.982,-629.992C342.709,-637.839 342.92,-647.115 342.615,-655.792"/>
<polygon fill="black" stroke="black" points="339.111,-655.746 341.988,-665.943 346.098,-656.178 339.111,-655.746"/>
</g>
<!-- app/text -->
<g id="node26" class="node"><title>app/text</title>
<ellipse fill="lawngreen" stroke="black" cx="282" cy="-537" rx="41.9273" ry="19.0919"/>
<text text-anchor="middle" x="282" y="-533.9" font-family="Times Roman,serif" font-size="14.00">app/text</text>
</g>
<!-- app/xcf&#45;&gt;app/text -->
<g id="edge24" class="edge"><title>app/xcf&#45;&gt;app/text</title>
<path fill="none" stroke="black" d="M409.387,-597.427C384.444,-585.595 348.056,-568.334 320.55,-555.287"/>
<polygon fill="black" stroke="black" points="321.76,-551.987 311.225,-550.863 318.76,-558.311 321.76,-551.987"/>
</g>
<!-- app/file&#45;&gt;app/plug&#45;in -->
<g id="edge32" class="edge"><title>app/file&#45;&gt;app/plug&#45;in</title>
<path fill="none" stroke="black" d="M80,-556.158C80,-584.37 80,-638.75 80,-685 80,-833 80,-833 80,-833 80,-875.187 84.7969,-886.276 99,-926 102.309,-935.256 106.934,-944.886 111.519,-953.453"/>
<polygon fill="black" stroke="black" points="108.498,-955.223 116.415,-962.264 114.617,-951.823 108.498,-955.223"/>
</g>
<!-- libgimpthumb -->
<g id="node34" class="node"><title>libgimpthumb</title>
<ellipse fill="#ff7256" stroke="black" cx="67" cy="-389" rx="67.1751" ry="19.0919"/>
<text text-anchor="middle" x="67" y="-385.9" font-family="Times Roman,serif" font-size="14.00">libgimpthumb</text>
</g>
<!-- app/file&#45;&gt;libgimpthumb -->
<g id="edge34" class="edge"><title>app/file&#45;&gt;libgimpthumb</title>
<path fill="none" stroke="black" d="M78.3271,-517.955C76.1162,-492.784 72.1982,-448.18 69.5998,-418.598"/>
<polygon fill="black" stroke="black" points="73.0694,-418.098 68.7078,-408.442 66.0963,-418.71 73.0694,-418.098"/>
</g>
<!-- libgimpbase -->
<g id="node29" class="node"><title>libgimpbase</title>
<ellipse fill="#ff7256" stroke="black" cx="418" cy="-241" rx="58.1882" ry="19.0919"/>
<text text-anchor="middle" x="418" y="-237.9" font-family="Times Roman,serif" font-size="14.00">libgimpbase</text>
</g>
<!-- libgimpmodule&#45;&gt;libgimpbase -->
<g id="edge36" class="edge"><title>libgimpmodule&#45;&gt;libgimpbase</title>
<path fill="none" stroke="black" d="M413.328,-517.579C414.183,-466.967 416.483,-330.777 417.503,-270.452"/>
<polygon fill="black" stroke="black" points="421.008,-270.166 417.677,-260.108 414.009,-270.048 421.008,-270.166"/>
</g>
<!-- app/vectors -->
<g id="node56" class="node"><title>app/vectors</title>
<ellipse fill="lawngreen" stroke="black" cx="334" cy="-463" rx="55.8614" ry="19.0919"/>
<text text-anchor="middle" x="334" y="-459.9" font-family="Times Roman,serif" font-size="14.00">app/vectors</text>
</g>
<!-- app/text&#45;&gt;app/vectors -->
<g id="edge76" class="edge"><title>app/text&#45;&gt;app/vectors</title>
<path fill="none" stroke="black" d="M294.854,-518.708C300.933,-510.057 308.313,-499.554 315.02,-490.01"/>
<polygon fill="black" stroke="black" points="318.047,-491.79 320.933,-481.596 312.32,-487.765 318.047,-491.79"/>
</g>
<!-- Pango -->
<g id="node70" class="node"><title>Pango</title>
<ellipse fill="lightblue" stroke="black" cx="225" cy="-463" rx="34.8574" ry="19.0919"/>
<text text-anchor="middle" x="225" y="-459.9" font-family="Times Roman,serif" font-size="14.00">Pango</text>
</g>
<!-- app/text&#45;&gt;Pango -->
<g id="edge78" class="edge"><title>app/text&#45;&gt;Pango</title>
<path fill="none" stroke="black" d="M267.91,-518.708C260.905,-509.613 252.323,-498.471 244.679,-488.549"/>
<polygon fill="black" stroke="black" points="247.41,-486.359 238.535,-480.572 241.865,-490.63 247.41,-486.359"/>
</g>
<!-- libgimpbase&#45;&gt;libgimpcolor -->
<g id="edge28" class="edge"><title>libgimpbase&#45;&gt;libgimpcolor</title>
<path fill="none" stroke="black" d="M418,-221.943C418,-214.149 418,-204.954 418,-196.338"/>
<polygon fill="black" stroke="black" points="421.5,-196.249 418,-186.249 414.5,-196.249 421.5,-196.249"/>
</g>
<!-- libgimpconfig -->
<g id="node31" class="node"><title>libgimpconfig</title>
<ellipse fill="#ff7256" stroke="black" cx="512" cy="-315" rx="65.9683" ry="19.0919"/>
<text text-anchor="middle" x="512" y="-311.9" font-family="Times Roman,serif" font-size="14.00">libgimpconfig</text>
</g>
<!-- libgimpconfig&#45;&gt;libgimpbase -->
<g id="edge30" class="edge"><title>libgimpconfig&#45;&gt;libgimpbase</title>
<path fill="none" stroke="black" d="M489.245,-297.087C477.006,-287.452 461.747,-275.439 448.493,-265.005"/>
<polygon fill="black" stroke="black" points="450.568,-262.184 440.545,-258.748 446.238,-267.684 450.568,-262.184"/>
</g>
<!-- libgimpthumb&#45;&gt;libgimpbase -->
<g id="edge38" class="edge"><title>libgimpthumb&#45;&gt;libgimpbase</title>
<path fill="none" stroke="black" d="M72.6671,-369.993C80.1177,-348.474 95.3615,-314.069 121,-296 157.281,-270.43 275.454,-254.627 351.346,-246.855"/>
<polygon fill="black" stroke="black" points="352.16,-250.291 361.761,-245.811 351.462,-243.326 352.16,-250.291"/>
</g>
<!-- Cairo -->
<g id="node38" class="node"><title>Cairo</title>
<ellipse fill="lightblue" stroke="black" cx="241" cy="-389" rx="33.234" ry="19.0919"/>
<text text-anchor="middle" x="241" y="-385.9" font-family="Times Roman,serif" font-size="14.00">Cairo</text>
</g>
<!-- app/actions -->
<g id="node39" class="node"><title>app/actions</title>
<ellipse fill="lawngreen" stroke="black" cx="799" cy="-315" rx="55.1543" ry="19.0919"/>
<text text-anchor="middle" x="799" y="-311.9" font-family="Times Roman,serif" font-size="14.00">app/actions</text>
</g>
<!-- app/dialogs -->
<g id="node40" class="node"><title>app/dialogs</title>
<ellipse fill="lawngreen" stroke="black" cx="756" cy="-685" rx="55.8614" ry="19.0919"/>
<text text-anchor="middle" x="756" y="-681.9" font-family="Times Roman,serif" font-size="14.00">app/dialogs</text>
</g>
<!-- app/actions&#45;&gt;app/dialogs -->
<g id="edge40" class="edge"><title>app/actions&#45;&gt;app/dialogs</title>
<path fill="none" stroke="black" d="M807.896,-334.161C819.955,-361.932 840,-415.275 840,-463 840,-537 840,-537 840,-537 840,-581.164 827.852,-592.208 805,-630 798.654,-640.495 790.185,-650.846 782.075,-659.688"/>
<polygon fill="black" stroke="black" points="779.448,-657.372 775.103,-667.035 784.526,-662.19 779.448,-657.372"/>
</g>
<!-- app/gui -->
<g id="node42" class="node"><title>app/gui</title>
<ellipse fill="lawngreen" stroke="black" cx="756" cy="-611" rx="39.8075" ry="19.0919"/>
<text text-anchor="middle" x="756" y="-607.9" font-family="Times Roman,serif" font-size="14.00">app/gui</text>
</g>
<!-- app/dialogs&#45;&gt;app/gui -->
<g id="edge42" class="edge"><title>app/dialogs&#45;&gt;app/gui</title>
<path fill="none" stroke="black" d="M756,-665.943C756,-658.149 756,-648.954 756,-640.338"/>
<polygon fill="black" stroke="black" points="759.5,-640.249 756,-630.249 752.5,-640.249 759.5,-640.249"/>
</g>
<!-- app/display -->
<g id="node44" class="node"><title>app/display</title>
<ellipse fill="lawngreen" stroke="black" cx="756" cy="-537" rx="55.8614" ry="19.0919"/>
<text text-anchor="middle" x="756" y="-533.9" font-family="Times Roman,serif" font-size="14.00">app/display</text>
</g>
<!-- app/gui&#45;&gt;app/display -->
<g id="edge44" class="edge"><title>app/gui&#45;&gt;app/display</title>
<path fill="none" stroke="black" d="M756,-591.943C756,-584.149 756,-574.954 756,-566.338"/>
<polygon fill="black" stroke="black" points="759.5,-566.249 756,-556.249 752.5,-566.249 759.5,-566.249"/>
</g>
<!-- app/widgets -->
<g id="node50" class="node"><title>app/widgets</title>
<ellipse fill="lawngreen" stroke="black" cx="754" cy="-463" rx="57.9828" ry="19.0919"/>
<text text-anchor="middle" x="754" y="-459.9" font-family="Times Roman,serif" font-size="14.00">app/widgets</text>
</g>
<!-- app/display&#45;&gt;app/widgets -->
<g id="edge50" class="edge"><title>app/display&#45;&gt;app/widgets</title>
<path fill="none" stroke="black" d="M755.485,-517.943C755.274,-510.149 755.026,-500.954 754.793,-492.338"/>
<polygon fill="black" stroke="black" points="758.289,-492.151 754.52,-482.249 751.292,-492.34 758.289,-492.151"/>
</g>
<!-- libgimpwidgets -->
<g id="node46" class="node"><title>libgimpwidgets</title>
<ellipse fill="#ff7256" stroke="black" cx="573" cy="-537" rx="70.9184" ry="19.0919"/>
<text text-anchor="middle" x="573" y="-533.9" font-family="Times Roman,serif" font-size="14.00">libgimpwidgets</text>
</g>
<!-- libgimpwidgets&#45;&gt;libgimpconfig -->
<g id="edge46" class="edge"><title>libgimpwidgets&#45;&gt;libgimpconfig</title>
<path fill="none" stroke="black" d="M554.592,-518.249C545.949,-508.295 536.384,-495.374 531,-482 512.661,-436.447 510.223,-378.617 510.704,-344.337"/>
<polygon fill="black" stroke="black" points="514.209,-344.16 510.947,-334.08 507.211,-343.994 514.209,-344.16"/>
</g>
<!-- GTK+ -->
<g id="node48" class="node"><title>GTK+</title>
<ellipse fill="lightblue" stroke="black" cx="577" cy="-463" rx="36.977" ry="19.0919"/>
<text text-anchor="middle" x="577" y="-459.9" font-family="Times Roman,serif" font-size="14.00">GTK</text>
</g>
<!-- libgimpwidgets&#45;&gt;GTK+ -->
<g id="edge48" class="edge"><title>libgimpwidgets&#45;&gt;GTK+</title>
<path fill="none" stroke="black" d="M574.03,-517.943C574.451,-510.149 574.948,-500.954 575.414,-492.338"/>
<polygon fill="black" stroke="black" points="578.915,-492.424 575.959,-482.249 571.925,-492.046 578.915,-492.424"/>
</g>
<!-- app/menus -->
<g id="node52" class="node"><title>app/menus</title>
<ellipse fill="lawngreen" stroke="black" cx="756" cy="-389" rx="53.2379" ry="19.0919"/>
<text text-anchor="middle" x="756" y="-385.9" font-family="Times Roman,serif" font-size="14.00">app/menus</text>
</g>
<!-- app/widgets&#45;&gt;app/menus -->
<g id="edge52" class="edge"><title>app/widgets&#45;&gt;app/menus</title>
<path fill="none" stroke="black" d="M754.515,-443.943C754.726,-436.149 754.974,-426.954 755.207,-418.338"/>
<polygon fill="black" stroke="black" points="758.708,-418.34 755.48,-408.249 751.711,-418.151 758.708,-418.34"/>
</g>
<!-- app/tools -->
<g id="node54" class="node"><title>app/tools</title>
<ellipse fill="lawngreen" stroke="black" cx="641" cy="-759" rx="46.1672" ry="19.0919"/>
<text text-anchor="middle" x="641" y="-755.9" font-family="Times Roman,serif" font-size="14.00">app/tools</text>
</g>
<!-- app/widgets&#45;&gt;app/tools -->
<g id="edge54" class="edge"><title>app/widgets&#45;&gt;app/tools</title>
<path fill="none" stroke="black" d="M727.313,-480.099C714.423,-489.715 699.864,-502.832 691,-518 651.098,-586.285 642.758,-681.803 641.199,-729.484"/>
<polygon fill="black" stroke="black" points="637.694,-729.637 640.946,-739.72 644.692,-729.81 637.694,-729.637"/>
</g>
<!-- app/menus&#45;&gt;app/actions -->
<g id="edge66" class="edge"><title>app/menus&#45;&gt;app/actions</title>
<path fill="none" stroke="black" d="M766.851,-370.327C771.74,-361.913 777.611,-351.809 782.991,-342.55"/>
<polygon fill="black" stroke="black" points="786.155,-344.072 788.153,-333.667 780.102,-340.555 786.155,-344.072"/>
</g>
<!-- app/tools&#45;&gt;app/core -->
<g id="edge68" class="edge"><title>app/tools&#45;&gt;app/core</title>
<path fill="none" stroke="black" d="M601.127,-749.326C545.412,-735.808 444.384,-711.296 384.418,-696.747"/>
<polygon fill="black" stroke="black" points="384.983,-693.283 374.44,-694.326 383.333,-700.086 384.983,-693.283"/>
</g>
<!-- app/tools&#45;&gt;app/dialogs -->
<g id="edge70" class="edge"><title>app/tools&#45;&gt;app/dialogs</title>
<path fill="none" stroke="black" d="M665.953,-742.943C682.076,-732.569 703.289,-718.918 721.076,-707.473"/>
<polygon fill="black" stroke="black" points="722.979,-710.41 729.495,-702.055 719.191,-704.524 722.979,-710.41"/>
</g>
<!-- app/tools&#45;&gt;libgimpwidgets -->
<g id="edge72" class="edge"><title>app/tools&#45;&gt;libgimpwidgets</title>
<path fill="none" stroke="black" d="M638.418,-739.973C633.758,-708.448 622.562,-643.73 603,-592 599.525,-582.81 594.767,-573.258 590.065,-564.744"/>
<polygon fill="black" stroke="black" points="593.052,-562.918 585.047,-555.978 586.977,-566.395 593.052,-562.918"/>
</g>
<!-- app/vectors&#45;&gt;Cairo -->
<g id="edge56" class="edge"><title>app/vectors&#45;&gt;Cairo</title>
<path fill="none" stroke="black" d="M311.96,-445.463C298.849,-435.031 282.111,-421.712 268.184,-410.63"/>
<polygon fill="black" stroke="black" points="270.362,-407.891 260.358,-404.403 266.004,-413.368 270.362,-407.891"/>
</g>
<!-- app/paint -->
<g id="node58" class="node"><title>app/paint</title>
<ellipse fill="lawngreen" stroke="black" cx="339" cy="-389" rx="46.8775" ry="19.0919"/>
<text text-anchor="middle" x="339" y="-385.9" font-family="Times Roman,serif" font-size="14.00">app/paint</text>
</g>
<!-- app/vectors&#45;&gt;app/paint -->
<g id="edge58" class="edge"><title>app/vectors&#45;&gt;app/paint</title>
<path fill="none" stroke="black" d="M335.288,-443.943C335.814,-436.149 336.436,-426.954 337.018,-418.338"/>
<polygon fill="black" stroke="black" points="340.517,-418.463 337.699,-408.249 333.533,-417.991 340.517,-418.463"/>
</g>
<!-- app/paint&#45;&gt;GEGL -->
<g id="edge60" class="edge"><title>app/paint&#45;&gt;GEGL</title>
<path fill="none" stroke="black" d="M339,-369.943C339,-362.149 339,-352.954 339,-344.338"/>
<polygon fill="black" stroke="black" points="342.5,-344.249 339,-334.249 335.5,-344.249 342.5,-344.249"/>
</g>
<!-- app/paint&#45;&gt;libgimpconfig -->
<g id="edge62" class="edge"><title>app/paint&#45;&gt;libgimpconfig</title>
<path fill="none" stroke="black" d="M371.54,-375.081C398.124,-363.71 436.03,-347.496 465.765,-334.777"/>
<polygon fill="black" stroke="black" points="467.29,-337.931 475.108,-330.781 464.537,-331.495 467.29,-337.931"/>
</g>
<!-- app/paint&#45;funcs -->
<g id="node62" class="node"><title>app/paint&#45;funcs</title>
<ellipse fill="lawngreen" stroke="black" cx="201" cy="-315" rx="70.9184" ry="19.0919"/>
<text text-anchor="middle" x="201" y="-311.9" font-family="Times Roman,serif" font-size="14.00">app/paint&#45;funcs</text>
</g>
<!-- app/paint&#45;&gt;app/paint&#45;funcs -->
<g id="edge64" class="edge"><title>app/paint&#45;&gt;app/paint&#45;funcs</title>
<path fill="none" stroke="black" d="M310.406,-373.667C290.562,-363.026 263.832,-348.692 241.761,-336.857"/>
<polygon fill="black" stroke="black" points="243.401,-333.765 232.934,-332.124 240.092,-339.934 243.401,-333.765"/>
</g>
<!-- app/paint&#45;funcs&#45;&gt;app/composite -->
<g id="edge74" class="edge"><title>app/paint&#45;funcs&#45;&gt;app/composite</title>
<path fill="none" stroke="black" d="M192.538,-334.205C181.067,-362.03 162,-415.437 162,-463 162,-759 162,-759 162,-759 162,-800.375 167.49,-847.969 171.594,-877.768"/>
<polygon fill="black" stroke="black" points="168.161,-878.487 173.031,-887.896 175.092,-877.504 168.161,-878.487"/>
</g>
<!-- libgimp -->
<g id="node72" class="node"><title>libgimp</title>
<ellipse fill="#ff7256" stroke="black" cx="553" cy="-611" rx="41.2167" ry="19.0919"/>
<text text-anchor="middle" x="553" y="-607.9" font-family="Times Roman,serif" font-size="14.00">libgimp</text>
</g>
<!-- libgimp&#45;&gt;libgimpmodule -->
<g id="edge80" class="edge"><title>libgimp&#45;&gt;libgimpmodule</title>
<path fill="none" stroke="black" d="M525.674,-596.556C505.302,-585.788 477.131,-570.898 454.054,-558.7"/>
<polygon fill="black" stroke="black" points="455.641,-555.58 445.164,-554.001 452.369,-561.768 455.641,-555.58"/>
</g>
<!-- libgimp&#45;&gt;libgimpwidgets -->
<g id="edge82" class="edge"><title>libgimp&#45;&gt;libgimpwidgets</title>
<path fill="none" stroke="black" d="M558.15,-591.943C560.297,-584.002 562.836,-574.606 565.203,-565.851"/>
<polygon fill="black" stroke="black" points="568.637,-566.559 567.867,-555.992 561.879,-564.733 568.637,-566.559"/>
</g>
<!-- app/tests -->
<g id="node75" class="node"><title>app/tests</title>
<ellipse fill="lawngreen" stroke="black" cx="756" cy="-759" rx="44.7575" ry="19.0919"/>
<text text-anchor="middle" x="756" y="-755.9" font-family="Times Roman,serif" font-size="14.00">app/tests</text>
</g>
<!-- app/tests&#45;&gt;app/dialogs -->
<g id="edge84" class="edge"><title>app/tests&#45;&gt;app/dialogs</title>
<path fill="none" stroke="black" d="M756,-739.943C756,-732.149 756,-722.954 756,-714.338"/>
<polygon fill="black" stroke="black" points="759.5,-714.249 756,-704.249 752.5,-714.249 759.5,-714.249"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 24 KiB

105
devel-docs/gitlab-mr.md Normal file
View file

@ -0,0 +1,105 @@
# Merge Request tricks
By default, a Merge Request pipeline would only build GIMP for
Debian with merely testing purposes.
You might want to actually generate easy-to-install builds, in
particular if you want it to be testable for non-developers, or
various other reasons.
So, as a developer, comment: /label ~Package:`AppImage` or `Flatpak` or `Windows Installer` or `Microsoft Store`
(this will generate the requested package for new pipelines).
☣️ We remind that these packages are built on-top of development code
(i.e. work-in-progress and potentially unstable codebase likely
containing critical bugs) with additional code which can be contributed
by anyone (any anonymous person is allowed to propose patches as merge
requests not only known team members).
Therefore you should always check the merge request changes before
running the code and never blindly trust that it is harmless. In any
case, run these builds at your own risk. ☢️
## Reviewing MR branches
Reviewing merge requests on the Gitlab interface often leads to poor
review, because:
- It doesn't show tabs, trailing whitespaces and other space issues
which a well-configured CLI git would usually emphasize with colors.
- The commit history is not emphasized, only the final results, but it's
very important to check individual commits, as well as usable commit
messages.
- It's anyway usually much easier to review patches on your usual
workflow environment rather than in a hard-to-use web interface.
There are ways to work on your local environments.
### Fetching MR branches automatically (read-only)
This first one is more of a trick, but an incredibly useful one.
Unfortunately it is read-only, so it means you can review but not edit
the MR yourself. Nevertheless since having to edit a MR should be the
exception, not the rule, it's actually not too bad.
Edit your `.git/config` by adding a second "fetch =" rule to the
"origin" remote. It should read:
```
[remote "origin"]
fetch = +refs/heads/*:refs/remotes/origin/*
fetch = +refs/merge-requests/*/head:refs/remotes/origin/merge-requests/*
url = git@ssh.gitlab.gnome.org:GNOME/gimp.git
```
From now on, when you `git pull` or `git fetch` the origin remote, any
new or updated merge request will also be fetched. \o/
### Pushing to a third-party MR branch
There are cases when you want to push to the MR branch. It should stay
rare occasions, but it can be for instance when the contributor seems
stuck and doesn't know how to do some things; or maybe one doesn't
understand instructions; sometimes also some contributors disappear
after pushing their patch and never answer to review anymore.
When this happen, you could merge the commit and fix it immediately
after (but it's never good to leave the repo in a bad state, even for
just a few minutes). You could also apply, fix and push the fixed
commits directly, but then the MR has to be closed and it doesn't look
like it was applied (which is not the end of the world, but it's still
nicer to show proper status on which patches were accepted or not).
Moreover you would not be able to pass the CI build.
So we will fetch the remote yet without naming the remote:
- Click the "Check out branch" button below the merge request
description. Gitlab gives you instructions but we will only use the
first step ("Fetch and check out the branch for this merge request").
For instance if contributor `xyz` created the branch `fix-bug-123` on
their own remote, you would run:
```
git fetch "git@ssh.gitlab.gnome.org:xyz/gimp.git" 'fix-bug-123'
git checkout -b 'xyz/fix-bug-123' FETCH_HEAD
```
- Now that you are in a local branch with their code, make your fix, add
a local commit.
- Finally push to the contributor's own remote with the call:
```
git push git@ssh.gitlab.gnome.org:xyz/gimp.git xyz/fix-bug-123:fix-bug-123
```
This assumes that the contributor checked the option "*Allow commits
from members who can merge to the target branch.*" (which we ask
contributors to check, and it's set by default)
- Now check the MR page. It will normally be updated with your new
commit(s) and a new pipeline should be triggered.
- Finally if you don't need the local branch anymore, you may delete it
locally. The nice thing is that since you didn't name the remote, it
doesn't pollute your git output and all data will be simply disposed
of the next time `git gc` runs (implicitly or explicitly).

51
devel-docs/includes.txt Normal file
View file

@ -0,0 +1,51 @@
includes.txt
============
The include policy for the files in app/ is as follows:
Each subdirectory has a <module>-types.h file which defines the type
space known to this module. All .c files in the directory include this
(and only this) <module>-types.h file. <foo>-types.h files from other
modules are included from the <module>-types.h file only. This way
<module>-types.h becomes the only place where the namespace known to a
module is defined.
***** .h files *****
No .h file includes anything, with two exceptions:
- objects include their immediate parent class
- if the header uses stuff like time_t (or off_t), it includes
<time.h> (or <sys/types.h>). This only applies to system stuff!
***** .c files *****
The include order of all .c files of a module is as follows:
/* example of a .c file from app/core */
#include "config.h" /* always and first */
#include <glib.h> /* *only* needed if the file needs stuff */
/* like G_OS_WIN32 for conditional inclusion */
/* of system headers */
#include <system headers> /* like <stdio.h> */
#include <glib-object.h>
#include "libgimpfoo/gimpfoo.h" /* as needed, e.g. "libgimpbase/gimpbase.h" */
#include "libgimpbar/gimpbar.h"
#include "core-types.h" /* and _no_ other foo-types.h file */
#include "base/foo.h" /* files from modules below this one */
#include "base/bar.h"
#include "gimp.h" /* files from this module */
#include "gimpimage.h"
#include "gimpwhatever.h"
#include "gimp-intl.h" /* if needed, *must* be the last include */

210
devel-docs/interpreters.txt Normal file
View file

@ -0,0 +1,210 @@
# Interpreters for GIMP plugins
## About this document
This describes how GIMP invokes interpreters for GIMP plugin files.
This doesn't discuss the architecture of GIMP's interpreters,
or how to write an interpreted plugin.
The audience is mainly GIMP developers.
This may also interest users who want to use different interpreters.
## Brief summary
On Linux (except for Lua) and MacOS, a shebang in a GIMP plugin
text file is enough to indicate what interpreter to start.
On Windows (and Linux for Lua), you also need an .interp file installed with GIMP.
It can get complicated;
there are many combinations of environment variables, shebangs, file suffixes, and .interp files that can work.
*To insure a GIMP interpreted plugin works across platforms,
it should have a shebang.*
*Except that ScriptFu plugin files installed to /scripts do not need a shebang
since the ScriptFu extension reads them.*
## Partial history of interpreters in GIMP
Rarely are interpreters added to GIMP.
GIMP 2 offers Perl, Scheme, and Python2 interpreters.
GIMP 3 offers Python3, lua, javascript, and the gimp-script-fu-interpreter interpreters.
## Background
An interpreter usually reads a text file.
A user often launches an interpreter and passes a text file.
But users can also double-click on a text file to launch the corresponding interpreter.
Similarly, GIMP launches an interpreter on GIMP plugin text files.
GIMP must figure out the "corresponding" interpreter.
The general mechanism for launching interpreters from their text files is built into the operating system.
On Linux and MacOS, the mechanism is called a shebang or sh-bang.
On Windows, the mechanism "associates" file extensions with programs.
GIMP uses similar mechanisms to launch interpreters.
See the code in /app/plug-ins/gimpinterpreterdb.c .
*The exception is the ScriptFu extension.
GIMP starts it when GIMP starts and it reads its ".scm" plugin files from the /scripts directory without benefit
of the shebang mechanism.*
GIMP uses the mechanism when it queries plugin files at startup.
Subsequently, GIMP knows the interpreter to launch,
for example when a user clicks on a menu item implemented by an interpreter.
A user should not click on a GIMP plugin file in a file browser;
only one of the GIMP apps should launch interpreted GIMP plugin files.
## Platform differences
On Linux (except for Lua) and MacOS, you simply need a shebang in a plugin text file.
On Windows (and Linux, at least for Lua), you must also define an .interp file.
The .interp files are part of GIMP's installation on Windows (and Linux for Lua)
(in both installer and Microsoft Store versions).
The .interp files are built when the Windows installer is built.
See the source file: /build/windows/installer/base_gimp3264.iss .
A user can optionally create .interp files on Linux and MacOS.
But they are not usually part of a Linux installation.
Sophisticated users can edit .interp files to change which interpreters GIMP launches.
## shebangs
A shebang is text in the first line of a text file to be interpreted.
A shebang starts with "#!",
followed by the name or path of an interpreter,
or followed by "/usr/bin/env", a space, and the name or path of an interpreter.
!!! Shebangs for GIMP plugins always use UNIX notation, i.e. forward slashes in path strings.
Even on Windows, the shebangs are in UNIX notation.
Recommended examples for GIMP 3 (see repo directory /extensions/goat-exercises):
#!/usr/bin/env python3
#!/usr/bin/env lua
#!/usr/bin/env gjs
#!/usr/bin/env gimp-script-fu-interpreter-3.0
Other examples:
#!python
#!/usr/bin/python
#!/usr/bin/env python
Whether the other examples actually work depends on:
- the platform
- the user's environment, namely search PATH's
- any .interp files
## .interp files
Again, .interp files are necessary on Windows (and Linux, at least for Lua).
They tell GIMP which executable interpreter to launch for a GIMP plugin text file.
You usually have one .interp file for each interpreter.
For example:
- python.interp
- lua.interp
- gimp-script-fu-interpreter.interp
The repo file /data/interpreters/default.interp is a non-functioning template
for a <foo>.interp file.
.interp files are installed on Windows to, for example:
C:\Users\foo\AppData\Programs\GIMP 3.0\lib\gimp\3.0\interpreters
interp files have three kinds of lines:
- "program" in the form lhs=rhs
- "extension" in the "binfmt" format
- "magic" in the "binfmt" format
### "program" lines in an .interp file
These lines associate a shebang with a path to an executable.
These are in the form: "lhs=rhs"
where lhs/rhs denotes "left hand side" and "right hand side."
The lhs matches the full text of a shebang after the "#!"
For example, the lhs can be "/usr/bin/env python", having a space.
Since a shebang is always in UNIX notation, any slashes are forward.
The rhs specifies a path to an interpreter.
The rhs on the Windows platform is in Windows notation, using back slashes.
For example, the rhs can be "C:\Users\foo\AppData\Programs\GIMP 3.0\bin\python"
### "extension" lines in an .interp file
These lines associate a three-letter (sic) file extension (suffix) with a path to an executable.
These lines are in binfmt format.
See https://en.wikipedia.org/wiki/Binfmt_misc.
Informally the format is: ":name:type:offset:magic: mask:interpreter:flags"
!!! Note the field delimiter is usually ":" but can be another character.
GIMP parses the binfmt using the first character as the delimiter.
The first field is a name or identifier and has little significance.
The second field is an "E".
The third, fifth, and seventh fields are usually empty.
The fourth field is an up-to-three letter suffix.
The sixth field "interpreter" is a name or path to an executable interpreter.
If the sixth field is a Windows path that has a ":"
then the fields must be delimited with another character, say a ",".
Examples:
:python:E::py::python3:
:luajit:E::lua::luajit:
,python,E,,py,,C:\Users\foo\AppData\GIMP 3.0\bin\python3,
Note the examples are not necessarily working examples.
They might not work if the name or path is not found,
for example if luajit was not installed to the Windows system directory of executables.
Note one example shows a path in Windows notation,
having a ":", back slashes, and a space in the path.
### "magic" lines in an .interp file
These lines associate "magic" bytes (inside a binary file) with a path to an executable.
These lines are in binfmt format.
The second field is an "M".
We won't discuss these further, since they are little used.
Binary files on Windows might not have "magic" bytes.
Usually interpreters read text files, and rarely binary files.
## Building .interp files for windows
If a GIMP developer adds an interpreter to the GIMP package,
they must modify GIMP's build for Windows
to ensure proper .interp files are installed.
See the repo file: /build/windows/installer/base_gimp3264.iss .
For the convenience of users, we usually install an .interp file having many lines.
Only one "program" line is needed if users only install canonical plugin text files
having a recommended shebang
using the actual filename of the target interpreter.
But since users may install non-canonical plugin text files by copying files,
for convenience we have more lines in the .interp file.
An extra "extension" line allows plugin text files without any shebang but a proper extension.
An extra "program" line allows plugin text files
having shebangs with alternate names for an interpreter.

13
devel-docs/meson.build Normal file
View file

@ -0,0 +1,13 @@
devel_docs_build_root = meson.current_build_dir()
scan_args_common = [
'--deprecated-guards=GIMP_DISABLE_DEPRECATED',
]
mkdb_args_common = [
'--name-space=gimp',
]
if gi_docgen.found() and have_gobject_introspection
subdir('reference')
endif

32
devel-docs/os-support.txt Normal file
View file

@ -0,0 +1,32 @@
## GIMP's Operating System Support
GIMP is available on a wide range of operating systems.
As a general rule, we should stop supporting a platform as soon as the
publisher stops supporting it, or if it is nearly not used anymore.
There is no accurate rule though, and it also mostly depends on what the
developers maintaining GIMP for this platform will decide.
### GNU/Linux, *BSD…
Until GIMP 3.0 release, Debian 12 "bookworm" stable will be our baseline target.
I.e. that we can bump a minimum dependency version only if it is in Debian
bookworm.
After GIMP 3.0 release, we might get back to depend on Debian Testing.
### macOS
Compatibility with MacOS 11 and over.
Hardware:
* x86_64 (Intel)
* ARM 64-bit (Apple Silicon)
### Windows
Windows 10 and over.
Hardware:
* x86 32 and 64-bit
* ARM 64-bit

316
devel-docs/parasites.txt Normal file
View file

@ -0,0 +1,316 @@
PARASITE REGISTRY
=================
This document describes parasites in GIMP.
Table of contents
-----------------
Parasite registry
Table of contents
Audience
1. Namespace
2. Known prefixes
3. Known global parasites
4. Known image parasites
5. Known layer/drawable parasites
6. Parasite format
Audience
--------
This document is designed for the convenience of GIMP developers.
It does not need to concern users.
>>>> If your plug-in or script writes parasites, please
>>>> amend this file in the Git repository or submit patches to
>>>> gimp-developer-list@gnome.org
1. NAMESPACE
============
Plug-in-specific data should be prefixed by the plug-in function name and
a slash, i.e. private data of plug_in_displace should be named like:
plug_in_displace/data1
plug_in_displace/data2
etc.
Global data follows no strict rules.
2. KNOWN PREFIXES
=================
"tiff" : The standard GIMP TIFF plugin
"jpeg" : The standard GIMP JPEG plugin
"png" : The standard GIMP PNG plugin
"dcm" : The standard GIMP DICOM plugin
"gimp" : For common and standard parasites
3. KNOWN GLOBAL PARASITES
=========================
"jpeg-save-defaults" (GLOBAL, PERSISTENT)
Default save parameters used by the JPEG plug-in.
"png-save-defaults" (GLOBAL, PERSISTENT)
Default save parameters used by the PNG plug-in.
"<plug-in>/_fu_data" (GLOBAL, IMAGE, DRAWABLE, PERSISTENT)
The Gimp::Fu module (Perl) might store the arguments of the
last plug-in invocation. It is usually attached to images,
but might also be found globally. The data format is either
pure character data (Data::Dumper) or a serialized data
stream created by Storable::nfreeze.
"exif-orientation-rotate" (GLOBAL, PERSISTENT)
Whether a load plug-in should automatically rotate the image
according to the orientation specified in the EXIF data. This
has values "yes" or "no". If the parasite is not set, the
plug-in should ask the user what to do. This parasite may be
removed in a future version (assuming always yes).
4. KNOWN IMAGE PARASITES
========================
"gimp-comment" (IMAGE, PERSISTENT)
Standard GIF-style image comments. This parasite should be
human-readable text in UTF-8 encoding. A trailing \0 might
be included and is not part of the comment. Note that image
comments may also be present in the "gimp-metadata" parasite.
"gimp-brush-name" (IMAGE, PERSISTENT)
A string in UTF-8 encoding specifying the name of a GIMP brush.
Currently, the gbr plug-in uses this parasite when loading and
saving .gbr files. A trailing \0 might be included and is not
part of the name.
"gimp-brush-pipe-name" (IMAGE, PERSISTENT)
A string in UTF-8 encoding specifying the name of a GIMP brush
pipe. Currently, the gih plug-in uses this parasite when loading and
saving .gih files. A trailing \0 might be included and is not
part of the name.
"gimp-brush-pipe-parameters" (IMAGE, PERSISTENT)
This is all very preliminary:
A string, containing parameters describing how an brush pipe
should be used. The contents is a space-separated list of
keywords and values. The keyword and value are separated by a
colon.
This parasite is currently attached to an image by the psp
plug-in when it loads a .tub file (Paint Shop Pro picture
tube). It is used (first attached with values asked from the
user, if nonexistent) by the gpb plug-in when it saves a .gih
file. The .gih file contains the same text in it.
The keywords are:
ncells: the number of brushes in the brush pipe
step: the default spacing for the pipe
dim: the dimension of the pipe. The number of cells
in the pipe should be equal to the product
of the ranks of each dimension.
cols: number of columns in each layer of the image,
to be used when editing the pipe as a GIMP image
rows: ditto for rows. Note that the number of columns and rows
not necessarily are identical to the ranks of the
dimensions of a pipe, but in the case of two-
and three-dimensional pipes, it probably is.
rank0, rank1, ...: (one for each dimension): the index range
for that dimension
placement: "default", "constant" or "random". "constant" means
use the spacing in the first brush in the pipe.
"random" means perturb that with some suitable
random number function. (Hmm, would it be overdoing it
if the pipe also could specify what random function
and its parameters...?)
sel0, sel1, ...: "default", "random", "incremental", "angular",
"pressure", "velocity", and whatever else suitable we might
think of ;-) Determines how one index from each dimension is
selected (until we have pinpointed the brush to use).
"gimp-image-grid" (IMAGE, PERSISTENT)
The GimpGrid object serialized to a string. Saved as parasite
to keep the XCF files backwards compatible. Although gimp-1.2
does not know how to handle the image grid, it keeps the grid
information intact.
"gimp-pattern-name" (IMAGE, PERSISTENT)
A string in UTF-8 encoding specifying the name of a GIMP pattern.
Currently, the pat plug-in uses this parasite when loading and
saving .pat files. A trailing \0 might be included and is not
part of the name.
"tiff-save-options" (IMAGE)
The TiffSaveVals structure from the TIFF plugin.
"jpeg-save-options" (IMAGE)
The JpegSaveVals structure from the JPEG plugin.
"jpeg-exif-data" (IMAGE) (deprecated)
The ExifData structure serialized into a uchar* blob from
libexif. This is deprecated in favor of "exif-data".
"jpeg-original-settings" (IMAGE, PERSISTENT)
The settings found in the original JPEG image: quality (IJG),
color space, component subsampling and quantization tables.
These can be reused when saving the image in order to minimize
quantization losses and keep the same size/quality ratio.
"gamma" (IMAGE, PERSISTENT)
The original gamma this image was created/saved. For JPEG; this is
always one, for PNG it's usually taken from the image data. GIMP
might use and modify this. The format is an ascii string with the
gamma exponent as a flotingpoint value.
Example: for sRGB images this might contain "0.45454545"
"chromaticity" (IMAGE, PERSISTENT)
This parasite contains 8 floatingpoint values (ascii, separated by
whitespace) specifying the x and y coordinates of the whitepoint, the
red, green and blue primaries, in this order.
Example: for sRGB images this might contain
"0.3127 0.329 0.64 0.33 0.3 0.6 0.15 0.06"
wx wy rx ry gx gy bx by
"rendering-intent" (IMAGE, PERSISTENT)
This specifies the rendering intent of the image. It's a value
between 0 and 3, again in ascii:
0 - perceptual (e.g. for photographs)
1 - relative colorimetric (e.g. for logos)
2 - saturation-preserving (e.g. for business charts)
3 - absolute colorimetric
"hot-spot" (IMAGE, PERSISTENT)
Use this parasite to store an image's "hot spot". Currently
used by the XBM plugin to store mouse cursor hot spots.
Example: a hot spot at coordinates (5,5) is stored as "5 5"
"exif-data" (IMAGE, PERSISTENT)
The ExifData structure serialized into a character array by
libexif (using exif_data_save_data). If a "gimp-metadata"
parasite is present, it should take precedence over this one.
"gimp-metadata" (IMAGE, PERSISTENT)
The metadata associated with the image, serialized as one XMP
packet. This metadata includes the contents of any XMP, EXIF
and IPTC blocks from the original image, as well as
user-specified values such as image comment, copyright,
license, etc.
"icc-profile" (IMAGE, PERSISTENT | UNDOABLE)
This contains an ICC profile describing the color space the
image was produced in. TIFF images stored in PhotoShop do
oftentimes contain embedded profiles. An experimental color
manager exists to use this parasite, and it will be used
for interchange between TIFF and PNG (identical profiles)
"icc-profile-name" (IMAGE, PERSISTENT | UNDOABLE)
The profile name is a convenient name for referring to the
profile. It is for example used in the PNG file format. The
name must be stored in UTF-8 encoding. If a file format uses
a different character encoding, it must be converted to UTF-8
for use as a parasite.
"decompose-data" (IMAGE, NONPERSISTENT)
Starting with GIMP 2.4, this is added to images produced by
the decompose plug-in, and contains information necessary to
recompose the original source RGB layer from the resulting
grayscale layers. It is ascii; a typical example would be
"source=2 type=RGBA 4 5 6 7". This means that layer 2 was
decomposed in RGBA mode, giving rise to layers 4, 5, 6, and 7.
"print-settings" (IMAGE, NONPERSISTENT)
This parasite is stored by the Print plug-in and holds settings
done in the Print dialog. It also has a version field so that
changes to the parasite can be done. GIMP 2.4 used version 0.3.
The format is GKeyFile. A lot of the contents are identical to
what is stored in ~/.gimp-2.x/print-settings but the parasite
has some additional image-related fields.
"print-page-setup" (IMAGE, NONPERSISTENT)
This parasite is stored by the Print plug-in and holds settings
done in the Page Setup dialog. The format is GKeyFile as created
from GtkPageSetup. The content is identical to what is stored in
~/.gimp-2.x/print-page-setup.
"dcm/XXXX-XXXX-AA" (IMAGE, PERSISTENT)
These parasites are stored by the Dicom plug-in and hold the DICOM
element information for that image. The format is raw binary data
as read from the original image.
where: XXXX is a 4-digit ascii encoded hexadecimal number
AA is a two character ascii value representing the Dicom
element's Value Representation (VR)
5. KNOWN LAYER/DRAWABLE PARASITES
=================================
"gimp-text-layer" (LAYER, PERSISTENT)
The associated GimpText object serialized to a string. For
convenience the string is terminated by a trailing '\0'.
The idea of using a parasite for text layers is to keep the XCF
files backward compatible. Although gimp-1.2 doesn't know how
to handle the text layer, it keeps the parasite intact.
"gfig" (LAYER, PERSISTENT)
As of GIMP 2.2, the gfig plug-in creates its own layers, and
stores a representation of the figure as a layer parasite.
The parasite contains a GFig save file, in an ascii format.
If gfig is started while the active layer contains a "gfig"
parasite, the contents of the parasite are loaded at startup.
6. PARASITE FORMAT
==================
The parasite data format is not rigidly specified. For non-persistent
parasites you are entirely free, as the parasite data does not survive the
current gimp session. If you need persistent data, you basically have to
choose between the following alternatives (also, having some standard for
non-persistent data might be fine as well):
- Cook your own binary data format
You can invent your own data format. This means that you will either
loose totally (consider endian-ness or version-ness issues) or you will
get yourself into deep trouble to get it "right" in all cases.
- Use character (string) data
Obvious to Perl people but less so to C programmers: just sprintf your
data into a string (e.g. "SIZE 100x200 XRES 300 YRES 300") and store
that in the parasite, and later sscanf it again. This often solves most
of the problems you might encounter, makes for easier debugging and
more robustness (consider the case when you add more entries to your
persistent data: older plug-ins might be able to read the relevant
parts and your application can detect missing fields easily). The
drawback is that your data is likely to be larger than a compact binary
representation would be. Not much a problem for most applications,
though.
You could also use one parasite per field you store, i.e. foo-size,
foo-offset-x, foo-offset-y etc...
- Use the libgimpconfig serialize functions
This is a special case of the previous one, using the convenience
functions provided by libgimpconfig. If you are not concerned about
the size of the string representation of your data, you can use
gimp_config_serialize_to_string() and other functions to easily
convert your data to/from a character string.

View file

@ -0,0 +1,119 @@
[library]
namespace = "GimpUi"
version = "@GIMP_VERSION@"
browse_url = "https://gitlab.gnome.org/GNOME/gimp/"
repository_url = "https://gitlab.gnome.org/GNOME/gimp.git"
website_url = "https://www.gimp.org"
authors = "GIMP contributors"
logo_url = "@GIMP_LOGO@"
license = "GPL-3.0-or-later"
description = "GIMP UI library"
dependencies = [
'Babl-0.1',
'Gimp-3.0',
'GLib-2.0',
'GObject-2.0',
'GdkPixbuf-2.0',
'Gegl-0.4',
'Gio-2.0',
'Gtk-3.0',
'cairo-1.0',
]
devhelp = true
search_index = true
# These links are mostly used for the dependency lists in index page.
[dependencies."Babl-0.1"]
name = "Babl"
description = "Pixel encoding and color space conversion engine"
docs_url = "https://gegl.org/babl"
[dependencies."Gimp-3.0"]
name = "Gimp"
description = "GIMP Library"
docs_url = "https://developer.gimp.org/api/3.0/libgimp/"
[dependencies."GLib-2.0"]
name = "GLib"
description = "C Utility Library"
docs_url = "https://developer.gnome.org/glib/stable"
[dependencies."GObject-2.0"]
name = "GObject"
description = "The base type system library"
docs_url = "https://developer.gnome.org/gobject/stable"
[dependencies."GdkPixbuf-2.0"]
name = "GdkPixbuf"
description = "Image loading and scaling"
docs_url = "https://docs.gtk.org/gdk-pixbuf/"
[dependencies."Gegl-0.4"]
name = "Gegl"
description = "Generic Graphics Library"
docs_url = "https://gegl.org/"
[dependencies."Gio-2.0"]
name = "Gio"
description = "GObject interfaces and objects"
docs_url = "https://developer.gnome.org/gio/stable"
[dependencies."Gtk-3.0"]
name = "Gtk"
description = "The GTK toolkit"
docs_url = "https://developer.gnome.org/gtk3/stable"
[dependencies."cairo-1.0"]
name = "cairo"
description = "A 2D graphics library with support for multiple output devices"
docs_url = "https://www.cairographics.org/manual/"
[theme]
name = "basic"
show_index_summary = true
show_class_hierarchy = true
[source-location]
base_url = "https://gitlab.gnome.org/GNOME/gimp/-/blob/master/"
[extra]
content_files = [
'widget-gallery.md',
]
content_images = [
'images/browser.png',
'images/busy-box.png',
'images/button.png',
'images/chain-button.png',
'images/color-area.png',
'images/color-button.png',
'images/color-hex-entry.png',
'images/color-notebook.png',
'images/color-profile-combo-box.png',
'images/color-profile-view.png',
'images/color-scale.png',
'images/color-scales.png',
'images/color-select.png',
'images/color-selection.png',
'images/dialog.png',
'images/enum-combo-box.png',
'images/enum-label.png',
'images/file-entry.png',
'images/frame.png',
'images/hint-box.png',
'images/int-combo-box.png',
'images/memsize-entry.png',
'images/number-pair-entry.png',
'images/offset-area.png',
'images/page-selector.png',
'images/path-editor.png',
'images/pick-button.png',
'images/preview-area.png',
'images/ruler.png',
'images/string-combo-box.png',
'images/unit-combo-box.png',
]
# The urlmap is used as base links when an API docs refer to a type or
# function from another library.
urlmap_file = "urlmap.js"

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

View file

@ -0,0 +1,39 @@
# Extra markdown files
gimp_ui_doc_content_files = [
'widget-gallery.md',
]
gimp_ui_doc_toml = configure_file(
input: 'gimp-ui-3.0.toml.in',
output: '@BASENAME@',
configuration: {
'GIMP_VERSION': gimp_version,
'GIMP_LOGO': '../images/gimp-devel-logo.png',
},
)
gimp_ui_docs = custom_target('gimp-ui-docs',
input: libgimpui_gir[0],
output: 'libgimpui-@0@'.format(gimp_api_version),
command: [
gi_docgen,
'generate',
'--quiet',
'--fatal-warnings',
'--config', gimp_ui_doc_toml,
'--output-dir=@OUTPUT@',
'--no-namespace-dir',
'--content-dir=@0@'.format(meson.current_source_dir()),
'--add-include-path=@0@'.format(meson.project_build_root() / 'libgimp'),
'--add-include-path=@0@'.format(get_option('prefix') / 'share' / 'gir-1.0'),
'@INPUT@',
],
depends: libgimp_gir[0],
depend_files: [
gimp_ui_doc_toml,
gimp_ui_doc_content_files,
],
build_by_default: true,
install: true,
install_dir: get_option('datadir') / 'doc' / 'gimp-@0@'.format(gimp_app_version),
)

View file

@ -0,0 +1,12 @@
// A map between namespaces and base URLs for their online gi-docgen documentation
baseURLs = [
[ 'Babl', 'https://developer.gimp.org/api/babl/' ],
[ 'Gegl', 'https://developer.gimp.org/api/gegl/' ],
[ 'GLib', 'https://docs.gtk.org/glib/' ],
[ 'GObject', 'https://docs.gtk.org/gobject/' ],
[ 'Gdk', 'https://docs.gtk.org/gdk3/' ],
[ 'GdkPixbuf', 'https://docs.gtk.org/gdk-pixbuf/' ],
[ 'Gio', 'https://docs.gtk.org/gio/' ],
[ 'Gtk', 'https://docs.gtk.org/gtk3/' ],
[ 'Gimp', 'https://developer.gimp.org/api/3.0/libgimp/' ],
]

View file

@ -0,0 +1,36 @@
Title: Widget gallery
Widget gallery
==============
[![Browser](browser.png)](class.Browser.html)
[![Button](button.png)](class.Button.html)
[![BusyBox](busy-box.png)](class.BusyBox.html)
[![ChainButton](chain-button.png)](class.ChainButton.html)
[![ColorArea](color-area.png)](class.ColorArea.html)
[![ColorButton](color-button.png)](class.ColorButton.html)
[![ColorHexEntry](color-hex-entry.png)](class.ColorHexEntry.html)
[![ColorNotebook](color-notebook.png)](class.ColorNotebook.html)
[![ColorScale](color-scale.png)](class.ColorScale.html)
[![ColorScales](color-scales.png)](class.ColorScales.html)
[![ColorSelect](color-select.png)](class.ColorSelect.html)
[![ColorSelection](color-selection.png)](class.ColorSelection.html)
[![ColorProfileComboBox](color-profile-combo-box.png)](class.ColorProfileComboBox.html)
[![ColorProfileView](color-profile-view.png)](class.ColorProfileView.html)
[![Dialog](dialog.png)](class.Dialog.html)
[![EnumComboBox](enum-combo-box.png)](class.EnumComboBox.html)
[![EnumLabel](enum-label.png)](class.EnumLabel.html)
[![FileEntry](file-entry.png)](class.FileEntry.html)
[![Frame](frame.png)](class.Frame.html)
[![HintBox](hint-box.png)](class.HintBox.html)
[![IntComboBox](int-combo-box.png)](class.IntComboBox.html)
[![MemsizeEntry](memsize-entry.png)](class.MemsizeEntry.html)
[![NumberPairEntry](number-pair-entry.png)](class.NumberPairEntry.html)
[![OffsetArea](offset-area.png)](class.OffsetArea.html)
[![PageSelector](page-selector.png)](class.PageSelector.html)
[![PathEditor](path-editor.png)](class.PathEditor.html)
[![PickButton](pick-button.png)](class.PickButton.html)
[![PreviewArea](preview-area.png)](class.PreviewArea.html)
[![Ruler](ruler.png)](class.Ruler.html)
[![StringComboBox](string-combo-box.png)](class.StringComboBox.html)
[![UnitComboBox](unit-combo-box.png)](class.UnitComboBox.html)

View file

@ -0,0 +1,80 @@
[library]
namespace = "Gimp"
version = "@GIMP_VERSION@"
browse_url = "https://gitlab.gnome.org/GNOME/gimp/"
repository_url = "https://gitlab.gnome.org/GNOME/gimp.git"
website_url = "https://www.gimp.org"
authors = "GIMP contributors"
logo_url = "@GIMP_LOGO_PATH@"
license = "GPL-3.0-or-later"
description = "GIMP library"
dependencies = [
'Babl-0.1',
'GLib-2.0',
'GObject-2.0',
'GdkPixbuf-2.0',
'Gegl-0.4',
'Gio-2.0',
'Gtk-3.0',
'cairo-1.0',
]
devhelp = true
search_index = true
[dependencies."Babl-0.1"]
name = "Babl"
description = "Pixel encoding and color space conversion engine"
docs_url = "https://gegl.org/babl"
[dependencies."GLib-2.0"]
name = "GLib"
description = "C Utility Library"
docs_url = "https://developer.gnome.org/glib/stable"
[dependencies."GObject-2.0"]
name = "GObject"
description = "The base type system library"
docs_url = "https://developer.gnome.org/gobject/stable"
[dependencies."GdkPixbuf-2.0"]
name = "GdkPixbuf"
description = "Image loading and scaling"
docs_url = "https://docs.gtk.org/gdk-pixbuf/"
[dependencies."Gegl-0.4"]
name = "Gegl"
description = "Generic Graphics Library"
docs_url = "https://gegl.org/"
[dependencies."Gio-2.0"]
name = "Gio"
description = "GObject interfaces and objects"
docs_url = "https://developer.gnome.org/gio/stable"
[dependencies."Gtk-3.0"]
name = "Gtk"
description = "The GTK toolkit"
docs_url = "https://developer.gnome.org/gtk3/stable"
[dependencies."cairo-1.0"]
name = "cairo"
description = "A 2D graphics library with support for multiple output devices"
docs_url = "https://www.cairographics.org/manual/"
[dependencies."Pango-1.0"]
name = "Pango"
description = "Internationalized text layout and rendering"
docs_url = "https://docs.gtk.org/Pango/"
[theme]
name = "basic"
show_index_summary = true
show_class_hierarchy = true
[source-location]
base_url = "https://gitlab.gnome.org/GNOME/gimp/-/blob/master/"
[extra]
content_files = [
]
urlmap_file = "urlmap.js"

View file

@ -0,0 +1,36 @@
# Extra markdown files
gimp_doc_content_files = [
]
gimp_doc_toml = configure_file(
input: 'gimp-3.0.toml.in',
output: '@BASENAME@',
configuration: {
'GIMP_VERSION': gimp_version,
'GIMP_LOGO_PATH': '../images/gimp-devel-logo.png',
},
)
gimp_docs = custom_target('gimp-docs',
input: libgimp_gir[0],
output: 'libgimp-@0@'.format(gimp_api_version),
command: [
gi_docgen,
'generate',
'--quiet',
'--fatal-warnings',
'--config', gimp_doc_toml,
'--output-dir=@OUTPUT@',
'--no-namespace-dir',
'--content-dir=@0@'.format(meson.current_source_dir()),
'--add-include-path=@0@'.format(get_option('prefix') / 'share' / 'gir-1.0'),
'@INPUT@',
],
depend_files: [
gimp_doc_toml,
gimp_doc_content_files,
],
build_by_default: true,
install: true,
install_dir: get_option('datadir') / 'doc' / 'gimp-@0@'.format(gimp_app_version),
)

View file

@ -0,0 +1,12 @@
// A map between namespaces and base URLs for their online gi-docgen documentation
baseURLs = [
[ 'Babl', 'https://developer.gimp.org/api/babl/' ],
[ 'Gegl', 'https://developer.gimp.org/api/gegl/' ],
[ 'GLib', 'https://docs.gtk.org/glib/' ],
[ 'GObject', 'https://docs.gtk.org/gobject/' ],
[ 'Gdk', 'https://docs.gtk.org/gdk3/' ],
[ 'GdkPixbuf', 'https://docs.gtk.org/gdk-pixbuf/' ],
[ 'Gio', 'https://docs.gtk.org/gio/' ],
[ 'Gtk', 'https://docs.gtk.org/gtk3/' ],
[ 'Pango', 'https://docs.gtk.org/Pango/' ],
]

View file

@ -0,0 +1,2 @@
subdir('gimp')
subdir('gimp-ui')

148
devel-docs/tagging.txt Normal file
View file

@ -0,0 +1,148 @@
=============================================================
How does resource tagging in Gimp work?
=============================================================
GimpTagged
Tagging is not limited to a concrete class hierarchy, but any class
implementing the GimpTagged interface can be tagged. In addition to
methods for adding/removing/enumerating tags it also requires
GimpTagged objects to identify themselves:
* gimp_tagged_get_identifier: used to get a unique identifier of a
GimpTagged object. For objects which are stored in a file it will
usually be a filename.
* gimp_tagged_get_checksum: the identifier mentioned above has the problem
that it can change during sessions (for example, user moves or renames
a resource file). Therefore, there needs to be a way to get another
identifier from the data of the tagged object, so that tags stored between
session can be remapped properly.
GimpTag
Tags are represented by a GimpTag object. There are no limitations for
tag names except that they cannot contain a selected set of terminal
punctuation characters (used to separate tags), leading or trailing
whitespace and cannot begin with a reserved prefix for internal tags
('gimp:'). These conditions are enforced when creating a tag object from a
tag string. The only reason for tag creation to fail is if there are
no characters left after trying to fix a tag according to the
rules above. Tag names are displayed as the user typed them (case
sensitive), but tag comparison is done case-insensitively.
Tags are immutable, i.e. when a tag is created with one name string, it
cannot be changed, but a new tag has to be created instead.
There are methods provided for convenient use with glib, a comparison
function which can be used to sort tag lists and functions for storing
tags in a GHashTable.
GimpTagCache
Between sessions, tags assigned to objects are stored in a cache
file. The cache file is a simple XML file, which lists all resources and
tags which are added to them. Resources which have no tags assigned
are listed here too, so that when we check the cache we know that they
have no tags assigned instead of trying to find out if the resource file
has been renamed.
When the session ends, a list of all resources and their tags
is constructed. Resources which were not loaded during this session,
but had tags assigned are also added to the list (they are saved
because they could be useful in the next session, for example, when
a temporarily disconnected network directory is reconnected). The list
is then written to a tag cache file in the user's home directory.
When the session starts, the previously saved resource and tag mapping has to
be loaded and assigned to GimpTagged objects. First the tag cache is
loaded from file, and then containers are added (GimpContainer objects
which contain items implementing the GimpTagged interface). After that,
loaded resources are assigned tags:
If a resource identifier matches an identifier in the cache,
corresponding tags are assigned to the GimpTagged object.
Else, if the identifier is not found in the tag cache,
an attempt is made to check if the resource file has been
moved/renamed. In such case the checksum is used to match the
GimpTagged object with all of the records in the tag cache.
If a match is found,
the identifier is updated in the tag cache.
Otherwise,
the loaded GimpTagged object is considered to be a newly
added resource.
GimpFilteredContainer
A GimpFilteredContainer is a "view" (representation) of a
GimpContainer. It is related to tagging in that it can be used to
filter a GimpContainer to contain only GimpTagged objects which have
certain tags assigned. It is automatically updated with any changes in
the GimpContainer it wraps. However, items should not be added or removed
from this container manually as changes do not affect the original
container and would be lost when the GimpFilteredContainer is
updated. Instead, the contents should be changed by setting a tag list
which would be used to filter GimpTagged objects containing all of the
given GimpTags.
GimpFilteredContainer can use any GimpContainer as a source
container. Therefore, it is possible to use the decorator design pattern
to implement additional container views, such as a view combining items
from multiple containers.
GimpTagEntry widget
The GimpTagEntry widget extends GtkEntry and is used to either assign or
query tags depending on the selected mode. The widget support various
usability features:
* Jellybeans: When a tag is entered and confirmed by either separator,
pressing return or otherwise, it becomes a jellybean, i.e. a single
unit, not a bunch of characters. Navigating in a GimpTagEntry,
deleting tags, etc. can be performed much faster. However, while a tag
is just being entered (not yet confirmed), all actions operate on
characters as usual.
* Custom auto completion is implemented in the GimpTagEntry widget which
allows to complete tags in the middle of a tag list, doesn't offer
already completed tags, tab cycles all possible completions, etc.
* If the GimpTagEntry is empty and unused it displays a description for
the user regarding its purpose.
When operating in tag assignment mode, tags are assigned only when
the user hits the return key.
When operating in tag query mode, the given GimpFilteredContainer is
filtered as the user types. The GimpTagEntry also remembers recently used
configurations, which can be cycled using up and down arrow keys.
GimpComboTagEntry widget
The GimpComboTagEntry widget extends GimpTagEntry and adds the ability to pick
tags from a menu-like list (using the GimpTagPopup widget).
GimpTagPopup widget
The GimpTagPopup widget is used as a tag list menu from the GimpComboTagEntry
widget. It is not designed to be used with any other widget.
GimpTagPopup has many visual and behavioral similarities to GtkMenu.
In particular, it uses menu-like scrolling.
GimpTagPopup implements various usability features, some of which are:
* Tags which would result in an empty selection of resources are made
insensitive.
* Closing either with the keyboard or by clicking outside the popup area.
* Underlining of highlighted (hovered) tags.

View file

@ -0,0 +1,57 @@
GIMP UI Framework
=================
This document describes how the GIMP UI framework functions and is
implemented. Here, "UI framework" refers to the system that saves the
UI layout between GIMP sessions, i.e. how docks, dockable dialogs etc
are setup.
Key Classes
-----------
GimpDockable - Represents a dockable dialog.
GimpDockbook - A GtkNotebook of GimpDockables
GimpDock - A columns of GimpDockbooks
GimpToolbox - Subclasses GimpDock, contains the toolbox.
Dockables are added at the bottom
GimpMenuDock - Subclasses GimpDock, contains dockables, should
probably be merged with GimpDock. The name
contains "menu" from the time when it hosted the
Image Selection Menu that is now in the
GimpDockWindow
GimpDockColumns - A set of GimpDocks arranged side by side.
GimpDockWindow - A toplevel window containing a GimpDockColumns.
GimpImageWindow - A toplevel window containing images and one
GimpDockColumns to the left and to the right.
GimpDialogFactory - A factory to create and position toplevel windows
GimpSessionInfo - Contains session info for one toplevel
GimpUIConfigurer - Configures the UI when switching between
single-window and multi-window mode
GimpDialogFactory
-----------------
The GimpDialogFactory can be considered to solve two distinct
problems:
1. Create widgets from text, in particular from text in sessionrc
2. Session manage toplevel windows so their position is remembered
across GIMP sessions
One possible design adjustment would be to have GimpWidgetFactory that
takes care of 1), and then have GimpDialogFactory inherit from
GtkWidgetFactory and implementing 2). GimpWidgetFactory could possibly
use GtkBuilder.
sessionrc
---------
When GIMP starts, the sessionrc file is parsed. This step puts
GimpSessionInfo:s into GimpDialogFactories. Later when dialogs are
created, the dialog factory looks up existing session info entries. If
one exists, it uses the session info to set e.g. the position of the
created dialog. If it doesn't exist, it creates a new session info
object for the dialog. When GIMP exists, the current session infos are
then written back to sessionrc.

73
devel-docs/undo.txt Normal file
View file

@ -0,0 +1,73 @@
A quick overview of the undo system
-----------------------------------
Actions on the image by the user are pushed onto an undo stack. Each
action object includes all the information needed to undo or redo an
operation, plus an UndoType. The type can be converted to text to
show to the user. Actions may be run forwards (UndoState == REDO) or
backwards (UndoState == UNDO). As the action is run, it swaps the
image's current state and the recorded state. A run action is moved
from the undo stack to the redo stack (or vice-versa if UndoState ==
REDO). Pushing something onto the undo stack causes the redo stack to
be cleared, since the actions on the redo stack may depend on the
image being in a particular state (eg consider: layer add, rename,
undo rename, layer delete. If the redo stack weren't cleared on undo,
then there would still be a "rename" operation on the redo stack which
could be run on a non-existent layer. Bad news.)
Undo groups
-----------
In order to group many basic operations together into a more useful
whole, code can push group start and end markers. A group is treated
as a single action for the purposes of the undo and redo user
commands. It is legal to nest groups, in which case the outermost
group is the only user-visible one.
Groups boundaries used to be implemented by pushing a NULL pointer on
the undo (or redo) stack. Now they are a special action which has the
"group_boundary" bit set. This allows the group boundaries to include
the undo type associated with the whole group. The individual actions
need to preserve their own undo type since the undo_free_* functions
sometimes need to know which action is being freed.
Undo events
-----------
Images emit UNDO_EVENT signals, to say that the user has performed an
undo or redo action on that image. This allows interested parties to
track image mutation actions. So far, only the undo history dialog
uses this feature. The other way to discover the undo status of an
image is to use the iterator functions undo_map_over_undo_stack() and
undo_map_over_redo_stack(). These call your function on each action
(or group) on the stack. There is also undo_get_undo_name() and
undo_get_redo_name() to peek at the top items on each stack. This
could be used (eg) to change the undo/redo menu strings to something
more meaningful, but currently lack synchronisation.
Dirtying images
---------------
NOTE about the gimage->dirty counter:
If 0, then the image is clean (ie, copy on disk is the same as the one
in memory).
If positive, then that's the number of dirtying operations done
on the image since the last save.
If negative, then user has hit undo and gone back in time prior
to the saved copy. Hitting redo will eventually come back to
the saved copy.
The image is dirty (ie, needs saving) if counter is non-zero.
If the counter is around 10000, this is due to undo-ing back
before a saved version, then mutating the image (thus destroying
the redo stack). Once this has happened, it's impossible to get
the image back to the state on disk, since the redo info has been
freed. See undo.c for the gorey details.
NEVER CALL gimp_image_dirty() directly!
If your code has just dirtied the image, push an undo instead.
Failing that, push the trivial undo which tells the user the
command is not undoable: undo_push_cantundo() (But really, it would
be best to push a proper undo). If you just dirty the image
without pushing an undo then the dirty count is increased, but
popping that many undo actions won't lead to a clean image.
Austin