summaryrefslogtreecommitdiffstats
path: root/app/core
diff options
context:
space:
mode:
Diffstat (limited to 'app/core')
-rw-r--r--app/core/Makefile.am550
-rw-r--r--app/core/Makefile.in2438
-rw-r--r--app/core/core-enums.c1316
-rw-r--r--app/core/core-enums.h701
-rw-r--r--app/core/core-types.h303
-rw-r--r--app/core/gimp-atomic.c95
-rw-r--r--app/core/gimp-atomic.h32
-rw-r--r--app/core/gimp-batch.c203
-rw-r--r--app/core/gimp-batch.h27
-rw-r--r--app/core/gimp-cairo.c217
-rw-r--r--app/core/gimp-cairo.h51
-rw-r--r--app/core/gimp-contexts.c161
-rw-r--r--app/core/gimp-contexts.h36
-rw-r--r--app/core/gimp-data-factories.c433
-rw-r--r--app/core/gimp-data-factories.h36
-rw-r--r--app/core/gimp-edit.c771
-rw-r--r--app/core/gimp-edit.h61
-rw-r--r--app/core/gimp-filter-history.c160
-rw-r--r--app/core/gimp-filter-history.h35
-rw-r--r--app/core/gimp-gradients.c174
-rw-r--r--app/core/gimp-gradients.h34
-rw-r--r--app/core/gimp-gui.c585
-rw-r--r--app/core/gimp-gui.h211
-rw-r--r--app/core/gimp-internal-data.c346
-rw-r--r--app/core/gimp-internal-data.h34
-rw-r--r--app/core/gimp-memsize.c341
-rw-r--r--app/core/gimp-memsize.h60
-rw-r--r--app/core/gimp-modules.c227
-rw-r--r--app/core/gimp-modules.h34
-rw-r--r--app/core/gimp-palettes.c143
-rw-r--r--app/core/gimp-palettes.h35
-rw-r--r--app/core/gimp-parallel.cc553
-rw-r--r--app/core/gimp-parallel.h157
-rw-r--r--app/core/gimp-parasites.c170
-rw-r--r--app/core/gimp-parasites.h41
-rw-r--r--app/core/gimp-spawn.c250
-rw-r--r--app/core/gimp-spawn.h34
-rw-r--r--app/core/gimp-tags.c271
-rw-r--r--app/core/gimp-tags.h25
-rw-r--r--app/core/gimp-templates.c213
-rw-r--r--app/core/gimp-templates.h28
-rw-r--r--app/core/gimp-transform-3d-utils.c359
-rw-r--r--app/core/gimp-transform-3d-utils.h95
-rw-r--r--app/core/gimp-transform-resize.c841
-rw-r--r--app/core/gimp-transform-resize.h34
-rw-r--r--app/core/gimp-transform-utils.c1211
-rw-r--r--app/core/gimp-transform-utils.h125
-rw-r--r--app/core/gimp-units.c488
-rw-r--r--app/core/gimp-units.h29
-rw-r--r--app/core/gimp-user-install.c977
-rw-r--r--app/core/gimp-user-install.h39
-rw-r--r--app/core/gimp-utils.c1098
-rw-r--r--app/core/gimp-utils.h116
-rw-r--r--app/core/gimp.c1224
-rw-r--r--app/core/gimp.h248
-rw-r--r--app/core/gimpasync.c752
-rw-r--r--app/core/gimpasync.h95
-rw-r--r--app/core/gimpasyncset.c348
-rw-r--r--app/core/gimpasyncset.h61
-rw-r--r--app/core/gimpauxitem.c147
-rw-r--r--app/core/gimpauxitem.h56
-rw-r--r--app/core/gimpauxitemundo.c138
-rw-r--r--app/core/gimpauxitemundo.h52
-rw-r--r--app/core/gimpbacktrace-backend.h34
-rw-r--r--app/core/gimpbacktrace-linux.c725
-rw-r--r--app/core/gimpbacktrace-none.c126
-rw-r--r--app/core/gimpbacktrace-windows.c706
-rw-r--r--app/core/gimpbacktrace.h70
-rw-r--r--app/core/gimpbezierdesc.c202
-rw-r--r--app/core/gimpbezierdesc.h47
-rw-r--r--app/core/gimpboundary.c1016
-rw-r--r--app/core/gimpboundary.h68
-rw-r--r--app/core/gimpbrush-boundary.c130
-rw-r--r--app/core/gimpbrush-boundary.h32
-rw-r--r--app/core/gimpbrush-header.h49
-rw-r--r--app/core/gimpbrush-load.c1213
-rw-r--r--app/core/gimpbrush-load.h43
-rw-r--r--app/core/gimpbrush-mipmap.cc514
-rw-r--r--app/core/gimpbrush-mipmap.h38
-rw-r--r--app/core/gimpbrush-private.h47
-rw-r--r--app/core/gimpbrush-save.c107
-rw-r--r--app/core/gimpbrush-save.h28
-rw-r--r--app/core/gimpbrush-transform.cc1043
-rw-r--r--app/core/gimpbrush-transform.h59
-rw-r--r--app/core/gimpbrush.c942
-rw-r--r--app/core/gimpbrush.h152
-rw-r--r--app/core/gimpbrushcache.c299
-rw-r--r--app/core/gimpbrushcache.h83
-rw-r--r--app/core/gimpbrushclipboard.c298
-rw-r--r--app/core/gimpbrushclipboard.h58
-rw-r--r--app/core/gimpbrushgenerated-load.c284
-rw-r--r--app/core/gimpbrushgenerated-load.h33
-rw-r--r--app/core/gimpbrushgenerated-save.c119
-rw-r--r--app/core/gimpbrushgenerated-save.h30
-rw-r--r--app/core/gimpbrushgenerated.c875
-rw-r--r--app/core/gimpbrushgenerated.h88
-rw-r--r--app/core/gimpbrushpipe-load.c163
-rw-r--r--app/core/gimpbrushpipe-load.h32
-rw-r--r--app/core/gimpbrushpipe-save.c59
-rw-r--r--app/core/gimpbrushpipe-save.h28
-rw-r--r--app/core/gimpbrushpipe.c420
-rw-r--r--app/core/gimpbrushpipe.h80
-rw-r--r--app/core/gimpbuffer.c541
-rw-r--r--app/core/gimpbuffer.h90
-rw-r--r--app/core/gimpcancelable.c72
-rw-r--r--app/core/gimpcancelable.h47
-rw-r--r--app/core/gimpchannel-combine.c471
-rw-r--r--app/core/gimpchannel-combine.h56
-rw-r--r--app/core/gimpchannel-select.c605
-rw-r--r--app/core/gimpchannel-select.h160
-rw-r--r--app/core/gimpchannel.c1956
-rw-r--r--app/core/gimpchannel.h216
-rw-r--r--app/core/gimpchannelpropundo.c108
-rw-r--r--app/core/gimpchannelpropundo.h52
-rw-r--r--app/core/gimpchannelundo.c214
-rw-r--r--app/core/gimpchannelundo.h54
-rw-r--r--app/core/gimpchunkiterator.c555
-rw-r--r--app/core/gimpchunkiterator.h44
-rw-r--r--app/core/gimpcontainer-filter.c174
-rw-r--r--app/core/gimpcontainer-filter.h38
-rw-r--r--app/core/gimpcontainer.c1167
-rw-r--r--app/core/gimpcontainer.h150
-rw-r--r--app/core/gimpcontext.c3828
-rw-r--r--app/core/gimpcontext.h360
-rw-r--r--app/core/gimpcoords-interpolate.c371
-rw-r--r--app/core/gimpcoords-interpolate.h41
-rw-r--r--app/core/gimpcoords.c248
-rw-r--r--app/core/gimpcoords.h57
-rw-r--r--app/core/gimpcurve-load.c54
-rw-r--r--app/core/gimpcurve-load.h30
-rw-r--r--app/core/gimpcurve-map.c253
-rw-r--r--app/core/gimpcurve-map.h34
-rw-r--r--app/core/gimpcurve-save.c44
-rw-r--r--app/core/gimpcurve-save.h28
-rw-r--r--app/core/gimpcurve.c1289
-rw-r--r--app/core/gimpcurve.h124
-rw-r--r--app/core/gimpdashpattern.c355
-rw-r--r--app/core/gimpdashpattern.h53
-rw-r--r--app/core/gimpdata.c1245
-rw-r--r--app/core/gimpdata.h133
-rw-r--r--app/core/gimpdatafactory.c945
-rw-r--r--app/core/gimpdatafactory.h125
-rw-r--r--app/core/gimpdataloaderfactory.c562
-rw-r--r--app/core/gimpdataloaderfactory.h77
-rw-r--r--app/core/gimpdocumentlist.c106
-rw-r--r--app/core/gimpdocumentlist.h54
-rw-r--r--app/core/gimpdrawable-bucket-fill.c499
-rw-r--r--app/core/gimpdrawable-bucket-fill.h59
-rw-r--r--app/core/gimpdrawable-combine.c144
-rw-r--r--app/core/gimpdrawable-combine.h39
-rw-r--r--app/core/gimpdrawable-edit.c231
-rw-r--r--app/core/gimpdrawable-edit.h29
-rw-r--r--app/core/gimpdrawable-equalize.c71
-rw-r--r--app/core/gimpdrawable-equalize.h26
-rw-r--r--app/core/gimpdrawable-fill.c279
-rw-r--r--app/core/gimpdrawable-fill.h60
-rw-r--r--app/core/gimpdrawable-filters.c343
-rw-r--r--app/core/gimpdrawable-filters.h46
-rw-r--r--app/core/gimpdrawable-floating-selection.c512
-rw-r--r--app/core/gimpdrawable-floating-selection.h31
-rw-r--r--app/core/gimpdrawable-foreground-extract.c150
-rw-r--r--app/core/gimpdrawable-foreground-extract.h31
-rw-r--r--app/core/gimpdrawable-gradient.c313
-rw-r--r--app/core/gimpdrawable-gradient.h57
-rw-r--r--app/core/gimpdrawable-histogram.c258
-rw-r--r--app/core/gimpdrawable-histogram.h32
-rw-r--r--app/core/gimpdrawable-levels.c77
-rw-r--r--app/core/gimpdrawable-levels.h26
-rw-r--r--app/core/gimpdrawable-offset.c83
-rw-r--r--app/core/gimpdrawable-offset.h30
-rw-r--r--app/core/gimpdrawable-operation.c128
-rw-r--r--app/core/gimpdrawable-operation.h43
-rw-r--r--app/core/gimpdrawable-preview.c492
-rw-r--r--app/core/gimpdrawable-preview.h63
-rw-r--r--app/core/gimpdrawable-private.h44
-rw-r--r--app/core/gimpdrawable-shadow.c110
-rw-r--r--app/core/gimpdrawable-shadow.h32
-rw-r--r--app/core/gimpdrawable-stroke.c161
-rw-r--r--app/core/gimpdrawable-stroke.h45
-rw-r--r--app/core/gimpdrawable-transform.c1070
-rw-r--r--app/core/gimpdrawable-transform.h94
-rw-r--r--app/core/gimpdrawable.c1916
-rw-r--r--app/core/gimpdrawable.h227
-rw-r--r--app/core/gimpdrawablefilter.c1364
-rw-r--r--app/core/gimpdrawablefilter.h108
-rw-r--r--app/core/gimpdrawablemodundo.c216
-rw-r--r--app/core/gimpdrawablemodundo.h55
-rw-r--r--app/core/gimpdrawablestack.c224
-rw-r--r--app/core/gimpdrawablestack.h66
-rw-r--r--app/core/gimpdrawableundo.c207
-rw-r--r--app/core/gimpdrawableundo.h54
-rw-r--r--app/core/gimpdynamics-load.c55
-rw-r--r--app/core/gimpdynamics-load.h31
-rw-r--r--app/core/gimpdynamics-save.c44
-rw-r--r--app/core/gimpdynamics-save.h28
-rw-r--r--app/core/gimpdynamics.c653
-rw-r--r--app/core/gimpdynamics.h77
-rw-r--r--app/core/gimpdynamicsoutput.c767
-rw-r--r--app/core/gimpdynamicsoutput.h68
-rw-r--r--app/core/gimperror.c36
-rw-r--r--app/core/gimperror.h33
-rw-r--r--app/core/gimpfilloptions.c548
-rw-r--r--app/core/gimpfilloptions.h95
-rw-r--r--app/core/gimpfilter.c315
-rw-r--r--app/core/gimpfilter.h73
-rw-r--r--app/core/gimpfilteredcontainer.c373
-rw-r--r--app/core/gimpfilteredcontainer.h68
-rw-r--r--app/core/gimpfilterstack.c350
-rw-r--r--app/core/gimpfilterstack.h55
-rw-r--r--app/core/gimpfloatingselectionundo.c135
-rw-r--r--app/core/gimpfloatingselectionundo.h52
-rw-r--r--app/core/gimpgradient-load.c575
-rw-r--r--app/core/gimpgradient-load.h36
-rw-r--r--app/core/gimpgradient-save.c221
-rw-r--r--app/core/gimpgradient-save.h32
-rw-r--r--app/core/gimpgradient.c2297
-rw-r--r--app/core/gimpgradient.h296
-rw-r--r--app/core/gimpgrid.c359
-rw-r--r--app/core/gimpgrid.h81
-rw-r--r--app/core/gimpgrouplayer.c2297
-rw-r--r--app/core/gimpgrouplayer.h78
-rw-r--r--app/core/gimpgrouplayerundo.c253
-rw-r--r--app/core/gimpgrouplayerundo.h57
-rw-r--r--app/core/gimpguide.c242
-rw-r--r--app/core/gimpguide.h75
-rw-r--r--app/core/gimpguideundo.c116
-rw-r--r--app/core/gimpguideundo.h53
-rw-r--r--app/core/gimphistogram.c1261
-rw-r--r--app/core/gimphistogram.h105
-rw-r--r--app/core/gimpidtable.c227
-rw-r--r--app/core/gimpidtable.h68
-rw-r--r--app/core/gimpimage-arrange.c388
-rw-r--r--app/core/gimpimage-arrange.h29
-rw-r--r--app/core/gimpimage-color-profile.c822
-rw-r--r--app/core/gimpimage-color-profile.h113
-rw-r--r--app/core/gimpimage-colormap.c362
-rw-r--r--app/core/gimpimage-colormap.h55
-rw-r--r--app/core/gimpimage-convert-data.h143
-rw-r--r--app/core/gimpimage-convert-fsdither.h559
-rw-r--r--app/core/gimpimage-convert-indexed.c4567
-rw-r--r--app/core/gimpimage-convert-indexed.h41
-rw-r--r--app/core/gimpimage-convert-precision.c304
-rw-r--r--app/core/gimpimage-convert-precision.h36
-rw-r--r--app/core/gimpimage-convert-type.c162
-rw-r--r--app/core/gimpimage-convert-type.h29
-rw-r--r--app/core/gimpimage-crop.c234
-rw-r--r--app/core/gimpimage-crop.h32
-rw-r--r--app/core/gimpimage-duplicate.c535
-rw-r--r--app/core/gimpimage-duplicate.h25
-rw-r--r--app/core/gimpimage-flip.c276
-rw-r--r--app/core/gimpimage-flip.h34
-rw-r--r--app/core/gimpimage-grid.c67
-rw-r--r--app/core/gimpimage-grid.h31
-rw-r--r--app/core/gimpimage-guides.c216
-rw-r--r--app/core/gimpimage-guides.h54
-rw-r--r--app/core/gimpimage-item-list.c401
-rw-r--r--app/core/gimpimage-item-list.h63
-rw-r--r--app/core/gimpimage-merge.c745
-rw-r--r--app/core/gimpimage-merge.h46
-rw-r--r--app/core/gimpimage-metadata.c184
-rw-r--r--app/core/gimpimage-metadata.h33
-rw-r--r--app/core/gimpimage-new.c393
-rw-r--r--app/core/gimpimage-new.h42
-rw-r--r--app/core/gimpimage-pick-color.c147
-rw-r--r--app/core/gimpimage-pick-color.h35
-rw-r--r--app/core/gimpimage-pick-item.c381
-rw-r--r--app/core/gimpimage-pick-item.h57
-rw-r--r--app/core/gimpimage-preview.c214
-rw-r--r--app/core/gimpimage-preview.h51
-rw-r--r--app/core/gimpimage-private.h149
-rw-r--r--app/core/gimpimage-quick-mask.c212
-rw-r--r--app/core/gimpimage-quick-mask.h43
-rw-r--r--app/core/gimpimage-resize.c327
-rw-r--r--app/core/gimpimage-resize.h53
-rw-r--r--app/core/gimpimage-rotate.c377
-rw-r--r--app/core/gimpimage-rotate.h28
-rw-r--r--app/core/gimpimage-sample-points.c213
-rw-r--r--app/core/gimpimage-sample-points.h60
-rw-r--r--app/core/gimpimage-scale.c260
-rw-r--r--app/core/gimpimage-scale.h36
-rw-r--r--app/core/gimpimage-snap.c719
-rw-r--r--app/core/gimpimage-snap.h63
-rw-r--r--app/core/gimpimage-symmetry.c189
-rw-r--r--app/core/gimpimage-symmetry.h40
-rw-r--r--app/core/gimpimage-transform.c338
-rw-r--r--app/core/gimpimage-transform.h34
-rw-r--r--app/core/gimpimage-undo-push.c1060
-rw-r--r--app/core/gimpimage-undo-push.h256
-rw-r--r--app/core/gimpimage-undo.c695
-rw-r--r--app/core/gimpimage-undo.h57
-rw-r--r--app/core/gimpimage.c5191
-rw-r--r--app/core/gimpimage.h463
-rw-r--r--app/core/gimpimagefile.c1078
-rw-r--r--app/core/gimpimagefile.h90
-rw-r--r--app/core/gimpimageproxy.c877
-rw-r--r--app/core/gimpimageproxy.h65
-rw-r--r--app/core/gimpimageundo.c535
-rw-r--r--app/core/gimpimageundo.h69
-rw-r--r--app/core/gimpitem-exclusive.c276
-rw-r--r--app/core/gimpitem-exclusive.h31
-rw-r--r--app/core/gimpitem-linked.c187
-rw-r--r--app/core/gimpitem-linked.h48
-rw-r--r--app/core/gimpitem-preview.c133
-rw-r--r--app/core/gimpitem-preview.h40
-rw-r--r--app/core/gimpitem.c2689
-rw-r--r--app/core/gimpitem.h405
-rw-r--r--app/core/gimpitempropundo.c358
-rw-r--r--app/core/gimpitempropundo.h63
-rw-r--r--app/core/gimpitemstack.c348
-rw-r--r--app/core/gimpitemstack.h66
-rw-r--r--app/core/gimpitemtree.c714
-rw-r--r--app/core/gimpitemtree.h89
-rw-r--r--app/core/gimpitemundo.c139
-rw-r--r--app/core/gimpitemundo.h52
-rw-r--r--app/core/gimplayer-floating-selection.c332
-rw-r--r--app/core/gimplayer-floating-selection.h33
-rw-r--r--app/core/gimplayer-new.c253
-rw-r--r--app/core/gimplayer-new.h51
-rw-r--r--app/core/gimplayer.c2943
-rw-r--r--app/core/gimplayer.h243
-rw-r--r--app/core/gimplayermask.c297
-rw-r--r--app/core/gimplayermask.h61
-rw-r--r--app/core/gimplayermaskpropundo.c122
-rw-r--r--app/core/gimplayermaskpropundo.h53
-rw-r--r--app/core/gimplayermaskundo.c195
-rw-r--r--app/core/gimplayermaskundo.h52
-rw-r--r--app/core/gimplayerpropundo.c157
-rw-r--r--app/core/gimplayerpropundo.h57
-rw-r--r--app/core/gimplayerstack.c243
-rw-r--r--app/core/gimplayerstack.h51
-rw-r--r--app/core/gimplayerundo.c212
-rw-r--r--app/core/gimplayerundo.h54
-rw-r--r--app/core/gimplineart.c2979
-rw-r--r--app/core/gimplineart.h73
-rw-r--r--app/core/gimplist.c691
-rw-r--r--app/core/gimplist.h70
-rw-r--r--app/core/gimpmarshal.c1901
-rw-r--r--app/core/gimpmarshal.h378
-rw-r--r--app/core/gimpmarshal.list74
-rw-r--r--app/core/gimpmaskundo.c292
-rw-r--r--app/core/gimpmaskundo.h58
-rw-r--r--app/core/gimpmybrush-load.c153
-rw-r--r--app/core/gimpmybrush-load.h33
-rw-r--r--app/core/gimpmybrush-private.h35
-rw-r--r--app/core/gimpmybrush.c281
-rw-r--r--app/core/gimpmybrush.h66
-rw-r--r--app/core/gimpobject.c512
-rw-r--r--app/core/gimpobject.h74
-rw-r--r--app/core/gimpobjectqueue.c198
-rw-r--r--app/core/gimpobjectqueue.h66
-rw-r--r--app/core/gimppaintinfo.c142
-rw-r--r--app/core/gimppaintinfo.h69
-rw-r--r--app/core/gimppalette-import.c566
-rw-r--r--app/core/gimppalette-import.h48
-rw-r--r--app/core/gimppalette-load.c702
-rw-r--r--app/core/gimppalette-load.h66
-rw-r--r--app/core/gimppalette-save.c77
-rw-r--r--app/core/gimppalette-save.h28
-rw-r--r--app/core/gimppalette.c721
-rw-r--r--app/core/gimppalette.h103
-rw-r--r--app/core/gimppalettemru.c230
-rw-r--r--app/core/gimppalettemru.h62
-rw-r--r--app/core/gimpparamspecs-desc.c193
-rw-r--r--app/core/gimpparamspecs-desc.h25
-rw-r--r--app/core/gimpparamspecs-duplicate.c269
-rw-r--r--app/core/gimpparamspecs-duplicate.h28
-rw-r--r--app/core/gimpparamspecs.c2925
-rw-r--r--app/core/gimpparamspecs.h904
-rw-r--r--app/core/gimpparasitelist.c453
-rw-r--r--app/core/gimpparasitelist.h69
-rw-r--r--app/core/gimppattern-header.h48
-rw-r--r--app/core/gimppattern-load.c220
-rw-r--r--app/core/gimppattern-load.h35
-rw-r--r--app/core/gimppattern-save.c87
-rw-r--r--app/core/gimppattern-save.h28
-rw-r--r--app/core/gimppattern.c287
-rw-r--r--app/core/gimppattern.h58
-rw-r--r--app/core/gimppatternclipboard.c213
-rw-r--r--app/core/gimppatternclipboard.h56
-rw-r--r--app/core/gimppdbprogress.c408
-rw-r--r--app/core/gimppdbprogress.h66
-rw-r--r--app/core/gimppickable-auto-shrink.c312
-rw-r--r--app/core/gimppickable-auto-shrink.h41
-rw-r--r--app/core/gimppickable-contiguous-region.cc1123
-rw-r--r--app/core/gimppickable-contiguous-region.h43
-rw-r--r--app/core/gimppickable.c378
-rw-r--r--app/core/gimppickable.h110
-rw-r--r--app/core/gimpprogress.c266
-rw-r--r--app/core/gimpprogress.h99
-rw-r--r--app/core/gimpprojectable.c262
-rw-r--r--app/core/gimpprojectable.h90
-rw-r--r--app/core/gimpprojection.c1132
-rw-r--r--app/core/gimpprojection.h83
-rw-r--r--app/core/gimpsamplepoint.c215
-rw-r--r--app/core/gimpsamplepoint.h68
-rw-r--r--app/core/gimpsamplepointundo.c121
-rw-r--r--app/core/gimpsamplepointundo.h54
-rw-r--r--app/core/gimpscanconvert.c647
-rw-r--r--app/core/gimpscanconvert.h81
-rw-r--r--app/core/gimpselection.c863
-rw-r--r--app/core/gimpselection.h76
-rw-r--r--app/core/gimpsettings.c195
-rw-r--r--app/core/gimpsettings.h57
-rw-r--r--app/core/gimpstrokeoptions.c638
-rw-r--r--app/core/gimpstrokeoptions.h81
-rw-r--r--app/core/gimpsubprogress.c317
-rw-r--r--app/core/gimpsubprogress.h59
-rw-r--r--app/core/gimpsymmetry-mandala.c574
-rw-r--r--app/core/gimpsymmetry-mandala.h61
-rw-r--r--app/core/gimpsymmetry-mirror.c738
-rw-r--r--app/core/gimpsymmetry-mirror.h62
-rw-r--r--app/core/gimpsymmetry-tiling.c442
-rw-r--r--app/core/gimpsymmetry-tiling.h58
-rw-r--r--app/core/gimpsymmetry.c591
-rw-r--r--app/core/gimpsymmetry.h106
-rw-r--r--app/core/gimptag.c442
-rw-r--r--app/core/gimptag.h80
-rw-r--r--app/core/gimptagcache.c646
-rw-r--r--app/core/gimptagcache.h63
-rw-r--r--app/core/gimptagged.c260
-rw-r--r--app/core/gimptagged.h72
-rw-r--r--app/core/gimptaggedcontainer.c483
-rw-r--r--app/core/gimptaggedcontainer.h67
-rw-r--r--app/core/gimptempbuf.c450
-rw-r--r--app/core/gimptempbuf.h67
-rw-r--r--app/core/gimptemplate.c595
-rw-r--r--app/core/gimptemplate.h97
-rw-r--r--app/core/gimptilehandlerprojectable.c91
-rw-r--r--app/core/gimptilehandlerprojectable.h64
-rw-r--r--app/core/gimptoolgroup.c413
-rw-r--r--app/core/gimptoolgroup.h68
-rw-r--r--app/core/gimptoolinfo.c263
-rw-r--r--app/core/gimptoolinfo.h95
-rw-r--r--app/core/gimptoolitem.c227
-rw-r--r--app/core/gimptoolitem.h70
-rw-r--r--app/core/gimptooloptions.c378
-rw-r--r--app/core/gimptooloptions.h73
-rw-r--r--app/core/gimptoolpreset-load.c71
-rw-r--r--app/core/gimptoolpreset-load.h31
-rw-r--r--app/core/gimptoolpreset-save.c44
-rw-r--r--app/core/gimptoolpreset-save.h28
-rw-r--r--app/core/gimptoolpreset.c705
-rw-r--r--app/core/gimptoolpreset.h67
-rw-r--r--app/core/gimptreehandler.c238
-rw-r--r--app/core/gimptreehandler.h64
-rw-r--r--app/core/gimptreeproxy.c634
-rw-r--r--app/core/gimptreeproxy.h66
-rw-r--r--app/core/gimptriviallycancelablewaitable.c92
-rw-r--r--app/core/gimptriviallycancelablewaitable.h57
-rw-r--r--app/core/gimpuncancelablewaitable.c137
-rw-r--r--app/core/gimpuncancelablewaitable.h54
-rw-r--r--app/core/gimpundo.c585
-rw-r--r--app/core/gimpundo.h99
-rw-r--r--app/core/gimpundostack.c208
-rw-r--r--app/core/gimpundostack.h64
-rw-r--r--app/core/gimpunit.c305
-rw-r--r--app/core/gimpunit.h61
-rw-r--r--app/core/gimpviewable.c1430
-rw-r--r--app/core/gimpviewable.h201
-rw-r--r--app/core/gimpwaitable.c118
-rw-r--r--app/core/gimpwaitable.h55
461 files changed, 144552 insertions, 0 deletions
diff --git a/app/core/Makefile.am b/app/core/Makefile.am
new file mode 100644
index 0000000..c25f302
--- /dev/null
+++ b/app/core/Makefile.am
@@ -0,0 +1,550 @@
+## Process this file with automake to produce Makefile.in
+
+if PLATFORM_OSX
+xobjective_c = "-xobjective-c"
+xobjective_cxx = "-xobjective-c++"
+xnone = "-xnone"
+endif
+
+AM_CPPFLAGS = \
+ -DGIMPDIR=\""$(gimpdir)"\" \
+ -DGIMP_APP_VERSION=\"$(GIMP_APP_VERSION)\" \
+ -DGIMP_USER_VERSION=\"$(GIMP_USER_VERSION)\" \
+ -DG_LOG_DOMAIN=\"Gimp-Core\" \
+ -I$(top_builddir) \
+ -I$(top_srcdir) \
+ -I$(top_builddir)/app \
+ -I$(top_srcdir)/app \
+ $(CAIRO_CFLAGS) \
+ $(GEGL_CFLAGS) \
+ $(GDK_PIXBUF_CFLAGS) \
+ $(LIBMYPAINT_CFLAGS) \
+ $(MYPAINT_BRUSHES_CFLAGS) \
+ $(GEXIV2_CFLAGS) \
+ $(LIBUNWIND_CFLAGS) \
+ -I$(includedir)
+
+AM_CFLAGS = \
+ $(xobjective_c)
+
+AM_CXXFLAGS = \
+ $(xobjective_cxx)
+
+AM_LDFLAGS = \
+ $(xnone)
+
+noinst_LIBRARIES = libappcore.a
+
+libappcore_a_sources = \
+ core-enums.h \
+ core-types.h \
+ gimp.c \
+ gimp.h \
+ gimp-atomic.c \
+ gimp-atomic.h \
+ gimp-batch.c \
+ gimp-batch.h \
+ gimp-cairo.c \
+ gimp-cairo.h \
+ gimp-contexts.c \
+ gimp-contexts.h \
+ gimp-data-factories.c \
+ gimp-data-factories.h \
+ gimp-edit.c \
+ gimp-edit.h \
+ gimp-filter-history.c \
+ gimp-filter-history.h \
+ gimp-gradients.c \
+ gimp-gradients.h \
+ gimp-gui.c \
+ gimp-gui.h \
+ gimp-internal-data.c \
+ gimp-internal-data.h \
+ gimp-memsize.c \
+ gimp-memsize.h \
+ gimp-modules.c \
+ gimp-modules.h \
+ gimp-palettes.c \
+ gimp-palettes.h \
+ gimp-parallel.cc \
+ gimp-parallel.h \
+ gimp-parasites.c \
+ gimp-parasites.h \
+ gimp-spawn.c \
+ gimp-spawn.h \
+ gimp-tags.c \
+ gimp-tags.h \
+ gimp-templates.c \
+ gimp-templates.h \
+ gimp-transform-resize.c \
+ gimp-transform-resize.h \
+ gimp-transform-3d-utils.c \
+ gimp-transform-3d-utils.h \
+ gimp-transform-utils.c \
+ gimp-transform-utils.h \
+ gimp-units.c \
+ gimp-units.h \
+ gimp-user-install.c \
+ gimp-user-install.h \
+ gimp-utils.c \
+ gimp-utils.h \
+ gimpasync.c \
+ gimpasync.h \
+ gimpasyncset.c \
+ gimpasyncset.h \
+ gimpauxitem.c \
+ gimpauxitem.h \
+ gimpauxitemundo.c \
+ gimpauxitemundo.h \
+ gimpbacktrace.h \
+ gimpbacktrace-backend.h \
+ gimpbacktrace-linux.c \
+ gimpbacktrace-none.c \
+ gimpbacktrace-windows.c \
+ gimpbezierdesc.h \
+ gimpbezierdesc.c \
+ gimpboundary.c \
+ gimpboundary.h \
+ gimpbrush.c \
+ gimpbrush.h \
+ gimpbrush-boundary.c \
+ gimpbrush-boundary.h \
+ gimpbrush-header.h \
+ gimpbrush-load.c \
+ gimpbrush-load.h \
+ gimpbrush-mipmap.cc \
+ gimpbrush-mipmap.h \
+ gimpbrush-private.h \
+ gimpbrush-save.c \
+ gimpbrush-save.h \
+ gimpbrush-transform.cc \
+ gimpbrush-transform.h \
+ gimpbrushcache.c \
+ gimpbrushcache.h \
+ gimpbrushclipboard.c \
+ gimpbrushclipboard.h \
+ gimpbrushgenerated.c \
+ gimpbrushgenerated.h \
+ gimpbrushgenerated-load.c \
+ gimpbrushgenerated-load.h \
+ gimpbrushgenerated-save.c \
+ gimpbrushgenerated-save.h \
+ gimpbrushpipe.c \
+ gimpbrushpipe.h \
+ gimpbrushpipe-load.c \
+ gimpbrushpipe-load.h \
+ gimpbrushpipe-save.c \
+ gimpbrushpipe-save.h \
+ gimpbuffer.c \
+ gimpbuffer.h \
+ gimpcancelable.c \
+ gimpcancelable.h \
+ gimpchannel.c \
+ gimpchannel.h \
+ gimpchannel-combine.c \
+ gimpchannel-combine.h \
+ gimpchannel-select.c \
+ gimpchannel-select.h \
+ gimpchannelpropundo.c \
+ gimpchannelpropundo.h \
+ gimpchannelundo.c \
+ gimpchannelundo.h \
+ gimpchunkiterator.c \
+ gimpchunkiterator.h \
+ gimpcontainer.c \
+ gimpcontainer.h \
+ gimpcontainer-filter.c \
+ gimpcontainer-filter.h \
+ gimpcontext.c \
+ gimpcontext.h \
+ gimpcoords.c \
+ gimpcoords.h \
+ gimpcoords-interpolate.c \
+ gimpcoords-interpolate.h \
+ gimpcurve.c \
+ gimpcurve.h \
+ gimpcurve-load.c \
+ gimpcurve-load.h \
+ gimpcurve-map.c \
+ gimpcurve-map.h \
+ gimpcurve-save.c \
+ gimpcurve-save.h \
+ gimpdashpattern.c \
+ gimpdashpattern.h \
+ gimpdata.c \
+ gimpdata.h \
+ gimpdatafactory.c \
+ gimpdatafactory.h \
+ gimpdataloaderfactory.c \
+ gimpdataloaderfactory.h \
+ gimpdocumentlist.c \
+ gimpdocumentlist.h \
+ gimpdrawable.c \
+ gimpdrawable.h \
+ gimpdrawable-bucket-fill.c \
+ gimpdrawable-bucket-fill.h \
+ gimpdrawable-combine.c \
+ gimpdrawable-combine.h \
+ gimpdrawable-edit.c \
+ gimpdrawable-edit.h \
+ gimpdrawable-equalize.c \
+ gimpdrawable-equalize.h \
+ gimpdrawable-fill.c \
+ gimpdrawable-fill.h \
+ gimpdrawable-filters.c \
+ gimpdrawable-filters.h \
+ gimpdrawable-floating-selection.c \
+ gimpdrawable-floating-selection.h \
+ gimpdrawable-foreground-extract.c \
+ gimpdrawable-foreground-extract.h \
+ gimpdrawable-gradient.c \
+ gimpdrawable-gradient.h \
+ gimpdrawable-histogram.c \
+ gimpdrawable-histogram.h \
+ gimpdrawable-levels.c \
+ gimpdrawable-levels.h \
+ gimpdrawable-offset.c \
+ gimpdrawable-offset.h \
+ gimpdrawable-operation.c \
+ gimpdrawable-operation.h \
+ gimpdrawable-preview.c \
+ gimpdrawable-preview.h \
+ gimpdrawable-private.h \
+ gimpdrawable-shadow.c \
+ gimpdrawable-shadow.h \
+ gimpdrawable-stroke.c \
+ gimpdrawable-stroke.h \
+ gimpdrawable-transform.c \
+ gimpdrawable-transform.h \
+ gimpdrawablefilter.c \
+ gimpdrawablefilter.h \
+ gimpdrawablemodundo.c \
+ gimpdrawablemodundo.h \
+ gimpdrawablestack.c \
+ gimpdrawablestack.h \
+ gimpdrawableundo.c \
+ gimpdrawableundo.h \
+ gimpdynamics.c \
+ gimpdynamics.h \
+ gimpdynamics-load.c \
+ gimpdynamics-load.h \
+ gimpdynamics-save.c \
+ gimpdynamics-save.h \
+ gimpdynamicsoutput.c \
+ gimpdynamicsoutput.h \
+ gimperror.c \
+ gimperror.h \
+ gimpfilloptions.c \
+ gimpfilloptions.h \
+ gimpfilter.c \
+ gimpfilter.h \
+ gimpfilteredcontainer.c \
+ gimpfilteredcontainer.h \
+ gimpfilterstack.c \
+ gimpfilterstack.h \
+ gimpfloatingselectionundo.c \
+ gimpfloatingselectionundo.h \
+ gimpgradient.c \
+ gimpgradient.h \
+ gimpgradient-load.c \
+ gimpgradient-load.h \
+ gimpgradient-save.c \
+ gimpgradient-save.h \
+ gimpgrid.c \
+ gimpgrid.h \
+ gimpgrouplayer.c \
+ gimpgrouplayer.h \
+ gimpgrouplayerundo.c \
+ gimpgrouplayerundo.h \
+ gimpguide.c \
+ gimpguide.h \
+ gimpguideundo.c \
+ gimpguideundo.h \
+ gimphistogram.c \
+ gimphistogram.h \
+ gimpidtable.c \
+ gimpidtable.h \
+ gimpimage.c \
+ gimpimage.h \
+ gimpimage-arrange.c \
+ gimpimage-arrange.h \
+ gimpimage-color-profile.c \
+ gimpimage-color-profile.h \
+ gimpimage-colormap.c \
+ gimpimage-colormap.h \
+ gimpimage-convert-indexed.c \
+ gimpimage-convert-indexed.h \
+ gimpimage-convert-fsdither.h \
+ gimpimage-convert-data.h \
+ gimpimage-convert-precision.c \
+ gimpimage-convert-precision.h \
+ gimpimage-convert-type.c \
+ gimpimage-convert-type.h \
+ gimpimage-crop.c \
+ gimpimage-crop.h \
+ gimpimage-duplicate.c \
+ gimpimage-duplicate.h \
+ gimpimage-flip.c \
+ gimpimage-flip.h \
+ gimpimage-grid.h \
+ gimpimage-grid.c \
+ gimpimage-guides.c \
+ gimpimage-guides.h \
+ gimpimage-item-list.c \
+ gimpimage-item-list.h \
+ gimpimage-merge.c \
+ gimpimage-merge.h \
+ gimpimage-metadata.c \
+ gimpimage-metadata.h \
+ gimpimage-new.c \
+ gimpimage-new.h \
+ gimpimage-pick-color.c \
+ gimpimage-pick-color.h \
+ gimpimage-pick-item.c \
+ gimpimage-pick-item.h \
+ gimpimage-preview.c \
+ gimpimage-preview.h \
+ gimpimage-private.h \
+ gimpimage-quick-mask.c \
+ gimpimage-quick-mask.h \
+ gimpimage-resize.c \
+ gimpimage-resize.h \
+ gimpimage-rotate.c \
+ gimpimage-rotate.h \
+ gimpimage-sample-points.c \
+ gimpimage-sample-points.h \
+ gimpimage-scale.c \
+ gimpimage-scale.h \
+ gimpimage-snap.c \
+ gimpimage-snap.h \
+ gimpimage-symmetry.c \
+ gimpimage-symmetry.h \
+ gimpimage-transform.c \
+ gimpimage-transform.h \
+ gimpimage-undo.c \
+ gimpimage-undo.h \
+ gimpimage-undo-push.c \
+ gimpimage-undo-push.h \
+ gimpimageproxy.c \
+ gimpimageproxy.h \
+ gimpimageundo.c \
+ gimpimageundo.h \
+ gimpimagefile.c \
+ gimpimagefile.h \
+ gimpitem.c \
+ gimpitem.h \
+ gimpitem-exclusive.c \
+ gimpitem-exclusive.h \
+ gimpitem-linked.c \
+ gimpitem-linked.h \
+ gimpitem-preview.c \
+ gimpitem-preview.h \
+ gimpitempropundo.c \
+ gimpitempropundo.h \
+ gimpitemstack.c \
+ gimpitemstack.h \
+ gimpitemtree.c \
+ gimpitemtree.h \
+ gimpitemundo.c \
+ gimpitemundo.h \
+ gimplayer.c \
+ gimplayer.h \
+ gimplayer-floating-selection.c \
+ gimplayer-floating-selection.h \
+ gimplayer-new.c \
+ gimplayer-new.h \
+ gimplayermask.c \
+ gimplayermask.h \
+ gimplayermaskpropundo.c \
+ gimplayermaskpropundo.h \
+ gimplayermaskundo.c \
+ gimplayermaskundo.h \
+ gimplayerpropundo.c \
+ gimplayerpropundo.h \
+ gimplayerstack.c \
+ gimplayerstack.h \
+ gimplayerundo.c \
+ gimplayerundo.h \
+ gimplineart.c \
+ gimplineart.h \
+ gimplist.c \
+ gimplist.h \
+ gimpmaskundo.c \
+ gimpmaskundo.h \
+ gimpmybrush.c \
+ gimpmybrush.h \
+ gimpmybrush-load.c \
+ gimpmybrush-load.h \
+ gimpmybrush-private.h \
+ gimpobject.c \
+ gimpobject.h \
+ gimpobjectqueue.c \
+ gimpobjectqueue.h \
+ gimppaintinfo.c \
+ gimppaintinfo.h \
+ gimppattern.c \
+ gimppattern.h \
+ gimppattern-header.h \
+ gimppattern-load.c \
+ gimppattern-load.h \
+ gimppattern-save.c \
+ gimppattern-save.h \
+ gimppatternclipboard.c \
+ gimppatternclipboard.h \
+ gimppalette.c \
+ gimppalette.h \
+ gimppalette-import.c \
+ gimppalette-import.h \
+ gimppalette-load.c \
+ gimppalette-load.h \
+ gimppalette-save.c \
+ gimppalette-save.h \
+ gimppalettemru.c \
+ gimppalettemru.h \
+ gimpparamspecs.c \
+ gimpparamspecs.h \
+ gimpparamspecs-desc.c \
+ gimpparamspecs-desc.h \
+ gimpparamspecs-duplicate.c \
+ gimpparamspecs-duplicate.h \
+ gimpparasitelist.c \
+ gimpparasitelist.h \
+ gimppdbprogress.c \
+ gimppdbprogress.h \
+ gimppickable.c \
+ gimppickable.h \
+ gimppickable-auto-shrink.c \
+ gimppickable-auto-shrink.h \
+ gimppickable-contiguous-region.cc \
+ gimppickable-contiguous-region.h \
+ gimpprogress.c \
+ gimpprogress.h \
+ gimpprojectable.c \
+ gimpprojectable.h \
+ gimpprojection.c \
+ gimpprojection.h \
+ gimpsamplepoint.c \
+ gimpsamplepoint.h \
+ gimpsamplepointundo.c \
+ gimpsamplepointundo.h \
+ gimpscanconvert.c \
+ gimpscanconvert.h \
+ gimpselection.c \
+ gimpselection.h \
+ gimpsettings.c \
+ gimpsettings.h \
+ gimpstrokeoptions.c \
+ gimpstrokeoptions.h \
+ gimpsubprogress.c \
+ gimpsubprogress.h \
+ gimpsymmetry.c \
+ gimpsymmetry.h \
+ gimpsymmetry-mandala.c \
+ gimpsymmetry-mandala.h \
+ gimpsymmetry-mirror.c \
+ gimpsymmetry-mirror.h \
+ gimpsymmetry-tiling.c \
+ gimpsymmetry-tiling.h \
+ gimptag.c \
+ gimptag.h \
+ gimptagcache.c \
+ gimptagcache.h \
+ gimptagged.c \
+ gimptagged.h \
+ gimptaggedcontainer.c \
+ gimptaggedcontainer.h \
+ gimptempbuf.c \
+ gimptempbuf.h \
+ gimptemplate.c \
+ gimptemplate.h \
+ gimptilehandlerprojectable.c \
+ gimptilehandlerprojectable.h \
+ gimptoolgroup.c \
+ gimptoolgroup.h \
+ gimptoolinfo.c \
+ gimptoolinfo.h \
+ gimptoolitem.c \
+ gimptoolitem.h \
+ gimptooloptions.c \
+ gimptooloptions.h \
+ gimptoolpreset.c \
+ gimptoolpreset.h \
+ gimptoolpreset-load.c \
+ gimptoolpreset-load.h \
+ gimptoolpreset-save.c \
+ gimptoolpreset-save.h \
+ gimptreehandler.c \
+ gimptreehandler.h \
+ gimptreeproxy.c \
+ gimptreeproxy.h \
+ gimptriviallycancelablewaitable.c \
+ gimptriviallycancelablewaitable.h \
+ gimpuncancelablewaitable.c \
+ gimpuncancelablewaitable.h \
+ gimpunit.c \
+ gimpunit.h \
+ gimpundo.c \
+ gimpundo.h \
+ gimpundostack.c \
+ gimpundostack.h \
+ gimpviewable.c \
+ gimpviewable.h \
+ gimpwaitable.c \
+ gimpwaitable.h
+
+libappcore_a_built_sources = \
+ core-enums.c \
+ gimpmarshal.c \
+ gimpmarshal.h
+
+libappcore_a_extra_sources = \
+ gimpmarshal.list
+
+libappcore_a_SOURCES = $(libappcore_a_built_sources) $(libappcore_a_sources)
+
+BUILT_SOURCES = \
+ $(libappcore_a_built_sources)
+
+EXTRA_DIST = \
+ $(libappcore_a_extra_sources)
+
+#
+# rules to generate built sources
+#
+# setup autogeneration dependencies
+gen_sources = xgen-gmh xgen-gmc xgen-cec
+CLEANFILES = $(gen_sources)
+
+gimpmarshal.h: $(srcdir)/gimpmarshal.list
+ $(AM_V_GEN) $(GLIB_GENMARSHAL) --prefix=gimp_marshal $(srcdir)/gimpmarshal.list --header >> xgen-gmh \
+ && (cmp -s xgen-gmh $(@F) || cp xgen-gmh $(@F)) \
+ && rm -f xgen-gmh xgen-gmh~
+
+gimpmarshal.c: gimpmarshal.h
+ $(AM_V_GEN) $(GLIB_GENMARSHAL) --prefix=gimp_marshal $(srcdir)/gimpmarshal.list --header --body >> xgen-gmc \
+ && cp xgen-gmc $(@F) \
+ && rm -f xgen-gmc xgen-gmc~
+
+xgen-cec: $(srcdir)/core-enums.h $(GIMP_MKENUMS) Makefile.am
+ $(AM_V_GEN) $(GIMP_MKENUMS) \
+ --fhead "#include \"config.h\"\n#include <gio/gio.h>\n#include \"libgimpbase/gimpbase.h\"\n#include \"core-enums.h\"\n#include \"gimp-intl.h\"" \
+ --fprod "\n/* enumerations from \"@basename@\" */" \
+ --vhead "GType\n@enum_name@_get_type (void)\n{\n static const G@Type@Value values[] =\n {" \
+ --vprod " { @VALUENAME@, \"@VALUENAME@\", \"@valuenick@\" }," \
+ --vtail " { 0, NULL, NULL }\n };\n" \
+ --dhead " static const Gimp@Type@Desc descs[] =\n {" \
+ --dprod " { @VALUENAME@, @valuedesc@, @valuehelp@ },@if ('@valueabbrev@' ne 'NULL')@\n /* Translators: this is an abbreviated version of @valueudesc@.\n Keep it short. */\n { @VALUENAME@, @valueabbrev@, NULL },@endif@" \
+ --dtail " { 0, NULL, NULL }\n };\n\n static GType type = 0;\n\n if (G_UNLIKELY (! type))\n {\n type = g_@type@_register_static (\"@EnumName@\", values);\n gimp_type_set_translation_context (type, \"@enumnick@\");\n gimp_@type@_set_value_descriptions (type, descs);\n }\n\n return type;\n}\n" \
+ $< > $@
+
+# copy the generated enum file back to the source directory only if it's
+# changed; otherwise, only update its timestamp, so that the recipe isn't
+# executed again on the next build, however, allow this to (harmlessly) fail,
+# to support building from a read-only source tree.
+$(srcdir)/core-enums.c: xgen-cec
+ $(AM_V_GEN) if ! cmp -s $< $@; then \
+ cp $< $@; \
+ else \
+ touch $@ 2> /dev/null \
+ || true; \
+ fi
diff --git a/app/core/Makefile.in b/app/core/Makefile.in
new file mode 100644
index 0000000..0d84372
--- /dev/null
+++ b/app/core/Makefile.in
@@ -0,0 +1,2438 @@
+# Makefile.in generated by automake 1.16.3 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2020 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = app/core
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/acinclude.m4 \
+ $(top_srcdir)/m4macros/alsa.m4 \
+ $(top_srcdir)/m4macros/ax_compare_version.m4 \
+ $(top_srcdir)/m4macros/ax_cxx_compile_stdcxx.m4 \
+ $(top_srcdir)/m4macros/ax_gcc_func_attribute.m4 \
+ $(top_srcdir)/m4macros/ax_prog_cc_for_build.m4 \
+ $(top_srcdir)/m4macros/ax_prog_perl_version.m4 \
+ $(top_srcdir)/m4macros/detectcflags.m4 \
+ $(top_srcdir)/m4macros/pythondev.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+LIBRARIES = $(noinst_LIBRARIES)
+ARFLAGS = cru
+AM_V_AR = $(am__v_AR_@AM_V@)
+am__v_AR_ = $(am__v_AR_@AM_DEFAULT_V@)
+am__v_AR_0 = @echo " AR " $@;
+am__v_AR_1 =
+libappcore_a_AR = $(AR) $(ARFLAGS)
+libappcore_a_LIBADD =
+am__objects_1 = core-enums.$(OBJEXT) gimpmarshal.$(OBJEXT)
+am__objects_2 = gimp.$(OBJEXT) gimp-atomic.$(OBJEXT) \
+ gimp-batch.$(OBJEXT) gimp-cairo.$(OBJEXT) \
+ gimp-contexts.$(OBJEXT) gimp-data-factories.$(OBJEXT) \
+ gimp-edit.$(OBJEXT) gimp-filter-history.$(OBJEXT) \
+ gimp-gradients.$(OBJEXT) gimp-gui.$(OBJEXT) \
+ gimp-internal-data.$(OBJEXT) gimp-memsize.$(OBJEXT) \
+ gimp-modules.$(OBJEXT) gimp-palettes.$(OBJEXT) \
+ gimp-parallel.$(OBJEXT) gimp-parasites.$(OBJEXT) \
+ gimp-spawn.$(OBJEXT) gimp-tags.$(OBJEXT) \
+ gimp-templates.$(OBJEXT) gimp-transform-resize.$(OBJEXT) \
+ gimp-transform-3d-utils.$(OBJEXT) \
+ gimp-transform-utils.$(OBJEXT) gimp-units.$(OBJEXT) \
+ gimp-user-install.$(OBJEXT) gimp-utils.$(OBJEXT) \
+ gimpasync.$(OBJEXT) gimpasyncset.$(OBJEXT) \
+ gimpauxitem.$(OBJEXT) gimpauxitemundo.$(OBJEXT) \
+ gimpbacktrace-linux.$(OBJEXT) gimpbacktrace-none.$(OBJEXT) \
+ gimpbacktrace-windows.$(OBJEXT) gimpbezierdesc.$(OBJEXT) \
+ gimpboundary.$(OBJEXT) gimpbrush.$(OBJEXT) \
+ gimpbrush-boundary.$(OBJEXT) gimpbrush-load.$(OBJEXT) \
+ gimpbrush-mipmap.$(OBJEXT) gimpbrush-save.$(OBJEXT) \
+ gimpbrush-transform.$(OBJEXT) gimpbrushcache.$(OBJEXT) \
+ gimpbrushclipboard.$(OBJEXT) gimpbrushgenerated.$(OBJEXT) \
+ gimpbrushgenerated-load.$(OBJEXT) \
+ gimpbrushgenerated-save.$(OBJEXT) gimpbrushpipe.$(OBJEXT) \
+ gimpbrushpipe-load.$(OBJEXT) gimpbrushpipe-save.$(OBJEXT) \
+ gimpbuffer.$(OBJEXT) gimpcancelable.$(OBJEXT) \
+ gimpchannel.$(OBJEXT) gimpchannel-combine.$(OBJEXT) \
+ gimpchannel-select.$(OBJEXT) gimpchannelpropundo.$(OBJEXT) \
+ gimpchannelundo.$(OBJEXT) gimpchunkiterator.$(OBJEXT) \
+ gimpcontainer.$(OBJEXT) gimpcontainer-filter.$(OBJEXT) \
+ gimpcontext.$(OBJEXT) gimpcoords.$(OBJEXT) \
+ gimpcoords-interpolate.$(OBJEXT) gimpcurve.$(OBJEXT) \
+ gimpcurve-load.$(OBJEXT) gimpcurve-map.$(OBJEXT) \
+ gimpcurve-save.$(OBJEXT) gimpdashpattern.$(OBJEXT) \
+ gimpdata.$(OBJEXT) gimpdatafactory.$(OBJEXT) \
+ gimpdataloaderfactory.$(OBJEXT) gimpdocumentlist.$(OBJEXT) \
+ gimpdrawable.$(OBJEXT) gimpdrawable-bucket-fill.$(OBJEXT) \
+ gimpdrawable-combine.$(OBJEXT) gimpdrawable-edit.$(OBJEXT) \
+ gimpdrawable-equalize.$(OBJEXT) gimpdrawable-fill.$(OBJEXT) \
+ gimpdrawable-filters.$(OBJEXT) \
+ gimpdrawable-floating-selection.$(OBJEXT) \
+ gimpdrawable-foreground-extract.$(OBJEXT) \
+ gimpdrawable-gradient.$(OBJEXT) \
+ gimpdrawable-histogram.$(OBJEXT) gimpdrawable-levels.$(OBJEXT) \
+ gimpdrawable-offset.$(OBJEXT) gimpdrawable-operation.$(OBJEXT) \
+ gimpdrawable-preview.$(OBJEXT) gimpdrawable-shadow.$(OBJEXT) \
+ gimpdrawable-stroke.$(OBJEXT) gimpdrawable-transform.$(OBJEXT) \
+ gimpdrawablefilter.$(OBJEXT) gimpdrawablemodundo.$(OBJEXT) \
+ gimpdrawablestack.$(OBJEXT) gimpdrawableundo.$(OBJEXT) \
+ gimpdynamics.$(OBJEXT) gimpdynamics-load.$(OBJEXT) \
+ gimpdynamics-save.$(OBJEXT) gimpdynamicsoutput.$(OBJEXT) \
+ gimperror.$(OBJEXT) gimpfilloptions.$(OBJEXT) \
+ gimpfilter.$(OBJEXT) gimpfilteredcontainer.$(OBJEXT) \
+ gimpfilterstack.$(OBJEXT) gimpfloatingselectionundo.$(OBJEXT) \
+ gimpgradient.$(OBJEXT) gimpgradient-load.$(OBJEXT) \
+ gimpgradient-save.$(OBJEXT) gimpgrid.$(OBJEXT) \
+ gimpgrouplayer.$(OBJEXT) gimpgrouplayerundo.$(OBJEXT) \
+ gimpguide.$(OBJEXT) gimpguideundo.$(OBJEXT) \
+ gimphistogram.$(OBJEXT) gimpidtable.$(OBJEXT) \
+ gimpimage.$(OBJEXT) gimpimage-arrange.$(OBJEXT) \
+ gimpimage-color-profile.$(OBJEXT) gimpimage-colormap.$(OBJEXT) \
+ gimpimage-convert-indexed.$(OBJEXT) \
+ gimpimage-convert-precision.$(OBJEXT) \
+ gimpimage-convert-type.$(OBJEXT) gimpimage-crop.$(OBJEXT) \
+ gimpimage-duplicate.$(OBJEXT) gimpimage-flip.$(OBJEXT) \
+ gimpimage-grid.$(OBJEXT) gimpimage-guides.$(OBJEXT) \
+ gimpimage-item-list.$(OBJEXT) gimpimage-merge.$(OBJEXT) \
+ gimpimage-metadata.$(OBJEXT) gimpimage-new.$(OBJEXT) \
+ gimpimage-pick-color.$(OBJEXT) gimpimage-pick-item.$(OBJEXT) \
+ gimpimage-preview.$(OBJEXT) gimpimage-quick-mask.$(OBJEXT) \
+ gimpimage-resize.$(OBJEXT) gimpimage-rotate.$(OBJEXT) \
+ gimpimage-sample-points.$(OBJEXT) gimpimage-scale.$(OBJEXT) \
+ gimpimage-snap.$(OBJEXT) gimpimage-symmetry.$(OBJEXT) \
+ gimpimage-transform.$(OBJEXT) gimpimage-undo.$(OBJEXT) \
+ gimpimage-undo-push.$(OBJEXT) gimpimageproxy.$(OBJEXT) \
+ gimpimageundo.$(OBJEXT) gimpimagefile.$(OBJEXT) \
+ gimpitem.$(OBJEXT) gimpitem-exclusive.$(OBJEXT) \
+ gimpitem-linked.$(OBJEXT) gimpitem-preview.$(OBJEXT) \
+ gimpitempropundo.$(OBJEXT) gimpitemstack.$(OBJEXT) \
+ gimpitemtree.$(OBJEXT) gimpitemundo.$(OBJEXT) \
+ gimplayer.$(OBJEXT) gimplayer-floating-selection.$(OBJEXT) \
+ gimplayer-new.$(OBJEXT) gimplayermask.$(OBJEXT) \
+ gimplayermaskpropundo.$(OBJEXT) gimplayermaskundo.$(OBJEXT) \
+ gimplayerpropundo.$(OBJEXT) gimplayerstack.$(OBJEXT) \
+ gimplayerundo.$(OBJEXT) gimplineart.$(OBJEXT) \
+ gimplist.$(OBJEXT) gimpmaskundo.$(OBJEXT) \
+ gimpmybrush.$(OBJEXT) gimpmybrush-load.$(OBJEXT) \
+ gimpobject.$(OBJEXT) gimpobjectqueue.$(OBJEXT) \
+ gimppaintinfo.$(OBJEXT) gimppattern.$(OBJEXT) \
+ gimppattern-load.$(OBJEXT) gimppattern-save.$(OBJEXT) \
+ gimppatternclipboard.$(OBJEXT) gimppalette.$(OBJEXT) \
+ gimppalette-import.$(OBJEXT) gimppalette-load.$(OBJEXT) \
+ gimppalette-save.$(OBJEXT) gimppalettemru.$(OBJEXT) \
+ gimpparamspecs.$(OBJEXT) gimpparamspecs-desc.$(OBJEXT) \
+ gimpparamspecs-duplicate.$(OBJEXT) gimpparasitelist.$(OBJEXT) \
+ gimppdbprogress.$(OBJEXT) gimppickable.$(OBJEXT) \
+ gimppickable-auto-shrink.$(OBJEXT) \
+ gimppickable-contiguous-region.$(OBJEXT) \
+ gimpprogress.$(OBJEXT) gimpprojectable.$(OBJEXT) \
+ gimpprojection.$(OBJEXT) gimpsamplepoint.$(OBJEXT) \
+ gimpsamplepointundo.$(OBJEXT) gimpscanconvert.$(OBJEXT) \
+ gimpselection.$(OBJEXT) gimpsettings.$(OBJEXT) \
+ gimpstrokeoptions.$(OBJEXT) gimpsubprogress.$(OBJEXT) \
+ gimpsymmetry.$(OBJEXT) gimpsymmetry-mandala.$(OBJEXT) \
+ gimpsymmetry-mirror.$(OBJEXT) gimpsymmetry-tiling.$(OBJEXT) \
+ gimptag.$(OBJEXT) gimptagcache.$(OBJEXT) gimptagged.$(OBJEXT) \
+ gimptaggedcontainer.$(OBJEXT) gimptempbuf.$(OBJEXT) \
+ gimptemplate.$(OBJEXT) gimptilehandlerprojectable.$(OBJEXT) \
+ gimptoolgroup.$(OBJEXT) gimptoolinfo.$(OBJEXT) \
+ gimptoolitem.$(OBJEXT) gimptooloptions.$(OBJEXT) \
+ gimptoolpreset.$(OBJEXT) gimptoolpreset-load.$(OBJEXT) \
+ gimptoolpreset-save.$(OBJEXT) gimptreehandler.$(OBJEXT) \
+ gimptreeproxy.$(OBJEXT) \
+ gimptriviallycancelablewaitable.$(OBJEXT) \
+ gimpuncancelablewaitable.$(OBJEXT) gimpunit.$(OBJEXT) \
+ gimpundo.$(OBJEXT) gimpundostack.$(OBJEXT) \
+ gimpviewable.$(OBJEXT) gimpwaitable.$(OBJEXT)
+am_libappcore_a_OBJECTS = $(am__objects_1) $(am__objects_2)
+libappcore_a_OBJECTS = $(am_libappcore_a_OBJECTS)
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/core-enums.Po \
+ ./$(DEPDIR)/gimp-atomic.Po ./$(DEPDIR)/gimp-batch.Po \
+ ./$(DEPDIR)/gimp-cairo.Po ./$(DEPDIR)/gimp-contexts.Po \
+ ./$(DEPDIR)/gimp-data-factories.Po ./$(DEPDIR)/gimp-edit.Po \
+ ./$(DEPDIR)/gimp-filter-history.Po \
+ ./$(DEPDIR)/gimp-gradients.Po ./$(DEPDIR)/gimp-gui.Po \
+ ./$(DEPDIR)/gimp-internal-data.Po ./$(DEPDIR)/gimp-memsize.Po \
+ ./$(DEPDIR)/gimp-modules.Po ./$(DEPDIR)/gimp-palettes.Po \
+ ./$(DEPDIR)/gimp-parallel.Po ./$(DEPDIR)/gimp-parasites.Po \
+ ./$(DEPDIR)/gimp-spawn.Po ./$(DEPDIR)/gimp-tags.Po \
+ ./$(DEPDIR)/gimp-templates.Po \
+ ./$(DEPDIR)/gimp-transform-3d-utils.Po \
+ ./$(DEPDIR)/gimp-transform-resize.Po \
+ ./$(DEPDIR)/gimp-transform-utils.Po ./$(DEPDIR)/gimp-units.Po \
+ ./$(DEPDIR)/gimp-user-install.Po ./$(DEPDIR)/gimp-utils.Po \
+ ./$(DEPDIR)/gimp.Po ./$(DEPDIR)/gimpasync.Po \
+ ./$(DEPDIR)/gimpasyncset.Po ./$(DEPDIR)/gimpauxitem.Po \
+ ./$(DEPDIR)/gimpauxitemundo.Po \
+ ./$(DEPDIR)/gimpbacktrace-linux.Po \
+ ./$(DEPDIR)/gimpbacktrace-none.Po \
+ ./$(DEPDIR)/gimpbacktrace-windows.Po \
+ ./$(DEPDIR)/gimpbezierdesc.Po ./$(DEPDIR)/gimpboundary.Po \
+ ./$(DEPDIR)/gimpbrush-boundary.Po \
+ ./$(DEPDIR)/gimpbrush-load.Po ./$(DEPDIR)/gimpbrush-mipmap.Po \
+ ./$(DEPDIR)/gimpbrush-save.Po \
+ ./$(DEPDIR)/gimpbrush-transform.Po ./$(DEPDIR)/gimpbrush.Po \
+ ./$(DEPDIR)/gimpbrushcache.Po \
+ ./$(DEPDIR)/gimpbrushclipboard.Po \
+ ./$(DEPDIR)/gimpbrushgenerated-load.Po \
+ ./$(DEPDIR)/gimpbrushgenerated-save.Po \
+ ./$(DEPDIR)/gimpbrushgenerated.Po \
+ ./$(DEPDIR)/gimpbrushpipe-load.Po \
+ ./$(DEPDIR)/gimpbrushpipe-save.Po ./$(DEPDIR)/gimpbrushpipe.Po \
+ ./$(DEPDIR)/gimpbuffer.Po ./$(DEPDIR)/gimpcancelable.Po \
+ ./$(DEPDIR)/gimpchannel-combine.Po \
+ ./$(DEPDIR)/gimpchannel-select.Po ./$(DEPDIR)/gimpchannel.Po \
+ ./$(DEPDIR)/gimpchannelpropundo.Po \
+ ./$(DEPDIR)/gimpchannelundo.Po \
+ ./$(DEPDIR)/gimpchunkiterator.Po \
+ ./$(DEPDIR)/gimpcontainer-filter.Po \
+ ./$(DEPDIR)/gimpcontainer.Po ./$(DEPDIR)/gimpcontext.Po \
+ ./$(DEPDIR)/gimpcoords-interpolate.Po \
+ ./$(DEPDIR)/gimpcoords.Po ./$(DEPDIR)/gimpcurve-load.Po \
+ ./$(DEPDIR)/gimpcurve-map.Po ./$(DEPDIR)/gimpcurve-save.Po \
+ ./$(DEPDIR)/gimpcurve.Po ./$(DEPDIR)/gimpdashpattern.Po \
+ ./$(DEPDIR)/gimpdata.Po ./$(DEPDIR)/gimpdatafactory.Po \
+ ./$(DEPDIR)/gimpdataloaderfactory.Po \
+ ./$(DEPDIR)/gimpdocumentlist.Po \
+ ./$(DEPDIR)/gimpdrawable-bucket-fill.Po \
+ ./$(DEPDIR)/gimpdrawable-combine.Po \
+ ./$(DEPDIR)/gimpdrawable-edit.Po \
+ ./$(DEPDIR)/gimpdrawable-equalize.Po \
+ ./$(DEPDIR)/gimpdrawable-fill.Po \
+ ./$(DEPDIR)/gimpdrawable-filters.Po \
+ ./$(DEPDIR)/gimpdrawable-floating-selection.Po \
+ ./$(DEPDIR)/gimpdrawable-foreground-extract.Po \
+ ./$(DEPDIR)/gimpdrawable-gradient.Po \
+ ./$(DEPDIR)/gimpdrawable-histogram.Po \
+ ./$(DEPDIR)/gimpdrawable-levels.Po \
+ ./$(DEPDIR)/gimpdrawable-offset.Po \
+ ./$(DEPDIR)/gimpdrawable-operation.Po \
+ ./$(DEPDIR)/gimpdrawable-preview.Po \
+ ./$(DEPDIR)/gimpdrawable-shadow.Po \
+ ./$(DEPDIR)/gimpdrawable-stroke.Po \
+ ./$(DEPDIR)/gimpdrawable-transform.Po \
+ ./$(DEPDIR)/gimpdrawable.Po ./$(DEPDIR)/gimpdrawablefilter.Po \
+ ./$(DEPDIR)/gimpdrawablemodundo.Po \
+ ./$(DEPDIR)/gimpdrawablestack.Po \
+ ./$(DEPDIR)/gimpdrawableundo.Po \
+ ./$(DEPDIR)/gimpdynamics-load.Po \
+ ./$(DEPDIR)/gimpdynamics-save.Po ./$(DEPDIR)/gimpdynamics.Po \
+ ./$(DEPDIR)/gimpdynamicsoutput.Po ./$(DEPDIR)/gimperror.Po \
+ ./$(DEPDIR)/gimpfilloptions.Po ./$(DEPDIR)/gimpfilter.Po \
+ ./$(DEPDIR)/gimpfilteredcontainer.Po \
+ ./$(DEPDIR)/gimpfilterstack.Po \
+ ./$(DEPDIR)/gimpfloatingselectionundo.Po \
+ ./$(DEPDIR)/gimpgradient-load.Po \
+ ./$(DEPDIR)/gimpgradient-save.Po ./$(DEPDIR)/gimpgradient.Po \
+ ./$(DEPDIR)/gimpgrid.Po ./$(DEPDIR)/gimpgrouplayer.Po \
+ ./$(DEPDIR)/gimpgrouplayerundo.Po ./$(DEPDIR)/gimpguide.Po \
+ ./$(DEPDIR)/gimpguideundo.Po ./$(DEPDIR)/gimphistogram.Po \
+ ./$(DEPDIR)/gimpidtable.Po ./$(DEPDIR)/gimpimage-arrange.Po \
+ ./$(DEPDIR)/gimpimage-color-profile.Po \
+ ./$(DEPDIR)/gimpimage-colormap.Po \
+ ./$(DEPDIR)/gimpimage-convert-indexed.Po \
+ ./$(DEPDIR)/gimpimage-convert-precision.Po \
+ ./$(DEPDIR)/gimpimage-convert-type.Po \
+ ./$(DEPDIR)/gimpimage-crop.Po \
+ ./$(DEPDIR)/gimpimage-duplicate.Po \
+ ./$(DEPDIR)/gimpimage-flip.Po ./$(DEPDIR)/gimpimage-grid.Po \
+ ./$(DEPDIR)/gimpimage-guides.Po \
+ ./$(DEPDIR)/gimpimage-item-list.Po \
+ ./$(DEPDIR)/gimpimage-merge.Po \
+ ./$(DEPDIR)/gimpimage-metadata.Po ./$(DEPDIR)/gimpimage-new.Po \
+ ./$(DEPDIR)/gimpimage-pick-color.Po \
+ ./$(DEPDIR)/gimpimage-pick-item.Po \
+ ./$(DEPDIR)/gimpimage-preview.Po \
+ ./$(DEPDIR)/gimpimage-quick-mask.Po \
+ ./$(DEPDIR)/gimpimage-resize.Po \
+ ./$(DEPDIR)/gimpimage-rotate.Po \
+ ./$(DEPDIR)/gimpimage-sample-points.Po \
+ ./$(DEPDIR)/gimpimage-scale.Po ./$(DEPDIR)/gimpimage-snap.Po \
+ ./$(DEPDIR)/gimpimage-symmetry.Po \
+ ./$(DEPDIR)/gimpimage-transform.Po \
+ ./$(DEPDIR)/gimpimage-undo-push.Po \
+ ./$(DEPDIR)/gimpimage-undo.Po ./$(DEPDIR)/gimpimage.Po \
+ ./$(DEPDIR)/gimpimagefile.Po ./$(DEPDIR)/gimpimageproxy.Po \
+ ./$(DEPDIR)/gimpimageundo.Po ./$(DEPDIR)/gimpitem-exclusive.Po \
+ ./$(DEPDIR)/gimpitem-linked.Po ./$(DEPDIR)/gimpitem-preview.Po \
+ ./$(DEPDIR)/gimpitem.Po ./$(DEPDIR)/gimpitempropundo.Po \
+ ./$(DEPDIR)/gimpitemstack.Po ./$(DEPDIR)/gimpitemtree.Po \
+ ./$(DEPDIR)/gimpitemundo.Po \
+ ./$(DEPDIR)/gimplayer-floating-selection.Po \
+ ./$(DEPDIR)/gimplayer-new.Po ./$(DEPDIR)/gimplayer.Po \
+ ./$(DEPDIR)/gimplayermask.Po \
+ ./$(DEPDIR)/gimplayermaskpropundo.Po \
+ ./$(DEPDIR)/gimplayermaskundo.Po \
+ ./$(DEPDIR)/gimplayerpropundo.Po ./$(DEPDIR)/gimplayerstack.Po \
+ ./$(DEPDIR)/gimplayerundo.Po ./$(DEPDIR)/gimplineart.Po \
+ ./$(DEPDIR)/gimplist.Po ./$(DEPDIR)/gimpmarshal.Po \
+ ./$(DEPDIR)/gimpmaskundo.Po ./$(DEPDIR)/gimpmybrush-load.Po \
+ ./$(DEPDIR)/gimpmybrush.Po ./$(DEPDIR)/gimpobject.Po \
+ ./$(DEPDIR)/gimpobjectqueue.Po ./$(DEPDIR)/gimppaintinfo.Po \
+ ./$(DEPDIR)/gimppalette-import.Po \
+ ./$(DEPDIR)/gimppalette-load.Po \
+ ./$(DEPDIR)/gimppalette-save.Po ./$(DEPDIR)/gimppalette.Po \
+ ./$(DEPDIR)/gimppalettemru.Po \
+ ./$(DEPDIR)/gimpparamspecs-desc.Po \
+ ./$(DEPDIR)/gimpparamspecs-duplicate.Po \
+ ./$(DEPDIR)/gimpparamspecs.Po ./$(DEPDIR)/gimpparasitelist.Po \
+ ./$(DEPDIR)/gimppattern-load.Po \
+ ./$(DEPDIR)/gimppattern-save.Po ./$(DEPDIR)/gimppattern.Po \
+ ./$(DEPDIR)/gimppatternclipboard.Po \
+ ./$(DEPDIR)/gimppdbprogress.Po \
+ ./$(DEPDIR)/gimppickable-auto-shrink.Po \
+ ./$(DEPDIR)/gimppickable-contiguous-region.Po \
+ ./$(DEPDIR)/gimppickable.Po ./$(DEPDIR)/gimpprogress.Po \
+ ./$(DEPDIR)/gimpprojectable.Po ./$(DEPDIR)/gimpprojection.Po \
+ ./$(DEPDIR)/gimpsamplepoint.Po \
+ ./$(DEPDIR)/gimpsamplepointundo.Po \
+ ./$(DEPDIR)/gimpscanconvert.Po ./$(DEPDIR)/gimpselection.Po \
+ ./$(DEPDIR)/gimpsettings.Po ./$(DEPDIR)/gimpstrokeoptions.Po \
+ ./$(DEPDIR)/gimpsubprogress.Po \
+ ./$(DEPDIR)/gimpsymmetry-mandala.Po \
+ ./$(DEPDIR)/gimpsymmetry-mirror.Po \
+ ./$(DEPDIR)/gimpsymmetry-tiling.Po ./$(DEPDIR)/gimpsymmetry.Po \
+ ./$(DEPDIR)/gimptag.Po ./$(DEPDIR)/gimptagcache.Po \
+ ./$(DEPDIR)/gimptagged.Po ./$(DEPDIR)/gimptaggedcontainer.Po \
+ ./$(DEPDIR)/gimptempbuf.Po ./$(DEPDIR)/gimptemplate.Po \
+ ./$(DEPDIR)/gimptilehandlerprojectable.Po \
+ ./$(DEPDIR)/gimptoolgroup.Po ./$(DEPDIR)/gimptoolinfo.Po \
+ ./$(DEPDIR)/gimptoolitem.Po ./$(DEPDIR)/gimptooloptions.Po \
+ ./$(DEPDIR)/gimptoolpreset-load.Po \
+ ./$(DEPDIR)/gimptoolpreset-save.Po \
+ ./$(DEPDIR)/gimptoolpreset.Po ./$(DEPDIR)/gimptreehandler.Po \
+ ./$(DEPDIR)/gimptreeproxy.Po \
+ ./$(DEPDIR)/gimptriviallycancelablewaitable.Po \
+ ./$(DEPDIR)/gimpuncancelablewaitable.Po \
+ ./$(DEPDIR)/gimpundo.Po ./$(DEPDIR)/gimpundostack.Po \
+ ./$(DEPDIR)/gimpunit.Po ./$(DEPDIR)/gimpviewable.Po \
+ ./$(DEPDIR)/gimpwaitable.Po
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+CXXCOMPILE = $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \
+ $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS)
+LTCXXCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CXXFLAGS) $(CXXFLAGS)
+AM_V_CXX = $(am__v_CXX_@AM_V@)
+am__v_CXX_ = $(am__v_CXX_@AM_DEFAULT_V@)
+am__v_CXX_0 = @echo " CXX " $@;
+am__v_CXX_1 =
+CXXLD = $(CXX)
+CXXLINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(AM_CXXFLAGS) \
+ $(CXXFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CXXLD = $(am__v_CXXLD_@AM_V@)
+am__v_CXXLD_ = $(am__v_CXXLD_@AM_DEFAULT_V@)
+am__v_CXXLD_0 = @echo " CXXLD " $@;
+am__v_CXXLD_1 =
+SOURCES = $(libappcore_a_SOURCES)
+DIST_SOURCES = $(libappcore_a_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+AA_LIBS = @AA_LIBS@
+ACLOCAL = @ACLOCAL@
+ALLOCA = @ALLOCA@
+ALL_LINGUAS = @ALL_LINGUAS@
+ALSA_CFLAGS = @ALSA_CFLAGS@
+ALSA_LIBS = @ALSA_LIBS@
+ALTIVEC_EXTRA_CFLAGS = @ALTIVEC_EXTRA_CFLAGS@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+APPSTREAM_UTIL = @APPSTREAM_UTIL@
+AR = @AR@
+AS = @AS@
+ATK_CFLAGS = @ATK_CFLAGS@
+ATK_LIBS = @ATK_LIBS@
+ATK_REQUIRED_VERSION = @ATK_REQUIRED_VERSION@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BABL_CFLAGS = @BABL_CFLAGS@
+BABL_LIBS = @BABL_LIBS@
+BABL_REQUIRED_VERSION = @BABL_REQUIRED_VERSION@
+BUG_REPORT_URL = @BUG_REPORT_URL@
+BUILD_EXEEXT = @BUILD_EXEEXT@
+BUILD_OBJEXT = @BUILD_OBJEXT@
+BZIP2_LIBS = @BZIP2_LIBS@
+CAIRO_CFLAGS = @CAIRO_CFLAGS@
+CAIRO_LIBS = @CAIRO_LIBS@
+CAIRO_PDF_CFLAGS = @CAIRO_PDF_CFLAGS@
+CAIRO_PDF_LIBS = @CAIRO_PDF_LIBS@
+CAIRO_PDF_REQUIRED_VERSION = @CAIRO_PDF_REQUIRED_VERSION@
+CAIRO_REQUIRED_VERSION = @CAIRO_REQUIRED_VERSION@
+CATALOGS = @CATALOGS@
+CATOBJEXT = @CATOBJEXT@
+CC = @CC@
+CCAS = @CCAS@
+CCASDEPMODE = @CCASDEPMODE@
+CCASFLAGS = @CCASFLAGS@
+CCDEPMODE = @CCDEPMODE@
+CC_FOR_BUILD = @CC_FOR_BUILD@
+CC_VERSION = @CC_VERSION@
+CFLAGS = @CFLAGS@
+CFLAGS_FOR_BUILD = @CFLAGS_FOR_BUILD@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CPPFLAGS_FOR_BUILD = @CPPFLAGS_FOR_BUILD@
+CPP_FOR_BUILD = @CPP_FOR_BUILD@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DATADIRNAME = @DATADIRNAME@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DESKTOP_DATADIR = @DESKTOP_DATADIR@
+DESKTOP_FILE_VALIDATE = @DESKTOP_FILE_VALIDATE@
+DLLTOOL = @DLLTOOL@
+DOC_SHOOTER = @DOC_SHOOTER@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+FILE_AA = @FILE_AA@
+FILE_EXR = @FILE_EXR@
+FILE_HEIF = @FILE_HEIF@
+FILE_JP2_LOAD = @FILE_JP2_LOAD@
+FILE_JPEGXL = @FILE_JPEGXL@
+FILE_MNG = @FILE_MNG@
+FILE_PDF_SAVE = @FILE_PDF_SAVE@
+FILE_PS = @FILE_PS@
+FILE_WMF = @FILE_WMF@
+FILE_XMC = @FILE_XMC@
+FILE_XPM = @FILE_XPM@
+FONTCONFIG_CFLAGS = @FONTCONFIG_CFLAGS@
+FONTCONFIG_LIBS = @FONTCONFIG_LIBS@
+FONTCONFIG_REQUIRED_VERSION = @FONTCONFIG_REQUIRED_VERSION@
+FREETYPE2_REQUIRED_VERSION = @FREETYPE2_REQUIRED_VERSION@
+FREETYPE_CFLAGS = @FREETYPE_CFLAGS@
+FREETYPE_LIBS = @FREETYPE_LIBS@
+GDBUS_CODEGEN = @GDBUS_CODEGEN@
+GDK_PIXBUF_CFLAGS = @GDK_PIXBUF_CFLAGS@
+GDK_PIXBUF_CSOURCE = @GDK_PIXBUF_CSOURCE@
+GDK_PIXBUF_LIBS = @GDK_PIXBUF_LIBS@
+GDK_PIXBUF_REQUIRED_VERSION = @GDK_PIXBUF_REQUIRED_VERSION@
+GEGL = @GEGL@
+GEGL_CFLAGS = @GEGL_CFLAGS@
+GEGL_LIBS = @GEGL_LIBS@
+GEGL_MAJOR_MINOR_VERSION = @GEGL_MAJOR_MINOR_VERSION@
+GEGL_REQUIRED_VERSION = @GEGL_REQUIRED_VERSION@
+GETTEXT_PACKAGE = @GETTEXT_PACKAGE@
+GEXIV2_CFLAGS = @GEXIV2_CFLAGS@
+GEXIV2_LIBS = @GEXIV2_LIBS@
+GEXIV2_REQUIRED_VERSION = @GEXIV2_REQUIRED_VERSION@
+GIMP_API_VERSION = @GIMP_API_VERSION@
+GIMP_APP_VERSION = @GIMP_APP_VERSION@
+GIMP_BINARY_AGE = @GIMP_BINARY_AGE@
+GIMP_COMMAND = @GIMP_COMMAND@
+GIMP_DATA_VERSION = @GIMP_DATA_VERSION@
+GIMP_FULL_NAME = @GIMP_FULL_NAME@
+GIMP_INTERFACE_AGE = @GIMP_INTERFACE_AGE@
+GIMP_MAJOR_VERSION = @GIMP_MAJOR_VERSION@
+GIMP_MICRO_VERSION = @GIMP_MICRO_VERSION@
+GIMP_MINOR_VERSION = @GIMP_MINOR_VERSION@
+GIMP_MKENUMS = @GIMP_MKENUMS@
+GIMP_MODULES = @GIMP_MODULES@
+GIMP_PACKAGE_REVISION = @GIMP_PACKAGE_REVISION@
+GIMP_PKGCONFIG_VERSION = @GIMP_PKGCONFIG_VERSION@
+GIMP_PLUGINS = @GIMP_PLUGINS@
+GIMP_PLUGIN_VERSION = @GIMP_PLUGIN_VERSION@
+GIMP_REAL_VERSION = @GIMP_REAL_VERSION@
+GIMP_RELEASE = @GIMP_RELEASE@
+GIMP_SYSCONF_VERSION = @GIMP_SYSCONF_VERSION@
+GIMP_TOOL_VERSION = @GIMP_TOOL_VERSION@
+GIMP_UNSTABLE = @GIMP_UNSTABLE@
+GIMP_USER_VERSION = @GIMP_USER_VERSION@
+GIMP_VERSION = @GIMP_VERSION@
+GIO_CFLAGS = @GIO_CFLAGS@
+GIO_LIBS = @GIO_LIBS@
+GIO_UNIX_CFLAGS = @GIO_UNIX_CFLAGS@
+GIO_UNIX_LIBS = @GIO_UNIX_LIBS@
+GIO_WINDOWS_CFLAGS = @GIO_WINDOWS_CFLAGS@
+GIO_WINDOWS_LIBS = @GIO_WINDOWS_LIBS@
+GLIB_CFLAGS = @GLIB_CFLAGS@
+GLIB_COMPILE_RESOURCES = @GLIB_COMPILE_RESOURCES@
+GLIB_GENMARSHAL = @GLIB_GENMARSHAL@
+GLIB_LIBS = @GLIB_LIBS@
+GLIB_MKENUMS = @GLIB_MKENUMS@
+GLIB_REQUIRED_VERSION = @GLIB_REQUIRED_VERSION@
+GMODULE_NO_EXPORT_CFLAGS = @GMODULE_NO_EXPORT_CFLAGS@
+GMODULE_NO_EXPORT_LIBS = @GMODULE_NO_EXPORT_LIBS@
+GMOFILES = @GMOFILES@
+GMSGFMT = @GMSGFMT@
+GOBJECT_QUERY = @GOBJECT_QUERY@
+GREP = @GREP@
+GS_LIBS = @GS_LIBS@
+GTKDOC_CHECK = @GTKDOC_CHECK@
+GTKDOC_CHECK_PATH = @GTKDOC_CHECK_PATH@
+GTKDOC_DEPS_CFLAGS = @GTKDOC_DEPS_CFLAGS@
+GTKDOC_DEPS_LIBS = @GTKDOC_DEPS_LIBS@
+GTKDOC_MKPDF = @GTKDOC_MKPDF@
+GTKDOC_REBASE = @GTKDOC_REBASE@
+GTK_CFLAGS = @GTK_CFLAGS@
+GTK_LIBS = @GTK_LIBS@
+GTK_MAC_INTEGRATION_CFLAGS = @GTK_MAC_INTEGRATION_CFLAGS@
+GTK_MAC_INTEGRATION_LIBS = @GTK_MAC_INTEGRATION_LIBS@
+GTK_REQUIRED_VERSION = @GTK_REQUIRED_VERSION@
+GTK_UPDATE_ICON_CACHE = @GTK_UPDATE_ICON_CACHE@
+GUDEV_CFLAGS = @GUDEV_CFLAGS@
+GUDEV_LIBS = @GUDEV_LIBS@
+HARFBUZZ_CFLAGS = @HARFBUZZ_CFLAGS@
+HARFBUZZ_LIBS = @HARFBUZZ_LIBS@
+HARFBUZZ_REQUIRED_VERSION = @HARFBUZZ_REQUIRED_VERSION@
+HAVE_CXX14 = @HAVE_CXX14@
+HAVE_FINITE = @HAVE_FINITE@
+HAVE_ISFINITE = @HAVE_ISFINITE@
+HAVE_VFORK = @HAVE_VFORK@
+HOST_GLIB_COMPILE_RESOURCES = @HOST_GLIB_COMPILE_RESOURCES@
+HTML_DIR = @HTML_DIR@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+INSTOBJEXT = @INSTOBJEXT@
+INTLLIBS = @INTLLIBS@
+INTLTOOL_EXTRACT = @INTLTOOL_EXTRACT@
+INTLTOOL_MERGE = @INTLTOOL_MERGE@
+INTLTOOL_PERL = @INTLTOOL_PERL@
+INTLTOOL_REQUIRED_VERSION = @INTLTOOL_REQUIRED_VERSION@
+INTLTOOL_UPDATE = @INTLTOOL_UPDATE@
+INTLTOOL_V_MERGE = @INTLTOOL_V_MERGE@
+INTLTOOL_V_MERGE_OPTIONS = @INTLTOOL_V_MERGE_OPTIONS@
+INTLTOOL__v_MERGE_ = @INTLTOOL__v_MERGE_@
+INTLTOOL__v_MERGE_0 = @INTLTOOL__v_MERGE_0@
+INTL_MACOSX_LIBS = @INTL_MACOSX_LIBS@
+ISO_CODES_LOCALEDIR = @ISO_CODES_LOCALEDIR@
+ISO_CODES_LOCATION = @ISO_CODES_LOCATION@
+JPEG_LIBS = @JPEG_LIBS@
+JSON_GLIB_CFLAGS = @JSON_GLIB_CFLAGS@
+JSON_GLIB_LIBS = @JSON_GLIB_LIBS@
+JXL_CFLAGS = @JXL_CFLAGS@
+JXL_LIBS = @JXL_LIBS@
+JXL_THREADS_CFLAGS = @JXL_THREADS_CFLAGS@
+JXL_THREADS_LIBS = @JXL_THREADS_LIBS@
+LCMS_CFLAGS = @LCMS_CFLAGS@
+LCMS_LIBS = @LCMS_LIBS@
+LCMS_REQUIRED_VERSION = @LCMS_REQUIRED_VERSION@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LDFLAGS_FOR_BUILD = @LDFLAGS_FOR_BUILD@
+LIBBACKTRACE_LIBS = @LIBBACKTRACE_LIBS@
+LIBHEIF_CFLAGS = @LIBHEIF_CFLAGS@
+LIBHEIF_LIBS = @LIBHEIF_LIBS@
+LIBHEIF_REQUIRED_VERSION = @LIBHEIF_REQUIRED_VERSION@
+LIBJXL_REQUIRED_VERSION = @LIBJXL_REQUIRED_VERSION@
+LIBLZMA_REQUIRED_VERSION = @LIBLZMA_REQUIRED_VERSION@
+LIBMYPAINT_CFLAGS = @LIBMYPAINT_CFLAGS@
+LIBMYPAINT_LIBS = @LIBMYPAINT_LIBS@
+LIBMYPAINT_REQUIRED_VERSION = @LIBMYPAINT_REQUIRED_VERSION@
+LIBOBJS = @LIBOBJS@
+LIBPNG_REQUIRED_VERSION = @LIBPNG_REQUIRED_VERSION@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@
+LIBUNWIND_LIBS = @LIBUNWIND_LIBS@
+LIBUNWIND_REQUIRED_VERSION = @LIBUNWIND_REQUIRED_VERSION@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+LT_CURRENT_MINUS_AGE = @LT_CURRENT_MINUS_AGE@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+LT_VERSION_INFO = @LT_VERSION_INFO@
+LZMA_CFLAGS = @LZMA_CFLAGS@
+LZMA_LIBS = @LZMA_LIBS@
+MAIL = @MAIL@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MIME_INFO_CFLAGS = @MIME_INFO_CFLAGS@
+MIME_INFO_LIBS = @MIME_INFO_LIBS@
+MIME_TYPES = @MIME_TYPES@
+MKDIR_P = @MKDIR_P@
+MKINSTALLDIRS = @MKINSTALLDIRS@
+MMX_EXTRA_CFLAGS = @MMX_EXTRA_CFLAGS@
+MNG_CFLAGS = @MNG_CFLAGS@
+MNG_LIBS = @MNG_LIBS@
+MSGFMT = @MSGFMT@
+MSGFMT_OPTS = @MSGFMT_OPTS@
+MSGMERGE = @MSGMERGE@
+MYPAINT_BRUSHES_CFLAGS = @MYPAINT_BRUSHES_CFLAGS@
+MYPAINT_BRUSHES_LIBS = @MYPAINT_BRUSHES_LIBS@
+NATIVE_GLIB_CFLAGS = @NATIVE_GLIB_CFLAGS@
+NATIVE_GLIB_LIBS = @NATIVE_GLIB_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OPENEXR_CFLAGS = @OPENEXR_CFLAGS@
+OPENEXR_LIBS = @OPENEXR_LIBS@
+OPENEXR_REQUIRED_VERSION = @OPENEXR_REQUIRED_VERSION@
+OPENJPEG_CFLAGS = @OPENJPEG_CFLAGS@
+OPENJPEG_LIBS = @OPENJPEG_LIBS@
+OPENJPEG_REQUIRED_VERSION = @OPENJPEG_REQUIRED_VERSION@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PANGOCAIRO_CFLAGS = @PANGOCAIRO_CFLAGS@
+PANGOCAIRO_LIBS = @PANGOCAIRO_LIBS@
+PANGOCAIRO_REQUIRED_VERSION = @PANGOCAIRO_REQUIRED_VERSION@
+PATHSEP = @PATHSEP@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PERL = @PERL@
+PERL_REQUIRED_VERSION = @PERL_REQUIRED_VERSION@
+PERL_VERSION = @PERL_VERSION@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+PNG_CFLAGS = @PNG_CFLAGS@
+PNG_LIBS = @PNG_LIBS@
+POFILES = @POFILES@
+POPPLER_CFLAGS = @POPPLER_CFLAGS@
+POPPLER_DATA_CFLAGS = @POPPLER_DATA_CFLAGS@
+POPPLER_DATA_LIBS = @POPPLER_DATA_LIBS@
+POPPLER_DATA_REQUIRED_VERSION = @POPPLER_DATA_REQUIRED_VERSION@
+POPPLER_LIBS = @POPPLER_LIBS@
+POPPLER_REQUIRED_VERSION = @POPPLER_REQUIRED_VERSION@
+POSUB = @POSUB@
+PO_IN_DATADIR_FALSE = @PO_IN_DATADIR_FALSE@
+PO_IN_DATADIR_TRUE = @PO_IN_DATADIR_TRUE@
+PYBIN_PATH = @PYBIN_PATH@
+PYCAIRO_CFLAGS = @PYCAIRO_CFLAGS@
+PYCAIRO_LIBS = @PYCAIRO_LIBS@
+PYGIMP_EXTRA_CFLAGS = @PYGIMP_EXTRA_CFLAGS@
+PYGTK_CFLAGS = @PYGTK_CFLAGS@
+PYGTK_CODEGEN = @PYGTK_CODEGEN@
+PYGTK_DEFSDIR = @PYGTK_DEFSDIR@
+PYGTK_LIBS = @PYGTK_LIBS@
+PYLINK_LIBS = @PYLINK_LIBS@
+PYTHON = @PYTHON@
+PYTHON2_REQUIRED_VERSION = @PYTHON2_REQUIRED_VERSION@
+PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@
+PYTHON_INCLUDES = @PYTHON_INCLUDES@
+PYTHON_PLATFORM = @PYTHON_PLATFORM@
+PYTHON_PREFIX = @PYTHON_PREFIX@
+PYTHON_VERSION = @PYTHON_VERSION@
+RANLIB = @RANLIB@
+RSVG_REQUIRED_VERSION = @RSVG_REQUIRED_VERSION@
+RT_LIBS = @RT_LIBS@
+SCREENSHOT_LIBS = @SCREENSHOT_LIBS@
+SED = @SED@
+SENDMAIL = @SENDMAIL@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SOCKET_LIBS = @SOCKET_LIBS@
+SSE2_EXTRA_CFLAGS = @SSE2_EXTRA_CFLAGS@
+SSE4_1_EXTRA_CFLAGS = @SSE4_1_EXTRA_CFLAGS@
+SSE_EXTRA_CFLAGS = @SSE_EXTRA_CFLAGS@
+STRIP = @STRIP@
+SVG_CFLAGS = @SVG_CFLAGS@
+SVG_LIBS = @SVG_LIBS@
+SYMPREFIX = @SYMPREFIX@
+TIFF_LIBS = @TIFF_LIBS@
+USE_NLS = @USE_NLS@
+VERSION = @VERSION@
+WEBKIT_CFLAGS = @WEBKIT_CFLAGS@
+WEBKIT_LIBS = @WEBKIT_LIBS@
+WEBKIT_REQUIRED_VERSION = @WEBKIT_REQUIRED_VERSION@
+WEBPDEMUX_CFLAGS = @WEBPDEMUX_CFLAGS@
+WEBPDEMUX_LIBS = @WEBPDEMUX_LIBS@
+WEBPMUX_CFLAGS = @WEBPMUX_CFLAGS@
+WEBPMUX_LIBS = @WEBPMUX_LIBS@
+WEBP_CFLAGS = @WEBP_CFLAGS@
+WEBP_LIBS = @WEBP_LIBS@
+WEBP_REQUIRED_VERSION = @WEBP_REQUIRED_VERSION@
+WEB_PAGE = @WEB_PAGE@
+WIN32_LARGE_ADDRESS_AWARE = @WIN32_LARGE_ADDRESS_AWARE@
+WINDRES = @WINDRES@
+WMF_CFLAGS = @WMF_CFLAGS@
+WMF_CONFIG = @WMF_CONFIG@
+WMF_LIBS = @WMF_LIBS@
+WMF_REQUIRED_VERSION = @WMF_REQUIRED_VERSION@
+XDG_EMAIL = @XDG_EMAIL@
+XFIXES_CFLAGS = @XFIXES_CFLAGS@
+XFIXES_LIBS = @XFIXES_LIBS@
+XGETTEXT = @XGETTEXT@
+XGETTEXT_REQUIRED_VERSION = @XGETTEXT_REQUIRED_VERSION@
+XMC_CFLAGS = @XMC_CFLAGS@
+XMC_LIBS = @XMC_LIBS@
+XMKMF = @XMKMF@
+XMLLINT = @XMLLINT@
+XMU_LIBS = @XMU_LIBS@
+XPM_LIBS = @XPM_LIBS@
+XSLTPROC = @XSLTPROC@
+XVFB_RUN = @XVFB_RUN@
+X_CFLAGS = @X_CFLAGS@
+X_EXTRA_LIBS = @X_EXTRA_LIBS@
+X_LIBS = @X_LIBS@
+X_PRE_LIBS = @X_PRE_LIBS@
+Z_LIBS = @Z_LIBS@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CC_FOR_BUILD = @ac_ct_CC_FOR_BUILD@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+gimpdatadir = @gimpdatadir@
+gimpdir = @gimpdir@
+gimplocaledir = @gimplocaledir@
+gimpplugindir = @gimpplugindir@
+gimpsysconfdir = @gimpsysconfdir@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+intltool__v_merge_options_ = @intltool__v_merge_options_@
+intltool__v_merge_options_0 = @intltool__v_merge_options_0@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+manpage_gimpdir = @manpage_gimpdir@
+mkdir_p = @mkdir_p@
+ms_librarian = @ms_librarian@
+mypaint_brushes_dir = @mypaint_brushes_dir@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+pkgpyexecdir = @pkgpyexecdir@
+pkgpythondir = @pkgpythondir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+pyexecdir = @pyexecdir@
+pythondir = @pythondir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+@PLATFORM_OSX_TRUE@xobjective_c = "-xobjective-c"
+@PLATFORM_OSX_TRUE@xobjective_cxx = "-xobjective-c++"
+@PLATFORM_OSX_TRUE@xnone = "-xnone"
+AM_CPPFLAGS = \
+ -DGIMPDIR=\""$(gimpdir)"\" \
+ -DGIMP_APP_VERSION=\"$(GIMP_APP_VERSION)\" \
+ -DGIMP_USER_VERSION=\"$(GIMP_USER_VERSION)\" \
+ -DG_LOG_DOMAIN=\"Gimp-Core\" \
+ -I$(top_builddir) \
+ -I$(top_srcdir) \
+ -I$(top_builddir)/app \
+ -I$(top_srcdir)/app \
+ $(CAIRO_CFLAGS) \
+ $(GEGL_CFLAGS) \
+ $(GDK_PIXBUF_CFLAGS) \
+ $(LIBMYPAINT_CFLAGS) \
+ $(MYPAINT_BRUSHES_CFLAGS) \
+ $(GEXIV2_CFLAGS) \
+ $(LIBUNWIND_CFLAGS) \
+ -I$(includedir)
+
+AM_CFLAGS = \
+ $(xobjective_c)
+
+AM_CXXFLAGS = \
+ $(xobjective_cxx)
+
+AM_LDFLAGS = \
+ $(xnone)
+
+noinst_LIBRARIES = libappcore.a
+libappcore_a_sources = \
+ core-enums.h \
+ core-types.h \
+ gimp.c \
+ gimp.h \
+ gimp-atomic.c \
+ gimp-atomic.h \
+ gimp-batch.c \
+ gimp-batch.h \
+ gimp-cairo.c \
+ gimp-cairo.h \
+ gimp-contexts.c \
+ gimp-contexts.h \
+ gimp-data-factories.c \
+ gimp-data-factories.h \
+ gimp-edit.c \
+ gimp-edit.h \
+ gimp-filter-history.c \
+ gimp-filter-history.h \
+ gimp-gradients.c \
+ gimp-gradients.h \
+ gimp-gui.c \
+ gimp-gui.h \
+ gimp-internal-data.c \
+ gimp-internal-data.h \
+ gimp-memsize.c \
+ gimp-memsize.h \
+ gimp-modules.c \
+ gimp-modules.h \
+ gimp-palettes.c \
+ gimp-palettes.h \
+ gimp-parallel.cc \
+ gimp-parallel.h \
+ gimp-parasites.c \
+ gimp-parasites.h \
+ gimp-spawn.c \
+ gimp-spawn.h \
+ gimp-tags.c \
+ gimp-tags.h \
+ gimp-templates.c \
+ gimp-templates.h \
+ gimp-transform-resize.c \
+ gimp-transform-resize.h \
+ gimp-transform-3d-utils.c \
+ gimp-transform-3d-utils.h \
+ gimp-transform-utils.c \
+ gimp-transform-utils.h \
+ gimp-units.c \
+ gimp-units.h \
+ gimp-user-install.c \
+ gimp-user-install.h \
+ gimp-utils.c \
+ gimp-utils.h \
+ gimpasync.c \
+ gimpasync.h \
+ gimpasyncset.c \
+ gimpasyncset.h \
+ gimpauxitem.c \
+ gimpauxitem.h \
+ gimpauxitemundo.c \
+ gimpauxitemundo.h \
+ gimpbacktrace.h \
+ gimpbacktrace-backend.h \
+ gimpbacktrace-linux.c \
+ gimpbacktrace-none.c \
+ gimpbacktrace-windows.c \
+ gimpbezierdesc.h \
+ gimpbezierdesc.c \
+ gimpboundary.c \
+ gimpboundary.h \
+ gimpbrush.c \
+ gimpbrush.h \
+ gimpbrush-boundary.c \
+ gimpbrush-boundary.h \
+ gimpbrush-header.h \
+ gimpbrush-load.c \
+ gimpbrush-load.h \
+ gimpbrush-mipmap.cc \
+ gimpbrush-mipmap.h \
+ gimpbrush-private.h \
+ gimpbrush-save.c \
+ gimpbrush-save.h \
+ gimpbrush-transform.cc \
+ gimpbrush-transform.h \
+ gimpbrushcache.c \
+ gimpbrushcache.h \
+ gimpbrushclipboard.c \
+ gimpbrushclipboard.h \
+ gimpbrushgenerated.c \
+ gimpbrushgenerated.h \
+ gimpbrushgenerated-load.c \
+ gimpbrushgenerated-load.h \
+ gimpbrushgenerated-save.c \
+ gimpbrushgenerated-save.h \
+ gimpbrushpipe.c \
+ gimpbrushpipe.h \
+ gimpbrushpipe-load.c \
+ gimpbrushpipe-load.h \
+ gimpbrushpipe-save.c \
+ gimpbrushpipe-save.h \
+ gimpbuffer.c \
+ gimpbuffer.h \
+ gimpcancelable.c \
+ gimpcancelable.h \
+ gimpchannel.c \
+ gimpchannel.h \
+ gimpchannel-combine.c \
+ gimpchannel-combine.h \
+ gimpchannel-select.c \
+ gimpchannel-select.h \
+ gimpchannelpropundo.c \
+ gimpchannelpropundo.h \
+ gimpchannelundo.c \
+ gimpchannelundo.h \
+ gimpchunkiterator.c \
+ gimpchunkiterator.h \
+ gimpcontainer.c \
+ gimpcontainer.h \
+ gimpcontainer-filter.c \
+ gimpcontainer-filter.h \
+ gimpcontext.c \
+ gimpcontext.h \
+ gimpcoords.c \
+ gimpcoords.h \
+ gimpcoords-interpolate.c \
+ gimpcoords-interpolate.h \
+ gimpcurve.c \
+ gimpcurve.h \
+ gimpcurve-load.c \
+ gimpcurve-load.h \
+ gimpcurve-map.c \
+ gimpcurve-map.h \
+ gimpcurve-save.c \
+ gimpcurve-save.h \
+ gimpdashpattern.c \
+ gimpdashpattern.h \
+ gimpdata.c \
+ gimpdata.h \
+ gimpdatafactory.c \
+ gimpdatafactory.h \
+ gimpdataloaderfactory.c \
+ gimpdataloaderfactory.h \
+ gimpdocumentlist.c \
+ gimpdocumentlist.h \
+ gimpdrawable.c \
+ gimpdrawable.h \
+ gimpdrawable-bucket-fill.c \
+ gimpdrawable-bucket-fill.h \
+ gimpdrawable-combine.c \
+ gimpdrawable-combine.h \
+ gimpdrawable-edit.c \
+ gimpdrawable-edit.h \
+ gimpdrawable-equalize.c \
+ gimpdrawable-equalize.h \
+ gimpdrawable-fill.c \
+ gimpdrawable-fill.h \
+ gimpdrawable-filters.c \
+ gimpdrawable-filters.h \
+ gimpdrawable-floating-selection.c \
+ gimpdrawable-floating-selection.h \
+ gimpdrawable-foreground-extract.c \
+ gimpdrawable-foreground-extract.h \
+ gimpdrawable-gradient.c \
+ gimpdrawable-gradient.h \
+ gimpdrawable-histogram.c \
+ gimpdrawable-histogram.h \
+ gimpdrawable-levels.c \
+ gimpdrawable-levels.h \
+ gimpdrawable-offset.c \
+ gimpdrawable-offset.h \
+ gimpdrawable-operation.c \
+ gimpdrawable-operation.h \
+ gimpdrawable-preview.c \
+ gimpdrawable-preview.h \
+ gimpdrawable-private.h \
+ gimpdrawable-shadow.c \
+ gimpdrawable-shadow.h \
+ gimpdrawable-stroke.c \
+ gimpdrawable-stroke.h \
+ gimpdrawable-transform.c \
+ gimpdrawable-transform.h \
+ gimpdrawablefilter.c \
+ gimpdrawablefilter.h \
+ gimpdrawablemodundo.c \
+ gimpdrawablemodundo.h \
+ gimpdrawablestack.c \
+ gimpdrawablestack.h \
+ gimpdrawableundo.c \
+ gimpdrawableundo.h \
+ gimpdynamics.c \
+ gimpdynamics.h \
+ gimpdynamics-load.c \
+ gimpdynamics-load.h \
+ gimpdynamics-save.c \
+ gimpdynamics-save.h \
+ gimpdynamicsoutput.c \
+ gimpdynamicsoutput.h \
+ gimperror.c \
+ gimperror.h \
+ gimpfilloptions.c \
+ gimpfilloptions.h \
+ gimpfilter.c \
+ gimpfilter.h \
+ gimpfilteredcontainer.c \
+ gimpfilteredcontainer.h \
+ gimpfilterstack.c \
+ gimpfilterstack.h \
+ gimpfloatingselectionundo.c \
+ gimpfloatingselectionundo.h \
+ gimpgradient.c \
+ gimpgradient.h \
+ gimpgradient-load.c \
+ gimpgradient-load.h \
+ gimpgradient-save.c \
+ gimpgradient-save.h \
+ gimpgrid.c \
+ gimpgrid.h \
+ gimpgrouplayer.c \
+ gimpgrouplayer.h \
+ gimpgrouplayerundo.c \
+ gimpgrouplayerundo.h \
+ gimpguide.c \
+ gimpguide.h \
+ gimpguideundo.c \
+ gimpguideundo.h \
+ gimphistogram.c \
+ gimphistogram.h \
+ gimpidtable.c \
+ gimpidtable.h \
+ gimpimage.c \
+ gimpimage.h \
+ gimpimage-arrange.c \
+ gimpimage-arrange.h \
+ gimpimage-color-profile.c \
+ gimpimage-color-profile.h \
+ gimpimage-colormap.c \
+ gimpimage-colormap.h \
+ gimpimage-convert-indexed.c \
+ gimpimage-convert-indexed.h \
+ gimpimage-convert-fsdither.h \
+ gimpimage-convert-data.h \
+ gimpimage-convert-precision.c \
+ gimpimage-convert-precision.h \
+ gimpimage-convert-type.c \
+ gimpimage-convert-type.h \
+ gimpimage-crop.c \
+ gimpimage-crop.h \
+ gimpimage-duplicate.c \
+ gimpimage-duplicate.h \
+ gimpimage-flip.c \
+ gimpimage-flip.h \
+ gimpimage-grid.h \
+ gimpimage-grid.c \
+ gimpimage-guides.c \
+ gimpimage-guides.h \
+ gimpimage-item-list.c \
+ gimpimage-item-list.h \
+ gimpimage-merge.c \
+ gimpimage-merge.h \
+ gimpimage-metadata.c \
+ gimpimage-metadata.h \
+ gimpimage-new.c \
+ gimpimage-new.h \
+ gimpimage-pick-color.c \
+ gimpimage-pick-color.h \
+ gimpimage-pick-item.c \
+ gimpimage-pick-item.h \
+ gimpimage-preview.c \
+ gimpimage-preview.h \
+ gimpimage-private.h \
+ gimpimage-quick-mask.c \
+ gimpimage-quick-mask.h \
+ gimpimage-resize.c \
+ gimpimage-resize.h \
+ gimpimage-rotate.c \
+ gimpimage-rotate.h \
+ gimpimage-sample-points.c \
+ gimpimage-sample-points.h \
+ gimpimage-scale.c \
+ gimpimage-scale.h \
+ gimpimage-snap.c \
+ gimpimage-snap.h \
+ gimpimage-symmetry.c \
+ gimpimage-symmetry.h \
+ gimpimage-transform.c \
+ gimpimage-transform.h \
+ gimpimage-undo.c \
+ gimpimage-undo.h \
+ gimpimage-undo-push.c \
+ gimpimage-undo-push.h \
+ gimpimageproxy.c \
+ gimpimageproxy.h \
+ gimpimageundo.c \
+ gimpimageundo.h \
+ gimpimagefile.c \
+ gimpimagefile.h \
+ gimpitem.c \
+ gimpitem.h \
+ gimpitem-exclusive.c \
+ gimpitem-exclusive.h \
+ gimpitem-linked.c \
+ gimpitem-linked.h \
+ gimpitem-preview.c \
+ gimpitem-preview.h \
+ gimpitempropundo.c \
+ gimpitempropundo.h \
+ gimpitemstack.c \
+ gimpitemstack.h \
+ gimpitemtree.c \
+ gimpitemtree.h \
+ gimpitemundo.c \
+ gimpitemundo.h \
+ gimplayer.c \
+ gimplayer.h \
+ gimplayer-floating-selection.c \
+ gimplayer-floating-selection.h \
+ gimplayer-new.c \
+ gimplayer-new.h \
+ gimplayermask.c \
+ gimplayermask.h \
+ gimplayermaskpropundo.c \
+ gimplayermaskpropundo.h \
+ gimplayermaskundo.c \
+ gimplayermaskundo.h \
+ gimplayerpropundo.c \
+ gimplayerpropundo.h \
+ gimplayerstack.c \
+ gimplayerstack.h \
+ gimplayerundo.c \
+ gimplayerundo.h \
+ gimplineart.c \
+ gimplineart.h \
+ gimplist.c \
+ gimplist.h \
+ gimpmaskundo.c \
+ gimpmaskundo.h \
+ gimpmybrush.c \
+ gimpmybrush.h \
+ gimpmybrush-load.c \
+ gimpmybrush-load.h \
+ gimpmybrush-private.h \
+ gimpobject.c \
+ gimpobject.h \
+ gimpobjectqueue.c \
+ gimpobjectqueue.h \
+ gimppaintinfo.c \
+ gimppaintinfo.h \
+ gimppattern.c \
+ gimppattern.h \
+ gimppattern-header.h \
+ gimppattern-load.c \
+ gimppattern-load.h \
+ gimppattern-save.c \
+ gimppattern-save.h \
+ gimppatternclipboard.c \
+ gimppatternclipboard.h \
+ gimppalette.c \
+ gimppalette.h \
+ gimppalette-import.c \
+ gimppalette-import.h \
+ gimppalette-load.c \
+ gimppalette-load.h \
+ gimppalette-save.c \
+ gimppalette-save.h \
+ gimppalettemru.c \
+ gimppalettemru.h \
+ gimpparamspecs.c \
+ gimpparamspecs.h \
+ gimpparamspecs-desc.c \
+ gimpparamspecs-desc.h \
+ gimpparamspecs-duplicate.c \
+ gimpparamspecs-duplicate.h \
+ gimpparasitelist.c \
+ gimpparasitelist.h \
+ gimppdbprogress.c \
+ gimppdbprogress.h \
+ gimppickable.c \
+ gimppickable.h \
+ gimppickable-auto-shrink.c \
+ gimppickable-auto-shrink.h \
+ gimppickable-contiguous-region.cc \
+ gimppickable-contiguous-region.h \
+ gimpprogress.c \
+ gimpprogress.h \
+ gimpprojectable.c \
+ gimpprojectable.h \
+ gimpprojection.c \
+ gimpprojection.h \
+ gimpsamplepoint.c \
+ gimpsamplepoint.h \
+ gimpsamplepointundo.c \
+ gimpsamplepointundo.h \
+ gimpscanconvert.c \
+ gimpscanconvert.h \
+ gimpselection.c \
+ gimpselection.h \
+ gimpsettings.c \
+ gimpsettings.h \
+ gimpstrokeoptions.c \
+ gimpstrokeoptions.h \
+ gimpsubprogress.c \
+ gimpsubprogress.h \
+ gimpsymmetry.c \
+ gimpsymmetry.h \
+ gimpsymmetry-mandala.c \
+ gimpsymmetry-mandala.h \
+ gimpsymmetry-mirror.c \
+ gimpsymmetry-mirror.h \
+ gimpsymmetry-tiling.c \
+ gimpsymmetry-tiling.h \
+ gimptag.c \
+ gimptag.h \
+ gimptagcache.c \
+ gimptagcache.h \
+ gimptagged.c \
+ gimptagged.h \
+ gimptaggedcontainer.c \
+ gimptaggedcontainer.h \
+ gimptempbuf.c \
+ gimptempbuf.h \
+ gimptemplate.c \
+ gimptemplate.h \
+ gimptilehandlerprojectable.c \
+ gimptilehandlerprojectable.h \
+ gimptoolgroup.c \
+ gimptoolgroup.h \
+ gimptoolinfo.c \
+ gimptoolinfo.h \
+ gimptoolitem.c \
+ gimptoolitem.h \
+ gimptooloptions.c \
+ gimptooloptions.h \
+ gimptoolpreset.c \
+ gimptoolpreset.h \
+ gimptoolpreset-load.c \
+ gimptoolpreset-load.h \
+ gimptoolpreset-save.c \
+ gimptoolpreset-save.h \
+ gimptreehandler.c \
+ gimptreehandler.h \
+ gimptreeproxy.c \
+ gimptreeproxy.h \
+ gimptriviallycancelablewaitable.c \
+ gimptriviallycancelablewaitable.h \
+ gimpuncancelablewaitable.c \
+ gimpuncancelablewaitable.h \
+ gimpunit.c \
+ gimpunit.h \
+ gimpundo.c \
+ gimpundo.h \
+ gimpundostack.c \
+ gimpundostack.h \
+ gimpviewable.c \
+ gimpviewable.h \
+ gimpwaitable.c \
+ gimpwaitable.h
+
+libappcore_a_built_sources = \
+ core-enums.c \
+ gimpmarshal.c \
+ gimpmarshal.h
+
+libappcore_a_extra_sources = \
+ gimpmarshal.list
+
+libappcore_a_SOURCES = $(libappcore_a_built_sources) $(libappcore_a_sources)
+BUILT_SOURCES = \
+ $(libappcore_a_built_sources)
+
+EXTRA_DIST = \
+ $(libappcore_a_extra_sources)
+
+
+#
+# rules to generate built sources
+#
+# setup autogeneration dependencies
+gen_sources = xgen-gmh xgen-gmc xgen-cec
+CLEANFILES = $(gen_sources)
+all: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .cc .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu app/core/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --gnu app/core/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstLIBRARIES:
+ -test -z "$(noinst_LIBRARIES)" || rm -f $(noinst_LIBRARIES)
+
+libappcore.a: $(libappcore_a_OBJECTS) $(libappcore_a_DEPENDENCIES) $(EXTRA_libappcore_a_DEPENDENCIES)
+ $(AM_V_at)-rm -f libappcore.a
+ $(AM_V_AR)$(libappcore_a_AR) libappcore.a $(libappcore_a_OBJECTS) $(libappcore_a_LIBADD)
+ $(AM_V_at)$(RANLIB) libappcore.a
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/core-enums.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimp-atomic.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimp-batch.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimp-cairo.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimp-contexts.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimp-data-factories.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimp-edit.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimp-filter-history.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimp-gradients.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimp-gui.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimp-internal-data.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimp-memsize.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimp-modules.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimp-palettes.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimp-parallel.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimp-parasites.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimp-spawn.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimp-tags.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimp-templates.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimp-transform-3d-utils.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimp-transform-resize.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimp-transform-utils.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimp-units.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimp-user-install.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimp-utils.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimp.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpasync.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpasyncset.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpauxitem.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpauxitemundo.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpbacktrace-linux.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpbacktrace-none.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpbacktrace-windows.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpbezierdesc.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpboundary.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpbrush-boundary.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpbrush-load.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpbrush-mipmap.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpbrush-save.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpbrush-transform.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpbrush.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpbrushcache.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpbrushclipboard.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpbrushgenerated-load.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpbrushgenerated-save.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpbrushgenerated.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpbrushpipe-load.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpbrushpipe-save.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpbrushpipe.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpbuffer.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcancelable.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpchannel-combine.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpchannel-select.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpchannel.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpchannelpropundo.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpchannelundo.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpchunkiterator.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcontainer-filter.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcontainer.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcontext.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcoords-interpolate.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcoords.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcurve-load.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcurve-map.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcurve-save.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcurve.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdashpattern.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdata.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdatafactory.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdataloaderfactory.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdocumentlist.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdrawable-bucket-fill.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdrawable-combine.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdrawable-edit.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdrawable-equalize.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdrawable-fill.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdrawable-filters.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdrawable-floating-selection.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdrawable-foreground-extract.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdrawable-gradient.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdrawable-histogram.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdrawable-levels.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdrawable-offset.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdrawable-operation.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdrawable-preview.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdrawable-shadow.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdrawable-stroke.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdrawable-transform.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdrawable.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdrawablefilter.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdrawablemodundo.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdrawablestack.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdrawableundo.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdynamics-load.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdynamics-save.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdynamics.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdynamicsoutput.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimperror.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpfilloptions.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpfilter.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpfilteredcontainer.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpfilterstack.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpfloatingselectionundo.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpgradient-load.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpgradient-save.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpgradient.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpgrid.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpgrouplayer.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpgrouplayerundo.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpguide.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpguideundo.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimphistogram.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpidtable.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpimage-arrange.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpimage-color-profile.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpimage-colormap.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpimage-convert-indexed.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpimage-convert-precision.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpimage-convert-type.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpimage-crop.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpimage-duplicate.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpimage-flip.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpimage-grid.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpimage-guides.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpimage-item-list.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpimage-merge.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpimage-metadata.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpimage-new.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpimage-pick-color.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpimage-pick-item.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpimage-preview.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpimage-quick-mask.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpimage-resize.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpimage-rotate.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpimage-sample-points.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpimage-scale.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpimage-snap.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpimage-symmetry.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpimage-transform.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpimage-undo-push.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpimage-undo.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpimage.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpimagefile.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpimageproxy.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpimageundo.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpitem-exclusive.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpitem-linked.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpitem-preview.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpitem.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpitempropundo.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpitemstack.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpitemtree.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpitemundo.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimplayer-floating-selection.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimplayer-new.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimplayer.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimplayermask.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimplayermaskpropundo.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimplayermaskundo.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimplayerpropundo.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimplayerstack.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimplayerundo.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimplineart.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimplist.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpmarshal.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpmaskundo.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpmybrush-load.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpmybrush.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpobject.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpobjectqueue.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimppaintinfo.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimppalette-import.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimppalette-load.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimppalette-save.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimppalette.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimppalettemru.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpparamspecs-desc.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpparamspecs-duplicate.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpparamspecs.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpparasitelist.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimppattern-load.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimppattern-save.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimppattern.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimppatternclipboard.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimppdbprogress.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimppickable-auto-shrink.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimppickable-contiguous-region.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimppickable.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpprogress.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpprojectable.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpprojection.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpsamplepoint.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpsamplepointundo.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpscanconvert.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpselection.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpsettings.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpstrokeoptions.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpsubprogress.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpsymmetry-mandala.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpsymmetry-mirror.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpsymmetry-tiling.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpsymmetry.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimptag.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimptagcache.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimptagged.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimptaggedcontainer.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimptempbuf.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimptemplate.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimptilehandlerprojectable.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimptoolgroup.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimptoolinfo.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimptoolitem.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimptooloptions.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimptoolpreset-load.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimptoolpreset-save.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimptoolpreset.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimptreehandler.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimptreeproxy.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimptriviallycancelablewaitable.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpuncancelablewaitable.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpundo.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpundostack.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpunit.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpviewable.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpwaitable.Po@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+.cc.o:
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ $<
+
+.cc.obj:
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.cc.lo:
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LTCXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LTCXXCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) check-am
+all-am: Makefile $(LIBRARIES)
+installdirs:
+install: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) install-am
+install-exec: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+ -test -z "$(CLEANFILES)" || rm -f $(CLEANFILES)
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+ -test -z "$(BUILT_SOURCES)" || rm -f $(BUILT_SOURCES)
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-noinstLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/core-enums.Po
+ -rm -f ./$(DEPDIR)/gimp-atomic.Po
+ -rm -f ./$(DEPDIR)/gimp-batch.Po
+ -rm -f ./$(DEPDIR)/gimp-cairo.Po
+ -rm -f ./$(DEPDIR)/gimp-contexts.Po
+ -rm -f ./$(DEPDIR)/gimp-data-factories.Po
+ -rm -f ./$(DEPDIR)/gimp-edit.Po
+ -rm -f ./$(DEPDIR)/gimp-filter-history.Po
+ -rm -f ./$(DEPDIR)/gimp-gradients.Po
+ -rm -f ./$(DEPDIR)/gimp-gui.Po
+ -rm -f ./$(DEPDIR)/gimp-internal-data.Po
+ -rm -f ./$(DEPDIR)/gimp-memsize.Po
+ -rm -f ./$(DEPDIR)/gimp-modules.Po
+ -rm -f ./$(DEPDIR)/gimp-palettes.Po
+ -rm -f ./$(DEPDIR)/gimp-parallel.Po
+ -rm -f ./$(DEPDIR)/gimp-parasites.Po
+ -rm -f ./$(DEPDIR)/gimp-spawn.Po
+ -rm -f ./$(DEPDIR)/gimp-tags.Po
+ -rm -f ./$(DEPDIR)/gimp-templates.Po
+ -rm -f ./$(DEPDIR)/gimp-transform-3d-utils.Po
+ -rm -f ./$(DEPDIR)/gimp-transform-resize.Po
+ -rm -f ./$(DEPDIR)/gimp-transform-utils.Po
+ -rm -f ./$(DEPDIR)/gimp-units.Po
+ -rm -f ./$(DEPDIR)/gimp-user-install.Po
+ -rm -f ./$(DEPDIR)/gimp-utils.Po
+ -rm -f ./$(DEPDIR)/gimp.Po
+ -rm -f ./$(DEPDIR)/gimpasync.Po
+ -rm -f ./$(DEPDIR)/gimpasyncset.Po
+ -rm -f ./$(DEPDIR)/gimpauxitem.Po
+ -rm -f ./$(DEPDIR)/gimpauxitemundo.Po
+ -rm -f ./$(DEPDIR)/gimpbacktrace-linux.Po
+ -rm -f ./$(DEPDIR)/gimpbacktrace-none.Po
+ -rm -f ./$(DEPDIR)/gimpbacktrace-windows.Po
+ -rm -f ./$(DEPDIR)/gimpbezierdesc.Po
+ -rm -f ./$(DEPDIR)/gimpboundary.Po
+ -rm -f ./$(DEPDIR)/gimpbrush-boundary.Po
+ -rm -f ./$(DEPDIR)/gimpbrush-load.Po
+ -rm -f ./$(DEPDIR)/gimpbrush-mipmap.Po
+ -rm -f ./$(DEPDIR)/gimpbrush-save.Po
+ -rm -f ./$(DEPDIR)/gimpbrush-transform.Po
+ -rm -f ./$(DEPDIR)/gimpbrush.Po
+ -rm -f ./$(DEPDIR)/gimpbrushcache.Po
+ -rm -f ./$(DEPDIR)/gimpbrushclipboard.Po
+ -rm -f ./$(DEPDIR)/gimpbrushgenerated-load.Po
+ -rm -f ./$(DEPDIR)/gimpbrushgenerated-save.Po
+ -rm -f ./$(DEPDIR)/gimpbrushgenerated.Po
+ -rm -f ./$(DEPDIR)/gimpbrushpipe-load.Po
+ -rm -f ./$(DEPDIR)/gimpbrushpipe-save.Po
+ -rm -f ./$(DEPDIR)/gimpbrushpipe.Po
+ -rm -f ./$(DEPDIR)/gimpbuffer.Po
+ -rm -f ./$(DEPDIR)/gimpcancelable.Po
+ -rm -f ./$(DEPDIR)/gimpchannel-combine.Po
+ -rm -f ./$(DEPDIR)/gimpchannel-select.Po
+ -rm -f ./$(DEPDIR)/gimpchannel.Po
+ -rm -f ./$(DEPDIR)/gimpchannelpropundo.Po
+ -rm -f ./$(DEPDIR)/gimpchannelundo.Po
+ -rm -f ./$(DEPDIR)/gimpchunkiterator.Po
+ -rm -f ./$(DEPDIR)/gimpcontainer-filter.Po
+ -rm -f ./$(DEPDIR)/gimpcontainer.Po
+ -rm -f ./$(DEPDIR)/gimpcontext.Po
+ -rm -f ./$(DEPDIR)/gimpcoords-interpolate.Po
+ -rm -f ./$(DEPDIR)/gimpcoords.Po
+ -rm -f ./$(DEPDIR)/gimpcurve-load.Po
+ -rm -f ./$(DEPDIR)/gimpcurve-map.Po
+ -rm -f ./$(DEPDIR)/gimpcurve-save.Po
+ -rm -f ./$(DEPDIR)/gimpcurve.Po
+ -rm -f ./$(DEPDIR)/gimpdashpattern.Po
+ -rm -f ./$(DEPDIR)/gimpdata.Po
+ -rm -f ./$(DEPDIR)/gimpdatafactory.Po
+ -rm -f ./$(DEPDIR)/gimpdataloaderfactory.Po
+ -rm -f ./$(DEPDIR)/gimpdocumentlist.Po
+ -rm -f ./$(DEPDIR)/gimpdrawable-bucket-fill.Po
+ -rm -f ./$(DEPDIR)/gimpdrawable-combine.Po
+ -rm -f ./$(DEPDIR)/gimpdrawable-edit.Po
+ -rm -f ./$(DEPDIR)/gimpdrawable-equalize.Po
+ -rm -f ./$(DEPDIR)/gimpdrawable-fill.Po
+ -rm -f ./$(DEPDIR)/gimpdrawable-filters.Po
+ -rm -f ./$(DEPDIR)/gimpdrawable-floating-selection.Po
+ -rm -f ./$(DEPDIR)/gimpdrawable-foreground-extract.Po
+ -rm -f ./$(DEPDIR)/gimpdrawable-gradient.Po
+ -rm -f ./$(DEPDIR)/gimpdrawable-histogram.Po
+ -rm -f ./$(DEPDIR)/gimpdrawable-levels.Po
+ -rm -f ./$(DEPDIR)/gimpdrawable-offset.Po
+ -rm -f ./$(DEPDIR)/gimpdrawable-operation.Po
+ -rm -f ./$(DEPDIR)/gimpdrawable-preview.Po
+ -rm -f ./$(DEPDIR)/gimpdrawable-shadow.Po
+ -rm -f ./$(DEPDIR)/gimpdrawable-stroke.Po
+ -rm -f ./$(DEPDIR)/gimpdrawable-transform.Po
+ -rm -f ./$(DEPDIR)/gimpdrawable.Po
+ -rm -f ./$(DEPDIR)/gimpdrawablefilter.Po
+ -rm -f ./$(DEPDIR)/gimpdrawablemodundo.Po
+ -rm -f ./$(DEPDIR)/gimpdrawablestack.Po
+ -rm -f ./$(DEPDIR)/gimpdrawableundo.Po
+ -rm -f ./$(DEPDIR)/gimpdynamics-load.Po
+ -rm -f ./$(DEPDIR)/gimpdynamics-save.Po
+ -rm -f ./$(DEPDIR)/gimpdynamics.Po
+ -rm -f ./$(DEPDIR)/gimpdynamicsoutput.Po
+ -rm -f ./$(DEPDIR)/gimperror.Po
+ -rm -f ./$(DEPDIR)/gimpfilloptions.Po
+ -rm -f ./$(DEPDIR)/gimpfilter.Po
+ -rm -f ./$(DEPDIR)/gimpfilteredcontainer.Po
+ -rm -f ./$(DEPDIR)/gimpfilterstack.Po
+ -rm -f ./$(DEPDIR)/gimpfloatingselectionundo.Po
+ -rm -f ./$(DEPDIR)/gimpgradient-load.Po
+ -rm -f ./$(DEPDIR)/gimpgradient-save.Po
+ -rm -f ./$(DEPDIR)/gimpgradient.Po
+ -rm -f ./$(DEPDIR)/gimpgrid.Po
+ -rm -f ./$(DEPDIR)/gimpgrouplayer.Po
+ -rm -f ./$(DEPDIR)/gimpgrouplayerundo.Po
+ -rm -f ./$(DEPDIR)/gimpguide.Po
+ -rm -f ./$(DEPDIR)/gimpguideundo.Po
+ -rm -f ./$(DEPDIR)/gimphistogram.Po
+ -rm -f ./$(DEPDIR)/gimpidtable.Po
+ -rm -f ./$(DEPDIR)/gimpimage-arrange.Po
+ -rm -f ./$(DEPDIR)/gimpimage-color-profile.Po
+ -rm -f ./$(DEPDIR)/gimpimage-colormap.Po
+ -rm -f ./$(DEPDIR)/gimpimage-convert-indexed.Po
+ -rm -f ./$(DEPDIR)/gimpimage-convert-precision.Po
+ -rm -f ./$(DEPDIR)/gimpimage-convert-type.Po
+ -rm -f ./$(DEPDIR)/gimpimage-crop.Po
+ -rm -f ./$(DEPDIR)/gimpimage-duplicate.Po
+ -rm -f ./$(DEPDIR)/gimpimage-flip.Po
+ -rm -f ./$(DEPDIR)/gimpimage-grid.Po
+ -rm -f ./$(DEPDIR)/gimpimage-guides.Po
+ -rm -f ./$(DEPDIR)/gimpimage-item-list.Po
+ -rm -f ./$(DEPDIR)/gimpimage-merge.Po
+ -rm -f ./$(DEPDIR)/gimpimage-metadata.Po
+ -rm -f ./$(DEPDIR)/gimpimage-new.Po
+ -rm -f ./$(DEPDIR)/gimpimage-pick-color.Po
+ -rm -f ./$(DEPDIR)/gimpimage-pick-item.Po
+ -rm -f ./$(DEPDIR)/gimpimage-preview.Po
+ -rm -f ./$(DEPDIR)/gimpimage-quick-mask.Po
+ -rm -f ./$(DEPDIR)/gimpimage-resize.Po
+ -rm -f ./$(DEPDIR)/gimpimage-rotate.Po
+ -rm -f ./$(DEPDIR)/gimpimage-sample-points.Po
+ -rm -f ./$(DEPDIR)/gimpimage-scale.Po
+ -rm -f ./$(DEPDIR)/gimpimage-snap.Po
+ -rm -f ./$(DEPDIR)/gimpimage-symmetry.Po
+ -rm -f ./$(DEPDIR)/gimpimage-transform.Po
+ -rm -f ./$(DEPDIR)/gimpimage-undo-push.Po
+ -rm -f ./$(DEPDIR)/gimpimage-undo.Po
+ -rm -f ./$(DEPDIR)/gimpimage.Po
+ -rm -f ./$(DEPDIR)/gimpimagefile.Po
+ -rm -f ./$(DEPDIR)/gimpimageproxy.Po
+ -rm -f ./$(DEPDIR)/gimpimageundo.Po
+ -rm -f ./$(DEPDIR)/gimpitem-exclusive.Po
+ -rm -f ./$(DEPDIR)/gimpitem-linked.Po
+ -rm -f ./$(DEPDIR)/gimpitem-preview.Po
+ -rm -f ./$(DEPDIR)/gimpitem.Po
+ -rm -f ./$(DEPDIR)/gimpitempropundo.Po
+ -rm -f ./$(DEPDIR)/gimpitemstack.Po
+ -rm -f ./$(DEPDIR)/gimpitemtree.Po
+ -rm -f ./$(DEPDIR)/gimpitemundo.Po
+ -rm -f ./$(DEPDIR)/gimplayer-floating-selection.Po
+ -rm -f ./$(DEPDIR)/gimplayer-new.Po
+ -rm -f ./$(DEPDIR)/gimplayer.Po
+ -rm -f ./$(DEPDIR)/gimplayermask.Po
+ -rm -f ./$(DEPDIR)/gimplayermaskpropundo.Po
+ -rm -f ./$(DEPDIR)/gimplayermaskundo.Po
+ -rm -f ./$(DEPDIR)/gimplayerpropundo.Po
+ -rm -f ./$(DEPDIR)/gimplayerstack.Po
+ -rm -f ./$(DEPDIR)/gimplayerundo.Po
+ -rm -f ./$(DEPDIR)/gimplineart.Po
+ -rm -f ./$(DEPDIR)/gimplist.Po
+ -rm -f ./$(DEPDIR)/gimpmarshal.Po
+ -rm -f ./$(DEPDIR)/gimpmaskundo.Po
+ -rm -f ./$(DEPDIR)/gimpmybrush-load.Po
+ -rm -f ./$(DEPDIR)/gimpmybrush.Po
+ -rm -f ./$(DEPDIR)/gimpobject.Po
+ -rm -f ./$(DEPDIR)/gimpobjectqueue.Po
+ -rm -f ./$(DEPDIR)/gimppaintinfo.Po
+ -rm -f ./$(DEPDIR)/gimppalette-import.Po
+ -rm -f ./$(DEPDIR)/gimppalette-load.Po
+ -rm -f ./$(DEPDIR)/gimppalette-save.Po
+ -rm -f ./$(DEPDIR)/gimppalette.Po
+ -rm -f ./$(DEPDIR)/gimppalettemru.Po
+ -rm -f ./$(DEPDIR)/gimpparamspecs-desc.Po
+ -rm -f ./$(DEPDIR)/gimpparamspecs-duplicate.Po
+ -rm -f ./$(DEPDIR)/gimpparamspecs.Po
+ -rm -f ./$(DEPDIR)/gimpparasitelist.Po
+ -rm -f ./$(DEPDIR)/gimppattern-load.Po
+ -rm -f ./$(DEPDIR)/gimppattern-save.Po
+ -rm -f ./$(DEPDIR)/gimppattern.Po
+ -rm -f ./$(DEPDIR)/gimppatternclipboard.Po
+ -rm -f ./$(DEPDIR)/gimppdbprogress.Po
+ -rm -f ./$(DEPDIR)/gimppickable-auto-shrink.Po
+ -rm -f ./$(DEPDIR)/gimppickable-contiguous-region.Po
+ -rm -f ./$(DEPDIR)/gimppickable.Po
+ -rm -f ./$(DEPDIR)/gimpprogress.Po
+ -rm -f ./$(DEPDIR)/gimpprojectable.Po
+ -rm -f ./$(DEPDIR)/gimpprojection.Po
+ -rm -f ./$(DEPDIR)/gimpsamplepoint.Po
+ -rm -f ./$(DEPDIR)/gimpsamplepointundo.Po
+ -rm -f ./$(DEPDIR)/gimpscanconvert.Po
+ -rm -f ./$(DEPDIR)/gimpselection.Po
+ -rm -f ./$(DEPDIR)/gimpsettings.Po
+ -rm -f ./$(DEPDIR)/gimpstrokeoptions.Po
+ -rm -f ./$(DEPDIR)/gimpsubprogress.Po
+ -rm -f ./$(DEPDIR)/gimpsymmetry-mandala.Po
+ -rm -f ./$(DEPDIR)/gimpsymmetry-mirror.Po
+ -rm -f ./$(DEPDIR)/gimpsymmetry-tiling.Po
+ -rm -f ./$(DEPDIR)/gimpsymmetry.Po
+ -rm -f ./$(DEPDIR)/gimptag.Po
+ -rm -f ./$(DEPDIR)/gimptagcache.Po
+ -rm -f ./$(DEPDIR)/gimptagged.Po
+ -rm -f ./$(DEPDIR)/gimptaggedcontainer.Po
+ -rm -f ./$(DEPDIR)/gimptempbuf.Po
+ -rm -f ./$(DEPDIR)/gimptemplate.Po
+ -rm -f ./$(DEPDIR)/gimptilehandlerprojectable.Po
+ -rm -f ./$(DEPDIR)/gimptoolgroup.Po
+ -rm -f ./$(DEPDIR)/gimptoolinfo.Po
+ -rm -f ./$(DEPDIR)/gimptoolitem.Po
+ -rm -f ./$(DEPDIR)/gimptooloptions.Po
+ -rm -f ./$(DEPDIR)/gimptoolpreset-load.Po
+ -rm -f ./$(DEPDIR)/gimptoolpreset-save.Po
+ -rm -f ./$(DEPDIR)/gimptoolpreset.Po
+ -rm -f ./$(DEPDIR)/gimptreehandler.Po
+ -rm -f ./$(DEPDIR)/gimptreeproxy.Po
+ -rm -f ./$(DEPDIR)/gimptriviallycancelablewaitable.Po
+ -rm -f ./$(DEPDIR)/gimpuncancelablewaitable.Po
+ -rm -f ./$(DEPDIR)/gimpundo.Po
+ -rm -f ./$(DEPDIR)/gimpundostack.Po
+ -rm -f ./$(DEPDIR)/gimpunit.Po
+ -rm -f ./$(DEPDIR)/gimpviewable.Po
+ -rm -f ./$(DEPDIR)/gimpwaitable.Po
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/core-enums.Po
+ -rm -f ./$(DEPDIR)/gimp-atomic.Po
+ -rm -f ./$(DEPDIR)/gimp-batch.Po
+ -rm -f ./$(DEPDIR)/gimp-cairo.Po
+ -rm -f ./$(DEPDIR)/gimp-contexts.Po
+ -rm -f ./$(DEPDIR)/gimp-data-factories.Po
+ -rm -f ./$(DEPDIR)/gimp-edit.Po
+ -rm -f ./$(DEPDIR)/gimp-filter-history.Po
+ -rm -f ./$(DEPDIR)/gimp-gradients.Po
+ -rm -f ./$(DEPDIR)/gimp-gui.Po
+ -rm -f ./$(DEPDIR)/gimp-internal-data.Po
+ -rm -f ./$(DEPDIR)/gimp-memsize.Po
+ -rm -f ./$(DEPDIR)/gimp-modules.Po
+ -rm -f ./$(DEPDIR)/gimp-palettes.Po
+ -rm -f ./$(DEPDIR)/gimp-parallel.Po
+ -rm -f ./$(DEPDIR)/gimp-parasites.Po
+ -rm -f ./$(DEPDIR)/gimp-spawn.Po
+ -rm -f ./$(DEPDIR)/gimp-tags.Po
+ -rm -f ./$(DEPDIR)/gimp-templates.Po
+ -rm -f ./$(DEPDIR)/gimp-transform-3d-utils.Po
+ -rm -f ./$(DEPDIR)/gimp-transform-resize.Po
+ -rm -f ./$(DEPDIR)/gimp-transform-utils.Po
+ -rm -f ./$(DEPDIR)/gimp-units.Po
+ -rm -f ./$(DEPDIR)/gimp-user-install.Po
+ -rm -f ./$(DEPDIR)/gimp-utils.Po
+ -rm -f ./$(DEPDIR)/gimp.Po
+ -rm -f ./$(DEPDIR)/gimpasync.Po
+ -rm -f ./$(DEPDIR)/gimpasyncset.Po
+ -rm -f ./$(DEPDIR)/gimpauxitem.Po
+ -rm -f ./$(DEPDIR)/gimpauxitemundo.Po
+ -rm -f ./$(DEPDIR)/gimpbacktrace-linux.Po
+ -rm -f ./$(DEPDIR)/gimpbacktrace-none.Po
+ -rm -f ./$(DEPDIR)/gimpbacktrace-windows.Po
+ -rm -f ./$(DEPDIR)/gimpbezierdesc.Po
+ -rm -f ./$(DEPDIR)/gimpboundary.Po
+ -rm -f ./$(DEPDIR)/gimpbrush-boundary.Po
+ -rm -f ./$(DEPDIR)/gimpbrush-load.Po
+ -rm -f ./$(DEPDIR)/gimpbrush-mipmap.Po
+ -rm -f ./$(DEPDIR)/gimpbrush-save.Po
+ -rm -f ./$(DEPDIR)/gimpbrush-transform.Po
+ -rm -f ./$(DEPDIR)/gimpbrush.Po
+ -rm -f ./$(DEPDIR)/gimpbrushcache.Po
+ -rm -f ./$(DEPDIR)/gimpbrushclipboard.Po
+ -rm -f ./$(DEPDIR)/gimpbrushgenerated-load.Po
+ -rm -f ./$(DEPDIR)/gimpbrushgenerated-save.Po
+ -rm -f ./$(DEPDIR)/gimpbrushgenerated.Po
+ -rm -f ./$(DEPDIR)/gimpbrushpipe-load.Po
+ -rm -f ./$(DEPDIR)/gimpbrushpipe-save.Po
+ -rm -f ./$(DEPDIR)/gimpbrushpipe.Po
+ -rm -f ./$(DEPDIR)/gimpbuffer.Po
+ -rm -f ./$(DEPDIR)/gimpcancelable.Po
+ -rm -f ./$(DEPDIR)/gimpchannel-combine.Po
+ -rm -f ./$(DEPDIR)/gimpchannel-select.Po
+ -rm -f ./$(DEPDIR)/gimpchannel.Po
+ -rm -f ./$(DEPDIR)/gimpchannelpropundo.Po
+ -rm -f ./$(DEPDIR)/gimpchannelundo.Po
+ -rm -f ./$(DEPDIR)/gimpchunkiterator.Po
+ -rm -f ./$(DEPDIR)/gimpcontainer-filter.Po
+ -rm -f ./$(DEPDIR)/gimpcontainer.Po
+ -rm -f ./$(DEPDIR)/gimpcontext.Po
+ -rm -f ./$(DEPDIR)/gimpcoords-interpolate.Po
+ -rm -f ./$(DEPDIR)/gimpcoords.Po
+ -rm -f ./$(DEPDIR)/gimpcurve-load.Po
+ -rm -f ./$(DEPDIR)/gimpcurve-map.Po
+ -rm -f ./$(DEPDIR)/gimpcurve-save.Po
+ -rm -f ./$(DEPDIR)/gimpcurve.Po
+ -rm -f ./$(DEPDIR)/gimpdashpattern.Po
+ -rm -f ./$(DEPDIR)/gimpdata.Po
+ -rm -f ./$(DEPDIR)/gimpdatafactory.Po
+ -rm -f ./$(DEPDIR)/gimpdataloaderfactory.Po
+ -rm -f ./$(DEPDIR)/gimpdocumentlist.Po
+ -rm -f ./$(DEPDIR)/gimpdrawable-bucket-fill.Po
+ -rm -f ./$(DEPDIR)/gimpdrawable-combine.Po
+ -rm -f ./$(DEPDIR)/gimpdrawable-edit.Po
+ -rm -f ./$(DEPDIR)/gimpdrawable-equalize.Po
+ -rm -f ./$(DEPDIR)/gimpdrawable-fill.Po
+ -rm -f ./$(DEPDIR)/gimpdrawable-filters.Po
+ -rm -f ./$(DEPDIR)/gimpdrawable-floating-selection.Po
+ -rm -f ./$(DEPDIR)/gimpdrawable-foreground-extract.Po
+ -rm -f ./$(DEPDIR)/gimpdrawable-gradient.Po
+ -rm -f ./$(DEPDIR)/gimpdrawable-histogram.Po
+ -rm -f ./$(DEPDIR)/gimpdrawable-levels.Po
+ -rm -f ./$(DEPDIR)/gimpdrawable-offset.Po
+ -rm -f ./$(DEPDIR)/gimpdrawable-operation.Po
+ -rm -f ./$(DEPDIR)/gimpdrawable-preview.Po
+ -rm -f ./$(DEPDIR)/gimpdrawable-shadow.Po
+ -rm -f ./$(DEPDIR)/gimpdrawable-stroke.Po
+ -rm -f ./$(DEPDIR)/gimpdrawable-transform.Po
+ -rm -f ./$(DEPDIR)/gimpdrawable.Po
+ -rm -f ./$(DEPDIR)/gimpdrawablefilter.Po
+ -rm -f ./$(DEPDIR)/gimpdrawablemodundo.Po
+ -rm -f ./$(DEPDIR)/gimpdrawablestack.Po
+ -rm -f ./$(DEPDIR)/gimpdrawableundo.Po
+ -rm -f ./$(DEPDIR)/gimpdynamics-load.Po
+ -rm -f ./$(DEPDIR)/gimpdynamics-save.Po
+ -rm -f ./$(DEPDIR)/gimpdynamics.Po
+ -rm -f ./$(DEPDIR)/gimpdynamicsoutput.Po
+ -rm -f ./$(DEPDIR)/gimperror.Po
+ -rm -f ./$(DEPDIR)/gimpfilloptions.Po
+ -rm -f ./$(DEPDIR)/gimpfilter.Po
+ -rm -f ./$(DEPDIR)/gimpfilteredcontainer.Po
+ -rm -f ./$(DEPDIR)/gimpfilterstack.Po
+ -rm -f ./$(DEPDIR)/gimpfloatingselectionundo.Po
+ -rm -f ./$(DEPDIR)/gimpgradient-load.Po
+ -rm -f ./$(DEPDIR)/gimpgradient-save.Po
+ -rm -f ./$(DEPDIR)/gimpgradient.Po
+ -rm -f ./$(DEPDIR)/gimpgrid.Po
+ -rm -f ./$(DEPDIR)/gimpgrouplayer.Po
+ -rm -f ./$(DEPDIR)/gimpgrouplayerundo.Po
+ -rm -f ./$(DEPDIR)/gimpguide.Po
+ -rm -f ./$(DEPDIR)/gimpguideundo.Po
+ -rm -f ./$(DEPDIR)/gimphistogram.Po
+ -rm -f ./$(DEPDIR)/gimpidtable.Po
+ -rm -f ./$(DEPDIR)/gimpimage-arrange.Po
+ -rm -f ./$(DEPDIR)/gimpimage-color-profile.Po
+ -rm -f ./$(DEPDIR)/gimpimage-colormap.Po
+ -rm -f ./$(DEPDIR)/gimpimage-convert-indexed.Po
+ -rm -f ./$(DEPDIR)/gimpimage-convert-precision.Po
+ -rm -f ./$(DEPDIR)/gimpimage-convert-type.Po
+ -rm -f ./$(DEPDIR)/gimpimage-crop.Po
+ -rm -f ./$(DEPDIR)/gimpimage-duplicate.Po
+ -rm -f ./$(DEPDIR)/gimpimage-flip.Po
+ -rm -f ./$(DEPDIR)/gimpimage-grid.Po
+ -rm -f ./$(DEPDIR)/gimpimage-guides.Po
+ -rm -f ./$(DEPDIR)/gimpimage-item-list.Po
+ -rm -f ./$(DEPDIR)/gimpimage-merge.Po
+ -rm -f ./$(DEPDIR)/gimpimage-metadata.Po
+ -rm -f ./$(DEPDIR)/gimpimage-new.Po
+ -rm -f ./$(DEPDIR)/gimpimage-pick-color.Po
+ -rm -f ./$(DEPDIR)/gimpimage-pick-item.Po
+ -rm -f ./$(DEPDIR)/gimpimage-preview.Po
+ -rm -f ./$(DEPDIR)/gimpimage-quick-mask.Po
+ -rm -f ./$(DEPDIR)/gimpimage-resize.Po
+ -rm -f ./$(DEPDIR)/gimpimage-rotate.Po
+ -rm -f ./$(DEPDIR)/gimpimage-sample-points.Po
+ -rm -f ./$(DEPDIR)/gimpimage-scale.Po
+ -rm -f ./$(DEPDIR)/gimpimage-snap.Po
+ -rm -f ./$(DEPDIR)/gimpimage-symmetry.Po
+ -rm -f ./$(DEPDIR)/gimpimage-transform.Po
+ -rm -f ./$(DEPDIR)/gimpimage-undo-push.Po
+ -rm -f ./$(DEPDIR)/gimpimage-undo.Po
+ -rm -f ./$(DEPDIR)/gimpimage.Po
+ -rm -f ./$(DEPDIR)/gimpimagefile.Po
+ -rm -f ./$(DEPDIR)/gimpimageproxy.Po
+ -rm -f ./$(DEPDIR)/gimpimageundo.Po
+ -rm -f ./$(DEPDIR)/gimpitem-exclusive.Po
+ -rm -f ./$(DEPDIR)/gimpitem-linked.Po
+ -rm -f ./$(DEPDIR)/gimpitem-preview.Po
+ -rm -f ./$(DEPDIR)/gimpitem.Po
+ -rm -f ./$(DEPDIR)/gimpitempropundo.Po
+ -rm -f ./$(DEPDIR)/gimpitemstack.Po
+ -rm -f ./$(DEPDIR)/gimpitemtree.Po
+ -rm -f ./$(DEPDIR)/gimpitemundo.Po
+ -rm -f ./$(DEPDIR)/gimplayer-floating-selection.Po
+ -rm -f ./$(DEPDIR)/gimplayer-new.Po
+ -rm -f ./$(DEPDIR)/gimplayer.Po
+ -rm -f ./$(DEPDIR)/gimplayermask.Po
+ -rm -f ./$(DEPDIR)/gimplayermaskpropundo.Po
+ -rm -f ./$(DEPDIR)/gimplayermaskundo.Po
+ -rm -f ./$(DEPDIR)/gimplayerpropundo.Po
+ -rm -f ./$(DEPDIR)/gimplayerstack.Po
+ -rm -f ./$(DEPDIR)/gimplayerundo.Po
+ -rm -f ./$(DEPDIR)/gimplineart.Po
+ -rm -f ./$(DEPDIR)/gimplist.Po
+ -rm -f ./$(DEPDIR)/gimpmarshal.Po
+ -rm -f ./$(DEPDIR)/gimpmaskundo.Po
+ -rm -f ./$(DEPDIR)/gimpmybrush-load.Po
+ -rm -f ./$(DEPDIR)/gimpmybrush.Po
+ -rm -f ./$(DEPDIR)/gimpobject.Po
+ -rm -f ./$(DEPDIR)/gimpobjectqueue.Po
+ -rm -f ./$(DEPDIR)/gimppaintinfo.Po
+ -rm -f ./$(DEPDIR)/gimppalette-import.Po
+ -rm -f ./$(DEPDIR)/gimppalette-load.Po
+ -rm -f ./$(DEPDIR)/gimppalette-save.Po
+ -rm -f ./$(DEPDIR)/gimppalette.Po
+ -rm -f ./$(DEPDIR)/gimppalettemru.Po
+ -rm -f ./$(DEPDIR)/gimpparamspecs-desc.Po
+ -rm -f ./$(DEPDIR)/gimpparamspecs-duplicate.Po
+ -rm -f ./$(DEPDIR)/gimpparamspecs.Po
+ -rm -f ./$(DEPDIR)/gimpparasitelist.Po
+ -rm -f ./$(DEPDIR)/gimppattern-load.Po
+ -rm -f ./$(DEPDIR)/gimppattern-save.Po
+ -rm -f ./$(DEPDIR)/gimppattern.Po
+ -rm -f ./$(DEPDIR)/gimppatternclipboard.Po
+ -rm -f ./$(DEPDIR)/gimppdbprogress.Po
+ -rm -f ./$(DEPDIR)/gimppickable-auto-shrink.Po
+ -rm -f ./$(DEPDIR)/gimppickable-contiguous-region.Po
+ -rm -f ./$(DEPDIR)/gimppickable.Po
+ -rm -f ./$(DEPDIR)/gimpprogress.Po
+ -rm -f ./$(DEPDIR)/gimpprojectable.Po
+ -rm -f ./$(DEPDIR)/gimpprojection.Po
+ -rm -f ./$(DEPDIR)/gimpsamplepoint.Po
+ -rm -f ./$(DEPDIR)/gimpsamplepointundo.Po
+ -rm -f ./$(DEPDIR)/gimpscanconvert.Po
+ -rm -f ./$(DEPDIR)/gimpselection.Po
+ -rm -f ./$(DEPDIR)/gimpsettings.Po
+ -rm -f ./$(DEPDIR)/gimpstrokeoptions.Po
+ -rm -f ./$(DEPDIR)/gimpsubprogress.Po
+ -rm -f ./$(DEPDIR)/gimpsymmetry-mandala.Po
+ -rm -f ./$(DEPDIR)/gimpsymmetry-mirror.Po
+ -rm -f ./$(DEPDIR)/gimpsymmetry-tiling.Po
+ -rm -f ./$(DEPDIR)/gimpsymmetry.Po
+ -rm -f ./$(DEPDIR)/gimptag.Po
+ -rm -f ./$(DEPDIR)/gimptagcache.Po
+ -rm -f ./$(DEPDIR)/gimptagged.Po
+ -rm -f ./$(DEPDIR)/gimptaggedcontainer.Po
+ -rm -f ./$(DEPDIR)/gimptempbuf.Po
+ -rm -f ./$(DEPDIR)/gimptemplate.Po
+ -rm -f ./$(DEPDIR)/gimptilehandlerprojectable.Po
+ -rm -f ./$(DEPDIR)/gimptoolgroup.Po
+ -rm -f ./$(DEPDIR)/gimptoolinfo.Po
+ -rm -f ./$(DEPDIR)/gimptoolitem.Po
+ -rm -f ./$(DEPDIR)/gimptooloptions.Po
+ -rm -f ./$(DEPDIR)/gimptoolpreset-load.Po
+ -rm -f ./$(DEPDIR)/gimptoolpreset-save.Po
+ -rm -f ./$(DEPDIR)/gimptoolpreset.Po
+ -rm -f ./$(DEPDIR)/gimptreehandler.Po
+ -rm -f ./$(DEPDIR)/gimptreeproxy.Po
+ -rm -f ./$(DEPDIR)/gimptriviallycancelablewaitable.Po
+ -rm -f ./$(DEPDIR)/gimpuncancelablewaitable.Po
+ -rm -f ./$(DEPDIR)/gimpundo.Po
+ -rm -f ./$(DEPDIR)/gimpundostack.Po
+ -rm -f ./$(DEPDIR)/gimpunit.Po
+ -rm -f ./$(DEPDIR)/gimpviewable.Po
+ -rm -f ./$(DEPDIR)/gimpwaitable.Po
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: all check install install-am install-exec install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-libtool clean-noinstLIBRARIES \
+ cscopelist-am ctags ctags-am distclean distclean-compile \
+ distclean-generic distclean-libtool distclean-tags distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-man install-pdf \
+ install-pdf-am install-ps install-ps-am install-strip \
+ installcheck installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am
+
+.PRECIOUS: Makefile
+
+
+gimpmarshal.h: $(srcdir)/gimpmarshal.list
+ $(AM_V_GEN) $(GLIB_GENMARSHAL) --prefix=gimp_marshal $(srcdir)/gimpmarshal.list --header >> xgen-gmh \
+ && (cmp -s xgen-gmh $(@F) || cp xgen-gmh $(@F)) \
+ && rm -f xgen-gmh xgen-gmh~
+
+gimpmarshal.c: gimpmarshal.h
+ $(AM_V_GEN) $(GLIB_GENMARSHAL) --prefix=gimp_marshal $(srcdir)/gimpmarshal.list --header --body >> xgen-gmc \
+ && cp xgen-gmc $(@F) \
+ && rm -f xgen-gmc xgen-gmc~
+
+xgen-cec: $(srcdir)/core-enums.h $(GIMP_MKENUMS) Makefile.am
+ $(AM_V_GEN) $(GIMP_MKENUMS) \
+ --fhead "#include \"config.h\"\n#include <gio/gio.h>\n#include \"libgimpbase/gimpbase.h\"\n#include \"core-enums.h\"\n#include \"gimp-intl.h\"" \
+ --fprod "\n/* enumerations from \"@basename@\" */" \
+ --vhead "GType\n@enum_name@_get_type (void)\n{\n static const G@Type@Value values[] =\n {" \
+ --vprod " { @VALUENAME@, \"@VALUENAME@\", \"@valuenick@\" }," \
+ --vtail " { 0, NULL, NULL }\n };\n" \
+ --dhead " static const Gimp@Type@Desc descs[] =\n {" \
+ --dprod " { @VALUENAME@, @valuedesc@, @valuehelp@ },@if ('@valueabbrev@' ne 'NULL')@\n /* Translators: this is an abbreviated version of @valueudesc@.\n Keep it short. */\n { @VALUENAME@, @valueabbrev@, NULL },@endif@" \
+ --dtail " { 0, NULL, NULL }\n };\n\n static GType type = 0;\n\n if (G_UNLIKELY (! type))\n {\n type = g_@type@_register_static (\"@EnumName@\", values);\n gimp_type_set_translation_context (type, \"@enumnick@\");\n gimp_@type@_set_value_descriptions (type, descs);\n }\n\n return type;\n}\n" \
+ $< > $@
+
+# copy the generated enum file back to the source directory only if it's
+# changed; otherwise, only update its timestamp, so that the recipe isn't
+# executed again on the next build, however, allow this to (harmlessly) fail,
+# to support building from a read-only source tree.
+$(srcdir)/core-enums.c: xgen-cec
+ $(AM_V_GEN) if ! cmp -s $< $@; then \
+ cp $< $@; \
+ else \
+ touch $@ 2> /dev/null \
+ || true; \
+ fi
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/app/core/core-enums.c b/app/core/core-enums.c
new file mode 100644
index 0000000..f8ecb14
--- /dev/null
+++ b/app/core/core-enums.c
@@ -0,0 +1,1316 @@
+
+/* Generated data (by gimp-mkenums) */
+
+#include "config.h"
+#include <gio/gio.h>
+#include "libgimpbase/gimpbase.h"
+#include "core-enums.h"
+#include "gimp-intl.h"
+
+/* enumerations from "core-enums.h" */
+GType
+gimp_align_reference_type_get_type (void)
+{
+ static const GEnumValue values[] =
+ {
+ { GIMP_ALIGN_REFERENCE_FIRST, "GIMP_ALIGN_REFERENCE_FIRST", "first" },
+ { GIMP_ALIGN_REFERENCE_IMAGE, "GIMP_ALIGN_REFERENCE_IMAGE", "image" },
+ { GIMP_ALIGN_REFERENCE_SELECTION, "GIMP_ALIGN_REFERENCE_SELECTION", "selection" },
+ { GIMP_ALIGN_REFERENCE_ACTIVE_LAYER, "GIMP_ALIGN_REFERENCE_ACTIVE_LAYER", "active-layer" },
+ { GIMP_ALIGN_REFERENCE_ACTIVE_CHANNEL, "GIMP_ALIGN_REFERENCE_ACTIVE_CHANNEL", "active-channel" },
+ { GIMP_ALIGN_REFERENCE_ACTIVE_PATH, "GIMP_ALIGN_REFERENCE_ACTIVE_PATH", "active-path" },
+ { 0, NULL, NULL }
+ };
+
+ static const GimpEnumDesc descs[] =
+ {
+ { GIMP_ALIGN_REFERENCE_FIRST, NC_("align-reference-type", "First item"), NULL },
+ { GIMP_ALIGN_REFERENCE_IMAGE, NC_("align-reference-type", "Image"), NULL },
+ { GIMP_ALIGN_REFERENCE_SELECTION, NC_("align-reference-type", "Selection"), NULL },
+ { GIMP_ALIGN_REFERENCE_ACTIVE_LAYER, NC_("align-reference-type", "Active layer"), NULL },
+ { GIMP_ALIGN_REFERENCE_ACTIVE_CHANNEL, NC_("align-reference-type", "Active channel"), NULL },
+ { GIMP_ALIGN_REFERENCE_ACTIVE_PATH, NC_("align-reference-type", "Active path"), NULL },
+ { 0, NULL, NULL }
+ };
+
+ static GType type = 0;
+
+ if (G_UNLIKELY (! type))
+ {
+ type = g_enum_register_static ("GimpAlignReferenceType", values);
+ gimp_type_set_translation_context (type, "align-reference-type");
+ gimp_enum_set_value_descriptions (type, descs);
+ }
+
+ return type;
+}
+
+GType
+gimp_alignment_type_get_type (void)
+{
+ static const GEnumValue values[] =
+ {
+ { GIMP_ALIGN_LEFT, "GIMP_ALIGN_LEFT", "align-left" },
+ { GIMP_ALIGN_HCENTER, "GIMP_ALIGN_HCENTER", "align-hcenter" },
+ { GIMP_ALIGN_RIGHT, "GIMP_ALIGN_RIGHT", "align-right" },
+ { GIMP_ALIGN_TOP, "GIMP_ALIGN_TOP", "align-top" },
+ { GIMP_ALIGN_VCENTER, "GIMP_ALIGN_VCENTER", "align-vcenter" },
+ { GIMP_ALIGN_BOTTOM, "GIMP_ALIGN_BOTTOM", "align-bottom" },
+ { GIMP_ARRANGE_LEFT, "GIMP_ARRANGE_LEFT", "arrange-left" },
+ { GIMP_ARRANGE_HCENTER, "GIMP_ARRANGE_HCENTER", "arrange-hcenter" },
+ { GIMP_ARRANGE_RIGHT, "GIMP_ARRANGE_RIGHT", "arrange-right" },
+ { GIMP_ARRANGE_TOP, "GIMP_ARRANGE_TOP", "arrange-top" },
+ { GIMP_ARRANGE_VCENTER, "GIMP_ARRANGE_VCENTER", "arrange-vcenter" },
+ { GIMP_ARRANGE_BOTTOM, "GIMP_ARRANGE_BOTTOM", "arrange-bottom" },
+ { GIMP_ARRANGE_HFILL, "GIMP_ARRANGE_HFILL", "arrange-hfill" },
+ { GIMP_ARRANGE_VFILL, "GIMP_ARRANGE_VFILL", "arrange-vfill" },
+ { 0, NULL, NULL }
+ };
+
+ static const GimpEnumDesc descs[] =
+ {
+ { GIMP_ALIGN_LEFT, "GIMP_ALIGN_LEFT", NULL },
+ { GIMP_ALIGN_HCENTER, "GIMP_ALIGN_HCENTER", NULL },
+ { GIMP_ALIGN_RIGHT, "GIMP_ALIGN_RIGHT", NULL },
+ { GIMP_ALIGN_TOP, "GIMP_ALIGN_TOP", NULL },
+ { GIMP_ALIGN_VCENTER, "GIMP_ALIGN_VCENTER", NULL },
+ { GIMP_ALIGN_BOTTOM, "GIMP_ALIGN_BOTTOM", NULL },
+ { GIMP_ARRANGE_LEFT, "GIMP_ARRANGE_LEFT", NULL },
+ { GIMP_ARRANGE_HCENTER, "GIMP_ARRANGE_HCENTER", NULL },
+ { GIMP_ARRANGE_RIGHT, "GIMP_ARRANGE_RIGHT", NULL },
+ { GIMP_ARRANGE_TOP, "GIMP_ARRANGE_TOP", NULL },
+ { GIMP_ARRANGE_VCENTER, "GIMP_ARRANGE_VCENTER", NULL },
+ { GIMP_ARRANGE_BOTTOM, "GIMP_ARRANGE_BOTTOM", NULL },
+ { GIMP_ARRANGE_HFILL, "GIMP_ARRANGE_HFILL", NULL },
+ { GIMP_ARRANGE_VFILL, "GIMP_ARRANGE_VFILL", NULL },
+ { 0, NULL, NULL }
+ };
+
+ static GType type = 0;
+
+ if (G_UNLIKELY (! type))
+ {
+ type = g_enum_register_static ("GimpAlignmentType", values);
+ gimp_type_set_translation_context (type, "alignment-type");
+ gimp_enum_set_value_descriptions (type, descs);
+ }
+
+ return type;
+}
+
+GType
+gimp_channel_border_style_get_type (void)
+{
+ static const GEnumValue values[] =
+ {
+ { GIMP_CHANNEL_BORDER_STYLE_HARD, "GIMP_CHANNEL_BORDER_STYLE_HARD", "hard" },
+ { GIMP_CHANNEL_BORDER_STYLE_SMOOTH, "GIMP_CHANNEL_BORDER_STYLE_SMOOTH", "smooth" },
+ { GIMP_CHANNEL_BORDER_STYLE_FEATHERED, "GIMP_CHANNEL_BORDER_STYLE_FEATHERED", "feathered" },
+ { 0, NULL, NULL }
+ };
+
+ static const GimpEnumDesc descs[] =
+ {
+ { GIMP_CHANNEL_BORDER_STYLE_HARD, NC_("channel-border-style", "Hard"), NULL },
+ { GIMP_CHANNEL_BORDER_STYLE_SMOOTH, NC_("channel-border-style", "Smooth"), NULL },
+ { GIMP_CHANNEL_BORDER_STYLE_FEATHERED, NC_("channel-border-style", "Feathered"), NULL },
+ { 0, NULL, NULL }
+ };
+
+ static GType type = 0;
+
+ if (G_UNLIKELY (! type))
+ {
+ type = g_enum_register_static ("GimpChannelBorderStyle", values);
+ gimp_type_set_translation_context (type, "channel-border-style");
+ gimp_enum_set_value_descriptions (type, descs);
+ }
+
+ return type;
+}
+
+GType
+gimp_color_pick_mode_get_type (void)
+{
+ static const GEnumValue values[] =
+ {
+ { GIMP_COLOR_PICK_MODE_PIXEL, "GIMP_COLOR_PICK_MODE_PIXEL", "pixel" },
+ { GIMP_COLOR_PICK_MODE_RGB_PERCENT, "GIMP_COLOR_PICK_MODE_RGB_PERCENT", "rgb-percent" },
+ { GIMP_COLOR_PICK_MODE_RGB_U8, "GIMP_COLOR_PICK_MODE_RGB_U8", "rgb-u8" },
+ { GIMP_COLOR_PICK_MODE_HSV, "GIMP_COLOR_PICK_MODE_HSV", "hsv" },
+ { GIMP_COLOR_PICK_MODE_LCH, "GIMP_COLOR_PICK_MODE_LCH", "lch" },
+ { GIMP_COLOR_PICK_MODE_LAB, "GIMP_COLOR_PICK_MODE_LAB", "lab" },
+ { GIMP_COLOR_PICK_MODE_CMYK, "GIMP_COLOR_PICK_MODE_CMYK", "cmyk" },
+ { GIMP_COLOR_PICK_MODE_XYY, "GIMP_COLOR_PICK_MODE_XYY", "xyy" },
+ { GIMP_COLOR_PICK_MODE_YUV, "GIMP_COLOR_PICK_MODE_YUV", "yuv" },
+ { 0, NULL, NULL }
+ };
+
+ static const GimpEnumDesc descs[] =
+ {
+ { GIMP_COLOR_PICK_MODE_PIXEL, NC_("color-pick-mode", "Pixel"), NULL },
+ { GIMP_COLOR_PICK_MODE_RGB_PERCENT, NC_("color-pick-mode", "RGB (%)"), NULL },
+ { GIMP_COLOR_PICK_MODE_RGB_U8, NC_("color-pick-mode", "RGB (0..255)"), NULL },
+ { GIMP_COLOR_PICK_MODE_HSV, NC_("color-pick-mode", "HSV"), NULL },
+ { GIMP_COLOR_PICK_MODE_LCH, NC_("color-pick-mode", "CIE LCh"), NULL },
+ { GIMP_COLOR_PICK_MODE_LAB, NC_("color-pick-mode", "CIE LAB"), NULL },
+ { GIMP_COLOR_PICK_MODE_CMYK, NC_("color-pick-mode", "CMYK"), NULL },
+ { GIMP_COLOR_PICK_MODE_XYY, NC_("color-pick-mode", "CIE xyY"), NULL },
+ { GIMP_COLOR_PICK_MODE_YUV, NC_("color-pick-mode", "CIE Yu'v'"), NULL },
+ { 0, NULL, NULL }
+ };
+
+ static GType type = 0;
+
+ if (G_UNLIKELY (! type))
+ {
+ type = g_enum_register_static ("GimpColorPickMode", values);
+ gimp_type_set_translation_context (type, "color-pick-mode");
+ gimp_enum_set_value_descriptions (type, descs);
+ }
+
+ return type;
+}
+
+GType
+gimp_color_profile_policy_get_type (void)
+{
+ static const GEnumValue values[] =
+ {
+ { GIMP_COLOR_PROFILE_POLICY_ASK, "GIMP_COLOR_PROFILE_POLICY_ASK", "ask" },
+ { GIMP_COLOR_PROFILE_POLICY_KEEP, "GIMP_COLOR_PROFILE_POLICY_KEEP", "keep" },
+ { GIMP_COLOR_PROFILE_POLICY_CONVERT, "GIMP_COLOR_PROFILE_POLICY_CONVERT", "convert" },
+ { 0, NULL, NULL }
+ };
+
+ static const GimpEnumDesc descs[] =
+ {
+ { GIMP_COLOR_PROFILE_POLICY_ASK, NC_("color-profile-policy", "Ask what to do"), NULL },
+ { GIMP_COLOR_PROFILE_POLICY_KEEP, NC_("color-profile-policy", "Keep embedded profile"), NULL },
+ { GIMP_COLOR_PROFILE_POLICY_CONVERT, NC_("color-profile-policy", "Convert to built-in sRGB or grayscale profile"), NULL },
+ { 0, NULL, NULL }
+ };
+
+ static GType type = 0;
+
+ if (G_UNLIKELY (! type))
+ {
+ type = g_enum_register_static ("GimpColorProfilePolicy", values);
+ gimp_type_set_translation_context (type, "color-profile-policy");
+ gimp_enum_set_value_descriptions (type, descs);
+ }
+
+ return type;
+}
+
+GType
+gimp_component_mask_get_type (void)
+{
+ static const GFlagsValue values[] =
+ {
+ { GIMP_COMPONENT_MASK_RED, "GIMP_COMPONENT_MASK_RED", "red" },
+ { GIMP_COMPONENT_MASK_GREEN, "GIMP_COMPONENT_MASK_GREEN", "green" },
+ { GIMP_COMPONENT_MASK_BLUE, "GIMP_COMPONENT_MASK_BLUE", "blue" },
+ { GIMP_COMPONENT_MASK_ALPHA, "GIMP_COMPONENT_MASK_ALPHA", "alpha" },
+ { GIMP_COMPONENT_MASK_ALL, "GIMP_COMPONENT_MASK_ALL", "all" },
+ { 0, NULL, NULL }
+ };
+
+ static const GimpFlagsDesc descs[] =
+ {
+ { GIMP_COMPONENT_MASK_RED, "GIMP_COMPONENT_MASK_RED", NULL },
+ { GIMP_COMPONENT_MASK_GREEN, "GIMP_COMPONENT_MASK_GREEN", NULL },
+ { GIMP_COMPONENT_MASK_BLUE, "GIMP_COMPONENT_MASK_BLUE", NULL },
+ { GIMP_COMPONENT_MASK_ALPHA, "GIMP_COMPONENT_MASK_ALPHA", NULL },
+ { GIMP_COMPONENT_MASK_ALL, "GIMP_COMPONENT_MASK_ALL", NULL },
+ { 0, NULL, NULL }
+ };
+
+ static GType type = 0;
+
+ if (G_UNLIKELY (! type))
+ {
+ type = g_flags_register_static ("GimpComponentMask", values);
+ gimp_type_set_translation_context (type, "component-mask");
+ gimp_flags_set_value_descriptions (type, descs);
+ }
+
+ return type;
+}
+
+GType
+gimp_container_policy_get_type (void)
+{
+ static const GEnumValue values[] =
+ {
+ { GIMP_CONTAINER_POLICY_STRONG, "GIMP_CONTAINER_POLICY_STRONG", "strong" },
+ { GIMP_CONTAINER_POLICY_WEAK, "GIMP_CONTAINER_POLICY_WEAK", "weak" },
+ { 0, NULL, NULL }
+ };
+
+ static const GimpEnumDesc descs[] =
+ {
+ { GIMP_CONTAINER_POLICY_STRONG, "GIMP_CONTAINER_POLICY_STRONG", NULL },
+ { GIMP_CONTAINER_POLICY_WEAK, "GIMP_CONTAINER_POLICY_WEAK", NULL },
+ { 0, NULL, NULL }
+ };
+
+ static GType type = 0;
+
+ if (G_UNLIKELY (! type))
+ {
+ type = g_enum_register_static ("GimpContainerPolicy", values);
+ gimp_type_set_translation_context (type, "container-policy");
+ gimp_enum_set_value_descriptions (type, descs);
+ }
+
+ return type;
+}
+
+GType
+gimp_convert_dither_type_get_type (void)
+{
+ static const GEnumValue values[] =
+ {
+ { GIMP_CONVERT_DITHER_NONE, "GIMP_CONVERT_DITHER_NONE", "none" },
+ { GIMP_CONVERT_DITHER_FS, "GIMP_CONVERT_DITHER_FS", "fs" },
+ { GIMP_CONVERT_DITHER_FS_LOWBLEED, "GIMP_CONVERT_DITHER_FS_LOWBLEED", "fs-lowbleed" },
+ { GIMP_CONVERT_DITHER_FIXED, "GIMP_CONVERT_DITHER_FIXED", "fixed" },
+ { 0, NULL, NULL }
+ };
+
+ static const GimpEnumDesc descs[] =
+ {
+ { GIMP_CONVERT_DITHER_NONE, NC_("convert-dither-type", "None"), NULL },
+ { GIMP_CONVERT_DITHER_FS, NC_("convert-dither-type", "Floyd-Steinberg (normal)"), NULL },
+ { GIMP_CONVERT_DITHER_FS_LOWBLEED, NC_("convert-dither-type", "Floyd-Steinberg (reduced color bleeding)"), NULL },
+ { GIMP_CONVERT_DITHER_FIXED, NC_("convert-dither-type", "Positioned"), NULL },
+ { 0, NULL, NULL }
+ };
+
+ static GType type = 0;
+
+ if (G_UNLIKELY (! type))
+ {
+ type = g_enum_register_static ("GimpConvertDitherType", values);
+ gimp_type_set_translation_context (type, "convert-dither-type");
+ gimp_enum_set_value_descriptions (type, descs);
+ }
+
+ return type;
+}
+
+GType
+gimp_convolution_type_get_type (void)
+{
+ static const GEnumValue values[] =
+ {
+ { GIMP_NORMAL_CONVOL, "GIMP_NORMAL_CONVOL", "normal-convol" },
+ { GIMP_ABSOLUTE_CONVOL, "GIMP_ABSOLUTE_CONVOL", "absolute-convol" },
+ { GIMP_NEGATIVE_CONVOL, "GIMP_NEGATIVE_CONVOL", "negative-convol" },
+ { 0, NULL, NULL }
+ };
+
+ static const GimpEnumDesc descs[] =
+ {
+ { GIMP_NORMAL_CONVOL, "GIMP_NORMAL_CONVOL", NULL },
+ { GIMP_ABSOLUTE_CONVOL, "GIMP_ABSOLUTE_CONVOL", NULL },
+ { GIMP_NEGATIVE_CONVOL, "GIMP_NEGATIVE_CONVOL", NULL },
+ { 0, NULL, NULL }
+ };
+
+ static GType type = 0;
+
+ if (G_UNLIKELY (! type))
+ {
+ type = g_enum_register_static ("GimpConvolutionType", values);
+ gimp_type_set_translation_context (type, "convolution-type");
+ gimp_enum_set_value_descriptions (type, descs);
+ }
+
+ return type;
+}
+
+GType
+gimp_curve_point_type_get_type (void)
+{
+ static const GEnumValue values[] =
+ {
+ { GIMP_CURVE_POINT_SMOOTH, "GIMP_CURVE_POINT_SMOOTH", "smooth" },
+ { GIMP_CURVE_POINT_CORNER, "GIMP_CURVE_POINT_CORNER", "corner" },
+ { 0, NULL, NULL }
+ };
+
+ static const GimpEnumDesc descs[] =
+ {
+ { GIMP_CURVE_POINT_SMOOTH, NC_("curve-point-type", "Smooth"), NULL },
+ { GIMP_CURVE_POINT_CORNER, NC_("curve-point-type", "Corner"), NULL },
+ { 0, NULL, NULL }
+ };
+
+ static GType type = 0;
+
+ if (G_UNLIKELY (! type))
+ {
+ type = g_enum_register_static ("GimpCurvePointType", values);
+ gimp_type_set_translation_context (type, "curve-point-type");
+ gimp_enum_set_value_descriptions (type, descs);
+ }
+
+ return type;
+}
+
+GType
+gimp_curve_type_get_type (void)
+{
+ static const GEnumValue values[] =
+ {
+ { GIMP_CURVE_SMOOTH, "GIMP_CURVE_SMOOTH", "smooth" },
+ { GIMP_CURVE_FREE, "GIMP_CURVE_FREE", "free" },
+ { 0, NULL, NULL }
+ };
+
+ static const GimpEnumDesc descs[] =
+ {
+ { GIMP_CURVE_SMOOTH, NC_("curve-type", "Smooth"), NULL },
+ { GIMP_CURVE_FREE, NC_("curve-type", "Freehand"), NULL },
+ { 0, NULL, NULL }
+ };
+
+ static GType type = 0;
+
+ if (G_UNLIKELY (! type))
+ {
+ type = g_enum_register_static ("GimpCurveType", values);
+ gimp_type_set_translation_context (type, "curve-type");
+ gimp_enum_set_value_descriptions (type, descs);
+ }
+
+ return type;
+}
+
+GType
+gimp_dash_preset_get_type (void)
+{
+ static const GEnumValue values[] =
+ {
+ { GIMP_DASH_CUSTOM, "GIMP_DASH_CUSTOM", "custom" },
+ { GIMP_DASH_LINE, "GIMP_DASH_LINE", "line" },
+ { GIMP_DASH_LONG_DASH, "GIMP_DASH_LONG_DASH", "long-dash" },
+ { GIMP_DASH_MEDIUM_DASH, "GIMP_DASH_MEDIUM_DASH", "medium-dash" },
+ { GIMP_DASH_SHORT_DASH, "GIMP_DASH_SHORT_DASH", "short-dash" },
+ { GIMP_DASH_SPARSE_DOTS, "GIMP_DASH_SPARSE_DOTS", "sparse-dots" },
+ { GIMP_DASH_NORMAL_DOTS, "GIMP_DASH_NORMAL_DOTS", "normal-dots" },
+ { GIMP_DASH_DENSE_DOTS, "GIMP_DASH_DENSE_DOTS", "dense-dots" },
+ { GIMP_DASH_STIPPLES, "GIMP_DASH_STIPPLES", "stipples" },
+ { GIMP_DASH_DASH_DOT, "GIMP_DASH_DASH_DOT", "dash-dot" },
+ { GIMP_DASH_DASH_DOT_DOT, "GIMP_DASH_DASH_DOT_DOT", "dash-dot-dot" },
+ { 0, NULL, NULL }
+ };
+
+ static const GimpEnumDesc descs[] =
+ {
+ { GIMP_DASH_CUSTOM, NC_("dash-preset", "Custom"), NULL },
+ { GIMP_DASH_LINE, NC_("dash-preset", "Line"), NULL },
+ { GIMP_DASH_LONG_DASH, NC_("dash-preset", "Long dashes"), NULL },
+ { GIMP_DASH_MEDIUM_DASH, NC_("dash-preset", "Medium dashes"), NULL },
+ { GIMP_DASH_SHORT_DASH, NC_("dash-preset", "Short dashes"), NULL },
+ { GIMP_DASH_SPARSE_DOTS, NC_("dash-preset", "Sparse dots"), NULL },
+ { GIMP_DASH_NORMAL_DOTS, NC_("dash-preset", "Normal dots"), NULL },
+ { GIMP_DASH_DENSE_DOTS, NC_("dash-preset", "Dense dots"), NULL },
+ { GIMP_DASH_STIPPLES, NC_("dash-preset", "Stipples"), NULL },
+ { GIMP_DASH_DASH_DOT, NC_("dash-preset", "Dash, dot"), NULL },
+ { GIMP_DASH_DASH_DOT_DOT, NC_("dash-preset", "Dash, dot, dot"), NULL },
+ { 0, NULL, NULL }
+ };
+
+ static GType type = 0;
+
+ if (G_UNLIKELY (! type))
+ {
+ type = g_enum_register_static ("GimpDashPreset", values);
+ gimp_type_set_translation_context (type, "dash-preset");
+ gimp_enum_set_value_descriptions (type, descs);
+ }
+
+ return type;
+}
+
+GType
+gimp_debug_policy_get_type (void)
+{
+ static const GEnumValue values[] =
+ {
+ { GIMP_DEBUG_POLICY_WARNING, "GIMP_DEBUG_POLICY_WARNING", "warning" },
+ { GIMP_DEBUG_POLICY_CRITICAL, "GIMP_DEBUG_POLICY_CRITICAL", "critical" },
+ { GIMP_DEBUG_POLICY_FATAL, "GIMP_DEBUG_POLICY_FATAL", "fatal" },
+ { GIMP_DEBUG_POLICY_NEVER, "GIMP_DEBUG_POLICY_NEVER", "never" },
+ { 0, NULL, NULL }
+ };
+
+ static const GimpEnumDesc descs[] =
+ {
+ { GIMP_DEBUG_POLICY_WARNING, NC_("debug-policy", "Debug warnings, critical errors and crashes"), NULL },
+ { GIMP_DEBUG_POLICY_CRITICAL, NC_("debug-policy", "Debug critical errors and crashes"), NULL },
+ { GIMP_DEBUG_POLICY_FATAL, NC_("debug-policy", "Debug crashes only"), NULL },
+ { GIMP_DEBUG_POLICY_NEVER, NC_("debug-policy", "Never debug GIMP"), NULL },
+ { 0, NULL, NULL }
+ };
+
+ static GType type = 0;
+
+ if (G_UNLIKELY (! type))
+ {
+ type = g_enum_register_static ("GimpDebugPolicy", values);
+ gimp_type_set_translation_context (type, "debug-policy");
+ gimp_enum_set_value_descriptions (type, descs);
+ }
+
+ return type;
+}
+
+GType
+gimp_dirty_mask_get_type (void)
+{
+ static const GFlagsValue values[] =
+ {
+ { GIMP_DIRTY_NONE, "GIMP_DIRTY_NONE", "none" },
+ { GIMP_DIRTY_IMAGE, "GIMP_DIRTY_IMAGE", "image" },
+ { GIMP_DIRTY_IMAGE_SIZE, "GIMP_DIRTY_IMAGE_SIZE", "image-size" },
+ { GIMP_DIRTY_IMAGE_META, "GIMP_DIRTY_IMAGE_META", "image-meta" },
+ { GIMP_DIRTY_IMAGE_STRUCTURE, "GIMP_DIRTY_IMAGE_STRUCTURE", "image-structure" },
+ { GIMP_DIRTY_ITEM, "GIMP_DIRTY_ITEM", "item" },
+ { GIMP_DIRTY_ITEM_META, "GIMP_DIRTY_ITEM_META", "item-meta" },
+ { GIMP_DIRTY_DRAWABLE, "GIMP_DIRTY_DRAWABLE", "drawable" },
+ { GIMP_DIRTY_VECTORS, "GIMP_DIRTY_VECTORS", "vectors" },
+ { GIMP_DIRTY_SELECTION, "GIMP_DIRTY_SELECTION", "selection" },
+ { GIMP_DIRTY_ACTIVE_DRAWABLE, "GIMP_DIRTY_ACTIVE_DRAWABLE", "active-drawable" },
+ { GIMP_DIRTY_ALL, "GIMP_DIRTY_ALL", "all" },
+ { 0, NULL, NULL }
+ };
+
+ static const GimpFlagsDesc descs[] =
+ {
+ { GIMP_DIRTY_NONE, "GIMP_DIRTY_NONE", NULL },
+ { GIMP_DIRTY_IMAGE, "GIMP_DIRTY_IMAGE", NULL },
+ { GIMP_DIRTY_IMAGE_SIZE, "GIMP_DIRTY_IMAGE_SIZE", NULL },
+ { GIMP_DIRTY_IMAGE_META, "GIMP_DIRTY_IMAGE_META", NULL },
+ { GIMP_DIRTY_IMAGE_STRUCTURE, "GIMP_DIRTY_IMAGE_STRUCTURE", NULL },
+ { GIMP_DIRTY_ITEM, "GIMP_DIRTY_ITEM", NULL },
+ { GIMP_DIRTY_ITEM_META, "GIMP_DIRTY_ITEM_META", NULL },
+ { GIMP_DIRTY_DRAWABLE, "GIMP_DIRTY_DRAWABLE", NULL },
+ { GIMP_DIRTY_VECTORS, "GIMP_DIRTY_VECTORS", NULL },
+ { GIMP_DIRTY_SELECTION, "GIMP_DIRTY_SELECTION", NULL },
+ { GIMP_DIRTY_ACTIVE_DRAWABLE, "GIMP_DIRTY_ACTIVE_DRAWABLE", NULL },
+ { GIMP_DIRTY_ALL, "GIMP_DIRTY_ALL", NULL },
+ { 0, NULL, NULL }
+ };
+
+ static GType type = 0;
+
+ if (G_UNLIKELY (! type))
+ {
+ type = g_flags_register_static ("GimpDirtyMask", values);
+ gimp_type_set_translation_context (type, "dirty-mask");
+ gimp_flags_set_value_descriptions (type, descs);
+ }
+
+ return type;
+}
+
+GType
+gimp_dynamics_output_type_get_type (void)
+{
+ static const GEnumValue values[] =
+ {
+ { GIMP_DYNAMICS_OUTPUT_OPACITY, "GIMP_DYNAMICS_OUTPUT_OPACITY", "opacity" },
+ { GIMP_DYNAMICS_OUTPUT_SIZE, "GIMP_DYNAMICS_OUTPUT_SIZE", "size" },
+ { GIMP_DYNAMICS_OUTPUT_ANGLE, "GIMP_DYNAMICS_OUTPUT_ANGLE", "angle" },
+ { GIMP_DYNAMICS_OUTPUT_COLOR, "GIMP_DYNAMICS_OUTPUT_COLOR", "color" },
+ { GIMP_DYNAMICS_OUTPUT_HARDNESS, "GIMP_DYNAMICS_OUTPUT_HARDNESS", "hardness" },
+ { GIMP_DYNAMICS_OUTPUT_FORCE, "GIMP_DYNAMICS_OUTPUT_FORCE", "force" },
+ { GIMP_DYNAMICS_OUTPUT_ASPECT_RATIO, "GIMP_DYNAMICS_OUTPUT_ASPECT_RATIO", "aspect-ratio" },
+ { GIMP_DYNAMICS_OUTPUT_SPACING, "GIMP_DYNAMICS_OUTPUT_SPACING", "spacing" },
+ { GIMP_DYNAMICS_OUTPUT_RATE, "GIMP_DYNAMICS_OUTPUT_RATE", "rate" },
+ { GIMP_DYNAMICS_OUTPUT_FLOW, "GIMP_DYNAMICS_OUTPUT_FLOW", "flow" },
+ { GIMP_DYNAMICS_OUTPUT_JITTER, "GIMP_DYNAMICS_OUTPUT_JITTER", "jitter" },
+ { 0, NULL, NULL }
+ };
+
+ static const GimpEnumDesc descs[] =
+ {
+ { GIMP_DYNAMICS_OUTPUT_OPACITY, NC_("dynamics-output-type", "Opacity"), NULL },
+ { GIMP_DYNAMICS_OUTPUT_SIZE, NC_("dynamics-output-type", "Size"), NULL },
+ { GIMP_DYNAMICS_OUTPUT_ANGLE, NC_("dynamics-output-type", "Angle"), NULL },
+ { GIMP_DYNAMICS_OUTPUT_COLOR, NC_("dynamics-output-type", "Color"), NULL },
+ { GIMP_DYNAMICS_OUTPUT_HARDNESS, NC_("dynamics-output-type", "Hardness"), NULL },
+ { GIMP_DYNAMICS_OUTPUT_FORCE, NC_("dynamics-output-type", "Force"), NULL },
+ { GIMP_DYNAMICS_OUTPUT_ASPECT_RATIO, NC_("dynamics-output-type", "Aspect ratio"), NULL },
+ { GIMP_DYNAMICS_OUTPUT_SPACING, NC_("dynamics-output-type", "Spacing"), NULL },
+ { GIMP_DYNAMICS_OUTPUT_RATE, NC_("dynamics-output-type", "Rate"), NULL },
+ { GIMP_DYNAMICS_OUTPUT_FLOW, NC_("dynamics-output-type", "Flow"), NULL },
+ { GIMP_DYNAMICS_OUTPUT_JITTER, NC_("dynamics-output-type", "Jitter"), NULL },
+ { 0, NULL, NULL }
+ };
+
+ static GType type = 0;
+
+ if (G_UNLIKELY (! type))
+ {
+ type = g_enum_register_static ("GimpDynamicsOutputType", values);
+ gimp_type_set_translation_context (type, "dynamics-output-type");
+ gimp_enum_set_value_descriptions (type, descs);
+ }
+
+ return type;
+}
+
+GType
+gimp_fill_style_get_type (void)
+{
+ static const GEnumValue values[] =
+ {
+ { GIMP_FILL_STYLE_SOLID, "GIMP_FILL_STYLE_SOLID", "solid" },
+ { GIMP_FILL_STYLE_PATTERN, "GIMP_FILL_STYLE_PATTERN", "pattern" },
+ { 0, NULL, NULL }
+ };
+
+ static const GimpEnumDesc descs[] =
+ {
+ { GIMP_FILL_STYLE_SOLID, NC_("fill-style", "Solid color"), NULL },
+ { GIMP_FILL_STYLE_PATTERN, NC_("fill-style", "Pattern"), NULL },
+ { 0, NULL, NULL }
+ };
+
+ static GType type = 0;
+
+ if (G_UNLIKELY (! type))
+ {
+ type = g_enum_register_static ("GimpFillStyle", values);
+ gimp_type_set_translation_context (type, "fill-style");
+ gimp_enum_set_value_descriptions (type, descs);
+ }
+
+ return type;
+}
+
+GType
+gimp_filter_region_get_type (void)
+{
+ static const GEnumValue values[] =
+ {
+ { GIMP_FILTER_REGION_SELECTION, "GIMP_FILTER_REGION_SELECTION", "selection" },
+ { GIMP_FILTER_REGION_DRAWABLE, "GIMP_FILTER_REGION_DRAWABLE", "drawable" },
+ { 0, NULL, NULL }
+ };
+
+ static const GimpEnumDesc descs[] =
+ {
+ { GIMP_FILTER_REGION_SELECTION, NC_("filter-region", "Use the selection as input"), NULL },
+ { GIMP_FILTER_REGION_DRAWABLE, NC_("filter-region", "Use the entire layer as input"), NULL },
+ { 0, NULL, NULL }
+ };
+
+ static GType type = 0;
+
+ if (G_UNLIKELY (! type))
+ {
+ type = g_enum_register_static ("GimpFilterRegion", values);
+ gimp_type_set_translation_context (type, "filter-region");
+ gimp_enum_set_value_descriptions (type, descs);
+ }
+
+ return type;
+}
+
+GType
+gimp_gradient_color_get_type (void)
+{
+ static const GEnumValue values[] =
+ {
+ { GIMP_GRADIENT_COLOR_FIXED, "GIMP_GRADIENT_COLOR_FIXED", "fixed" },
+ { GIMP_GRADIENT_COLOR_FOREGROUND, "GIMP_GRADIENT_COLOR_FOREGROUND", "foreground" },
+ { GIMP_GRADIENT_COLOR_FOREGROUND_TRANSPARENT, "GIMP_GRADIENT_COLOR_FOREGROUND_TRANSPARENT", "foreground-transparent" },
+ { GIMP_GRADIENT_COLOR_BACKGROUND, "GIMP_GRADIENT_COLOR_BACKGROUND", "background" },
+ { GIMP_GRADIENT_COLOR_BACKGROUND_TRANSPARENT, "GIMP_GRADIENT_COLOR_BACKGROUND_TRANSPARENT", "background-transparent" },
+ { 0, NULL, NULL }
+ };
+
+ static const GimpEnumDesc descs[] =
+ {
+ { GIMP_GRADIENT_COLOR_FIXED, NC_("gradient-color", "Fixed"), NULL },
+ { GIMP_GRADIENT_COLOR_FOREGROUND, NC_("gradient-color", "Foreground color"), NULL },
+ /* Translators: this is an abbreviated version of "Foreground color".
+ Keep it short. */
+ { GIMP_GRADIENT_COLOR_FOREGROUND, NC_("gradient-color", "FG"), NULL },
+ { GIMP_GRADIENT_COLOR_FOREGROUND_TRANSPARENT, NC_("gradient-color", "Foreground color (transparent)"), NULL },
+ /* Translators: this is an abbreviated version of "Foreground color (transparent)".
+ Keep it short. */
+ { GIMP_GRADIENT_COLOR_FOREGROUND_TRANSPARENT, NC_("gradient-color", "FG (t)"), NULL },
+ { GIMP_GRADIENT_COLOR_BACKGROUND, NC_("gradient-color", "Background color"), NULL },
+ /* Translators: this is an abbreviated version of "Background color".
+ Keep it short. */
+ { GIMP_GRADIENT_COLOR_BACKGROUND, NC_("gradient-color", "BG"), NULL },
+ { GIMP_GRADIENT_COLOR_BACKGROUND_TRANSPARENT, NC_("gradient-color", "Background color (transparent)"), NULL },
+ /* Translators: this is an abbreviated version of "Background color (transparent)".
+ Keep it short. */
+ { GIMP_GRADIENT_COLOR_BACKGROUND_TRANSPARENT, NC_("gradient-color", "BG (t)"), NULL },
+ { 0, NULL, NULL }
+ };
+
+ static GType type = 0;
+
+ if (G_UNLIKELY (! type))
+ {
+ type = g_enum_register_static ("GimpGradientColor", values);
+ gimp_type_set_translation_context (type, "gradient-color");
+ gimp_enum_set_value_descriptions (type, descs);
+ }
+
+ return type;
+}
+
+GType
+gimp_gravity_type_get_type (void)
+{
+ static const GEnumValue values[] =
+ {
+ { GIMP_GRAVITY_NONE, "GIMP_GRAVITY_NONE", "none" },
+ { GIMP_GRAVITY_NORTH_WEST, "GIMP_GRAVITY_NORTH_WEST", "north-west" },
+ { GIMP_GRAVITY_NORTH, "GIMP_GRAVITY_NORTH", "north" },
+ { GIMP_GRAVITY_NORTH_EAST, "GIMP_GRAVITY_NORTH_EAST", "north-east" },
+ { GIMP_GRAVITY_WEST, "GIMP_GRAVITY_WEST", "west" },
+ { GIMP_GRAVITY_CENTER, "GIMP_GRAVITY_CENTER", "center" },
+ { GIMP_GRAVITY_EAST, "GIMP_GRAVITY_EAST", "east" },
+ { GIMP_GRAVITY_SOUTH_WEST, "GIMP_GRAVITY_SOUTH_WEST", "south-west" },
+ { GIMP_GRAVITY_SOUTH, "GIMP_GRAVITY_SOUTH", "south" },
+ { GIMP_GRAVITY_SOUTH_EAST, "GIMP_GRAVITY_SOUTH_EAST", "south-east" },
+ { 0, NULL, NULL }
+ };
+
+ static const GimpEnumDesc descs[] =
+ {
+ { GIMP_GRAVITY_NONE, "GIMP_GRAVITY_NONE", NULL },
+ { GIMP_GRAVITY_NORTH_WEST, "GIMP_GRAVITY_NORTH_WEST", NULL },
+ { GIMP_GRAVITY_NORTH, "GIMP_GRAVITY_NORTH", NULL },
+ { GIMP_GRAVITY_NORTH_EAST, "GIMP_GRAVITY_NORTH_EAST", NULL },
+ { GIMP_GRAVITY_WEST, "GIMP_GRAVITY_WEST", NULL },
+ { GIMP_GRAVITY_CENTER, "GIMP_GRAVITY_CENTER", NULL },
+ { GIMP_GRAVITY_EAST, "GIMP_GRAVITY_EAST", NULL },
+ { GIMP_GRAVITY_SOUTH_WEST, "GIMP_GRAVITY_SOUTH_WEST", NULL },
+ { GIMP_GRAVITY_SOUTH, "GIMP_GRAVITY_SOUTH", NULL },
+ { GIMP_GRAVITY_SOUTH_EAST, "GIMP_GRAVITY_SOUTH_EAST", NULL },
+ { 0, NULL, NULL }
+ };
+
+ static GType type = 0;
+
+ if (G_UNLIKELY (! type))
+ {
+ type = g_enum_register_static ("GimpGravityType", values);
+ gimp_type_set_translation_context (type, "gravity-type");
+ gimp_enum_set_value_descriptions (type, descs);
+ }
+
+ return type;
+}
+
+GType
+gimp_guide_style_get_type (void)
+{
+ static const GEnumValue values[] =
+ {
+ { GIMP_GUIDE_STYLE_NONE, "GIMP_GUIDE_STYLE_NONE", "none" },
+ { GIMP_GUIDE_STYLE_NORMAL, "GIMP_GUIDE_STYLE_NORMAL", "normal" },
+ { GIMP_GUIDE_STYLE_MIRROR, "GIMP_GUIDE_STYLE_MIRROR", "mirror" },
+ { GIMP_GUIDE_STYLE_MANDALA, "GIMP_GUIDE_STYLE_MANDALA", "mandala" },
+ { GIMP_GUIDE_STYLE_SPLIT_VIEW, "GIMP_GUIDE_STYLE_SPLIT_VIEW", "split-view" },
+ { 0, NULL, NULL }
+ };
+
+ static const GimpEnumDesc descs[] =
+ {
+ { GIMP_GUIDE_STYLE_NONE, "GIMP_GUIDE_STYLE_NONE", NULL },
+ { GIMP_GUIDE_STYLE_NORMAL, "GIMP_GUIDE_STYLE_NORMAL", NULL },
+ { GIMP_GUIDE_STYLE_MIRROR, "GIMP_GUIDE_STYLE_MIRROR", NULL },
+ { GIMP_GUIDE_STYLE_MANDALA, "GIMP_GUIDE_STYLE_MANDALA", NULL },
+ { GIMP_GUIDE_STYLE_SPLIT_VIEW, "GIMP_GUIDE_STYLE_SPLIT_VIEW", NULL },
+ { 0, NULL, NULL }
+ };
+
+ static GType type = 0;
+
+ if (G_UNLIKELY (! type))
+ {
+ type = g_enum_register_static ("GimpGuideStyle", values);
+ gimp_type_set_translation_context (type, "guide-style");
+ gimp_enum_set_value_descriptions (type, descs);
+ }
+
+ return type;
+}
+
+GType
+gimp_histogram_channel_get_type (void)
+{
+ static const GEnumValue values[] =
+ {
+ { GIMP_HISTOGRAM_VALUE, "GIMP_HISTOGRAM_VALUE", "value" },
+ { GIMP_HISTOGRAM_RED, "GIMP_HISTOGRAM_RED", "red" },
+ { GIMP_HISTOGRAM_GREEN, "GIMP_HISTOGRAM_GREEN", "green" },
+ { GIMP_HISTOGRAM_BLUE, "GIMP_HISTOGRAM_BLUE", "blue" },
+ { GIMP_HISTOGRAM_ALPHA, "GIMP_HISTOGRAM_ALPHA", "alpha" },
+ { GIMP_HISTOGRAM_LUMINANCE, "GIMP_HISTOGRAM_LUMINANCE", "luminance" },
+ { GIMP_HISTOGRAM_RGB, "GIMP_HISTOGRAM_RGB", "rgb" },
+ { 0, NULL, NULL }
+ };
+
+ static const GimpEnumDesc descs[] =
+ {
+ { GIMP_HISTOGRAM_VALUE, NC_("histogram-channel", "Value"), NULL },
+ { GIMP_HISTOGRAM_RED, NC_("histogram-channel", "Red"), NULL },
+ { GIMP_HISTOGRAM_GREEN, NC_("histogram-channel", "Green"), NULL },
+ { GIMP_HISTOGRAM_BLUE, NC_("histogram-channel", "Blue"), NULL },
+ { GIMP_HISTOGRAM_ALPHA, NC_("histogram-channel", "Alpha"), NULL },
+ { GIMP_HISTOGRAM_LUMINANCE, NC_("histogram-channel", "Luminance"), NULL },
+ { GIMP_HISTOGRAM_RGB, NC_("histogram-channel", "RGB"), NULL },
+ { 0, NULL, NULL }
+ };
+
+ static GType type = 0;
+
+ if (G_UNLIKELY (! type))
+ {
+ type = g_enum_register_static ("GimpHistogramChannel", values);
+ gimp_type_set_translation_context (type, "histogram-channel");
+ gimp_enum_set_value_descriptions (type, descs);
+ }
+
+ return type;
+}
+
+GType
+gimp_item_set_get_type (void)
+{
+ static const GEnumValue values[] =
+ {
+ { GIMP_ITEM_SET_NONE, "GIMP_ITEM_SET_NONE", "none" },
+ { GIMP_ITEM_SET_ALL, "GIMP_ITEM_SET_ALL", "all" },
+ { GIMP_ITEM_SET_IMAGE_SIZED, "GIMP_ITEM_SET_IMAGE_SIZED", "image-sized" },
+ { GIMP_ITEM_SET_VISIBLE, "GIMP_ITEM_SET_VISIBLE", "visible" },
+ { GIMP_ITEM_SET_LINKED, "GIMP_ITEM_SET_LINKED", "linked" },
+ { 0, NULL, NULL }
+ };
+
+ static const GimpEnumDesc descs[] =
+ {
+ { GIMP_ITEM_SET_NONE, NC_("item-set", "None"), NULL },
+ { GIMP_ITEM_SET_ALL, NC_("item-set", "All layers"), NULL },
+ { GIMP_ITEM_SET_IMAGE_SIZED, NC_("item-set", "Image-sized layers"), NULL },
+ { GIMP_ITEM_SET_VISIBLE, NC_("item-set", "All visible layers"), NULL },
+ { GIMP_ITEM_SET_LINKED, NC_("item-set", "All linked layers"), NULL },
+ { 0, NULL, NULL }
+ };
+
+ static GType type = 0;
+
+ if (G_UNLIKELY (! type))
+ {
+ type = g_enum_register_static ("GimpItemSet", values);
+ gimp_type_set_translation_context (type, "item-set");
+ gimp_enum_set_value_descriptions (type, descs);
+ }
+
+ return type;
+}
+
+GType
+gimp_matting_engine_get_type (void)
+{
+ static const GEnumValue values[] =
+ {
+ { GIMP_MATTING_ENGINE_GLOBAL, "GIMP_MATTING_ENGINE_GLOBAL", "global" },
+ { GIMP_MATTING_ENGINE_LEVIN, "GIMP_MATTING_ENGINE_LEVIN", "levin" },
+ { 0, NULL, NULL }
+ };
+
+ static const GimpEnumDesc descs[] =
+ {
+ { GIMP_MATTING_ENGINE_GLOBAL, NC_("matting-engine", "Matting Global"), NULL },
+ { GIMP_MATTING_ENGINE_LEVIN, NC_("matting-engine", "Matting Levin"), NULL },
+ { 0, NULL, NULL }
+ };
+
+ static GType type = 0;
+
+ if (G_UNLIKELY (! type))
+ {
+ type = g_enum_register_static ("GimpMattingEngine", values);
+ gimp_type_set_translation_context (type, "matting-engine");
+ gimp_enum_set_value_descriptions (type, descs);
+ }
+
+ return type;
+}
+
+GType
+gimp_message_severity_get_type (void)
+{
+ static const GEnumValue values[] =
+ {
+ { GIMP_MESSAGE_INFO, "GIMP_MESSAGE_INFO", "info" },
+ { GIMP_MESSAGE_WARNING, "GIMP_MESSAGE_WARNING", "warning" },
+ { GIMP_MESSAGE_ERROR, "GIMP_MESSAGE_ERROR", "error" },
+ { GIMP_MESSAGE_BUG_WARNING, "GIMP_MESSAGE_BUG_WARNING", "bug-warning" },
+ { GIMP_MESSAGE_BUG_CRITICAL, "GIMP_MESSAGE_BUG_CRITICAL", "bug-critical" },
+ { 0, NULL, NULL }
+ };
+
+ static const GimpEnumDesc descs[] =
+ {
+ { GIMP_MESSAGE_INFO, NC_("message-severity", "Message"), NULL },
+ { GIMP_MESSAGE_WARNING, NC_("message-severity", "Warning"), NULL },
+ { GIMP_MESSAGE_ERROR, NC_("message-severity", "Error"), NULL },
+ { GIMP_MESSAGE_BUG_WARNING, NC_("message-severity", "WARNING"), NULL },
+ { GIMP_MESSAGE_BUG_CRITICAL, NC_("message-severity", "CRITICAL"), NULL },
+ { 0, NULL, NULL }
+ };
+
+ static GType type = 0;
+
+ if (G_UNLIKELY (! type))
+ {
+ type = g_enum_register_static ("GimpMessageSeverity", values);
+ gimp_type_set_translation_context (type, "message-severity");
+ gimp_enum_set_value_descriptions (type, descs);
+ }
+
+ return type;
+}
+
+GType
+gimp_paste_type_get_type (void)
+{
+ static const GEnumValue values[] =
+ {
+ { GIMP_PASTE_TYPE_FLOATING, "GIMP_PASTE_TYPE_FLOATING", "floating" },
+ { GIMP_PASTE_TYPE_FLOATING_IN_PLACE, "GIMP_PASTE_TYPE_FLOATING_IN_PLACE", "floating-in-place" },
+ { GIMP_PASTE_TYPE_FLOATING_INTO, "GIMP_PASTE_TYPE_FLOATING_INTO", "floating-into" },
+ { GIMP_PASTE_TYPE_FLOATING_INTO_IN_PLACE, "GIMP_PASTE_TYPE_FLOATING_INTO_IN_PLACE", "floating-into-in-place" },
+ { GIMP_PASTE_TYPE_NEW_LAYER, "GIMP_PASTE_TYPE_NEW_LAYER", "new-layer" },
+ { GIMP_PASTE_TYPE_NEW_LAYER_IN_PLACE, "GIMP_PASTE_TYPE_NEW_LAYER_IN_PLACE", "new-layer-in-place" },
+ { 0, NULL, NULL }
+ };
+
+ static const GimpEnumDesc descs[] =
+ {
+ { GIMP_PASTE_TYPE_FLOATING, "GIMP_PASTE_TYPE_FLOATING", NULL },
+ { GIMP_PASTE_TYPE_FLOATING_IN_PLACE, "GIMP_PASTE_TYPE_FLOATING_IN_PLACE", NULL },
+ { GIMP_PASTE_TYPE_FLOATING_INTO, "GIMP_PASTE_TYPE_FLOATING_INTO", NULL },
+ { GIMP_PASTE_TYPE_FLOATING_INTO_IN_PLACE, "GIMP_PASTE_TYPE_FLOATING_INTO_IN_PLACE", NULL },
+ { GIMP_PASTE_TYPE_NEW_LAYER, "GIMP_PASTE_TYPE_NEW_LAYER", NULL },
+ { GIMP_PASTE_TYPE_NEW_LAYER_IN_PLACE, "GIMP_PASTE_TYPE_NEW_LAYER_IN_PLACE", NULL },
+ { 0, NULL, NULL }
+ };
+
+ static GType type = 0;
+
+ if (G_UNLIKELY (! type))
+ {
+ type = g_enum_register_static ("GimpPasteType", values);
+ gimp_type_set_translation_context (type, "paste-type");
+ gimp_enum_set_value_descriptions (type, descs);
+ }
+
+ return type;
+}
+
+GType
+gimp_thumbnail_size_get_type (void)
+{
+ static const GEnumValue values[] =
+ {
+ { GIMP_THUMBNAIL_SIZE_NONE, "GIMP_THUMBNAIL_SIZE_NONE", "none" },
+ { GIMP_THUMBNAIL_SIZE_NORMAL, "GIMP_THUMBNAIL_SIZE_NORMAL", "normal" },
+ { GIMP_THUMBNAIL_SIZE_LARGE, "GIMP_THUMBNAIL_SIZE_LARGE", "large" },
+ { 0, NULL, NULL }
+ };
+
+ static const GimpEnumDesc descs[] =
+ {
+ { GIMP_THUMBNAIL_SIZE_NONE, NC_("thumbnail-size", "No thumbnails"), NULL },
+ { GIMP_THUMBNAIL_SIZE_NORMAL, NC_("thumbnail-size", "Normal (128x128)"), NULL },
+ { GIMP_THUMBNAIL_SIZE_LARGE, NC_("thumbnail-size", "Large (256x256)"), NULL },
+ { 0, NULL, NULL }
+ };
+
+ static GType type = 0;
+
+ if (G_UNLIKELY (! type))
+ {
+ type = g_enum_register_static ("GimpThumbnailSize", values);
+ gimp_type_set_translation_context (type, "thumbnail-size");
+ gimp_enum_set_value_descriptions (type, descs);
+ }
+
+ return type;
+}
+
+GType
+gimp_undo_event_get_type (void)
+{
+ static const GEnumValue values[] =
+ {
+ { GIMP_UNDO_EVENT_UNDO_PUSHED, "GIMP_UNDO_EVENT_UNDO_PUSHED", "undo-pushed" },
+ { GIMP_UNDO_EVENT_UNDO_EXPIRED, "GIMP_UNDO_EVENT_UNDO_EXPIRED", "undo-expired" },
+ { GIMP_UNDO_EVENT_REDO_EXPIRED, "GIMP_UNDO_EVENT_REDO_EXPIRED", "redo-expired" },
+ { GIMP_UNDO_EVENT_UNDO, "GIMP_UNDO_EVENT_UNDO", "undo" },
+ { GIMP_UNDO_EVENT_REDO, "GIMP_UNDO_EVENT_REDO", "redo" },
+ { GIMP_UNDO_EVENT_UNDO_FREE, "GIMP_UNDO_EVENT_UNDO_FREE", "undo-free" },
+ { GIMP_UNDO_EVENT_UNDO_FREEZE, "GIMP_UNDO_EVENT_UNDO_FREEZE", "undo-freeze" },
+ { GIMP_UNDO_EVENT_UNDO_THAW, "GIMP_UNDO_EVENT_UNDO_THAW", "undo-thaw" },
+ { 0, NULL, NULL }
+ };
+
+ static const GimpEnumDesc descs[] =
+ {
+ { GIMP_UNDO_EVENT_UNDO_PUSHED, "GIMP_UNDO_EVENT_UNDO_PUSHED", NULL },
+ { GIMP_UNDO_EVENT_UNDO_EXPIRED, "GIMP_UNDO_EVENT_UNDO_EXPIRED", NULL },
+ { GIMP_UNDO_EVENT_REDO_EXPIRED, "GIMP_UNDO_EVENT_REDO_EXPIRED", NULL },
+ { GIMP_UNDO_EVENT_UNDO, "GIMP_UNDO_EVENT_UNDO", NULL },
+ { GIMP_UNDO_EVENT_REDO, "GIMP_UNDO_EVENT_REDO", NULL },
+ { GIMP_UNDO_EVENT_UNDO_FREE, "GIMP_UNDO_EVENT_UNDO_FREE", NULL },
+ { GIMP_UNDO_EVENT_UNDO_FREEZE, "GIMP_UNDO_EVENT_UNDO_FREEZE", NULL },
+ { GIMP_UNDO_EVENT_UNDO_THAW, "GIMP_UNDO_EVENT_UNDO_THAW", NULL },
+ { 0, NULL, NULL }
+ };
+
+ static GType type = 0;
+
+ if (G_UNLIKELY (! type))
+ {
+ type = g_enum_register_static ("GimpUndoEvent", values);
+ gimp_type_set_translation_context (type, "undo-event");
+ gimp_enum_set_value_descriptions (type, descs);
+ }
+
+ return type;
+}
+
+GType
+gimp_undo_mode_get_type (void)
+{
+ static const GEnumValue values[] =
+ {
+ { GIMP_UNDO_MODE_UNDO, "GIMP_UNDO_MODE_UNDO", "undo" },
+ { GIMP_UNDO_MODE_REDO, "GIMP_UNDO_MODE_REDO", "redo" },
+ { 0, NULL, NULL }
+ };
+
+ static const GimpEnumDesc descs[] =
+ {
+ { GIMP_UNDO_MODE_UNDO, "GIMP_UNDO_MODE_UNDO", NULL },
+ { GIMP_UNDO_MODE_REDO, "GIMP_UNDO_MODE_REDO", NULL },
+ { 0, NULL, NULL }
+ };
+
+ static GType type = 0;
+
+ if (G_UNLIKELY (! type))
+ {
+ type = g_enum_register_static ("GimpUndoMode", values);
+ gimp_type_set_translation_context (type, "undo-mode");
+ gimp_enum_set_value_descriptions (type, descs);
+ }
+
+ return type;
+}
+
+GType
+gimp_undo_type_get_type (void)
+{
+ static const GEnumValue values[] =
+ {
+ { GIMP_UNDO_GROUP_NONE, "GIMP_UNDO_GROUP_NONE", "group-none" },
+ { GIMP_UNDO_GROUP_IMAGE_SCALE, "GIMP_UNDO_GROUP_IMAGE_SCALE", "group-image-scale" },
+ { GIMP_UNDO_GROUP_IMAGE_RESIZE, "GIMP_UNDO_GROUP_IMAGE_RESIZE", "group-image-resize" },
+ { GIMP_UNDO_GROUP_IMAGE_FLIP, "GIMP_UNDO_GROUP_IMAGE_FLIP", "group-image-flip" },
+ { GIMP_UNDO_GROUP_IMAGE_ROTATE, "GIMP_UNDO_GROUP_IMAGE_ROTATE", "group-image-rotate" },
+ { GIMP_UNDO_GROUP_IMAGE_TRANSFORM, "GIMP_UNDO_GROUP_IMAGE_TRANSFORM", "group-image-transform" },
+ { GIMP_UNDO_GROUP_IMAGE_CROP, "GIMP_UNDO_GROUP_IMAGE_CROP", "group-image-crop" },
+ { GIMP_UNDO_GROUP_IMAGE_CONVERT, "GIMP_UNDO_GROUP_IMAGE_CONVERT", "group-image-convert" },
+ { GIMP_UNDO_GROUP_IMAGE_ITEM_REMOVE, "GIMP_UNDO_GROUP_IMAGE_ITEM_REMOVE", "group-image-item-remove" },
+ { GIMP_UNDO_GROUP_IMAGE_ITEM_REORDER, "GIMP_UNDO_GROUP_IMAGE_ITEM_REORDER", "group-image-item-reorder" },
+ { GIMP_UNDO_GROUP_IMAGE_LAYERS_MERGE, "GIMP_UNDO_GROUP_IMAGE_LAYERS_MERGE", "group-image-layers-merge" },
+ { GIMP_UNDO_GROUP_IMAGE_VECTORS_MERGE, "GIMP_UNDO_GROUP_IMAGE_VECTORS_MERGE", "group-image-vectors-merge" },
+ { GIMP_UNDO_GROUP_IMAGE_QUICK_MASK, "GIMP_UNDO_GROUP_IMAGE_QUICK_MASK", "group-image-quick-mask" },
+ { GIMP_UNDO_GROUP_IMAGE_GRID, "GIMP_UNDO_GROUP_IMAGE_GRID", "group-image-grid" },
+ { GIMP_UNDO_GROUP_GUIDE, "GIMP_UNDO_GROUP_GUIDE", "group-guide" },
+ { GIMP_UNDO_GROUP_SAMPLE_POINT, "GIMP_UNDO_GROUP_SAMPLE_POINT", "group-sample-point" },
+ { GIMP_UNDO_GROUP_DRAWABLE, "GIMP_UNDO_GROUP_DRAWABLE", "group-drawable" },
+ { GIMP_UNDO_GROUP_DRAWABLE_MOD, "GIMP_UNDO_GROUP_DRAWABLE_MOD", "group-drawable-mod" },
+ { GIMP_UNDO_GROUP_MASK, "GIMP_UNDO_GROUP_MASK", "group-mask" },
+ { GIMP_UNDO_GROUP_ITEM_VISIBILITY, "GIMP_UNDO_GROUP_ITEM_VISIBILITY", "group-item-visibility" },
+ { GIMP_UNDO_GROUP_ITEM_LINKED, "GIMP_UNDO_GROUP_ITEM_LINKED", "group-item-linked" },
+ { GIMP_UNDO_GROUP_ITEM_PROPERTIES, "GIMP_UNDO_GROUP_ITEM_PROPERTIES", "group-item-properties" },
+ { GIMP_UNDO_GROUP_ITEM_DISPLACE, "GIMP_UNDO_GROUP_ITEM_DISPLACE", "group-item-displace" },
+ { GIMP_UNDO_GROUP_ITEM_SCALE, "GIMP_UNDO_GROUP_ITEM_SCALE", "group-item-scale" },
+ { GIMP_UNDO_GROUP_ITEM_RESIZE, "GIMP_UNDO_GROUP_ITEM_RESIZE", "group-item-resize" },
+ { GIMP_UNDO_GROUP_LAYER_ADD, "GIMP_UNDO_GROUP_LAYER_ADD", "group-layer-add" },
+ { GIMP_UNDO_GROUP_LAYER_ADD_MASK, "GIMP_UNDO_GROUP_LAYER_ADD_MASK", "group-layer-add-mask" },
+ { GIMP_UNDO_GROUP_LAYER_APPLY_MASK, "GIMP_UNDO_GROUP_LAYER_APPLY_MASK", "group-layer-apply-mask" },
+ { GIMP_UNDO_GROUP_FS_TO_LAYER, "GIMP_UNDO_GROUP_FS_TO_LAYER", "group-fs-to-layer" },
+ { GIMP_UNDO_GROUP_FS_FLOAT, "GIMP_UNDO_GROUP_FS_FLOAT", "group-fs-float" },
+ { GIMP_UNDO_GROUP_FS_ANCHOR, "GIMP_UNDO_GROUP_FS_ANCHOR", "group-fs-anchor" },
+ { GIMP_UNDO_GROUP_EDIT_PASTE, "GIMP_UNDO_GROUP_EDIT_PASTE", "group-edit-paste" },
+ { GIMP_UNDO_GROUP_EDIT_CUT, "GIMP_UNDO_GROUP_EDIT_CUT", "group-edit-cut" },
+ { GIMP_UNDO_GROUP_TEXT, "GIMP_UNDO_GROUP_TEXT", "group-text" },
+ { GIMP_UNDO_GROUP_TRANSFORM, "GIMP_UNDO_GROUP_TRANSFORM", "group-transform" },
+ { GIMP_UNDO_GROUP_PAINT, "GIMP_UNDO_GROUP_PAINT", "group-paint" },
+ { GIMP_UNDO_GROUP_PARASITE_ATTACH, "GIMP_UNDO_GROUP_PARASITE_ATTACH", "group-parasite-attach" },
+ { GIMP_UNDO_GROUP_PARASITE_REMOVE, "GIMP_UNDO_GROUP_PARASITE_REMOVE", "group-parasite-remove" },
+ { GIMP_UNDO_GROUP_VECTORS_IMPORT, "GIMP_UNDO_GROUP_VECTORS_IMPORT", "group-vectors-import" },
+ { GIMP_UNDO_GROUP_MISC, "GIMP_UNDO_GROUP_MISC", "group-misc" },
+ { GIMP_UNDO_IMAGE_TYPE, "GIMP_UNDO_IMAGE_TYPE", "image-type" },
+ { GIMP_UNDO_IMAGE_PRECISION, "GIMP_UNDO_IMAGE_PRECISION", "image-precision" },
+ { GIMP_UNDO_IMAGE_SIZE, "GIMP_UNDO_IMAGE_SIZE", "image-size" },
+ { GIMP_UNDO_IMAGE_RESOLUTION, "GIMP_UNDO_IMAGE_RESOLUTION", "image-resolution" },
+ { GIMP_UNDO_IMAGE_GRID, "GIMP_UNDO_IMAGE_GRID", "image-grid" },
+ { GIMP_UNDO_IMAGE_METADATA, "GIMP_UNDO_IMAGE_METADATA", "image-metadata" },
+ { GIMP_UNDO_IMAGE_COLORMAP, "GIMP_UNDO_IMAGE_COLORMAP", "image-colormap" },
+ { GIMP_UNDO_IMAGE_COLOR_MANAGED, "GIMP_UNDO_IMAGE_COLOR_MANAGED", "image-color-managed" },
+ { GIMP_UNDO_GUIDE, "GIMP_UNDO_GUIDE", "guide" },
+ { GIMP_UNDO_SAMPLE_POINT, "GIMP_UNDO_SAMPLE_POINT", "sample-point" },
+ { GIMP_UNDO_DRAWABLE, "GIMP_UNDO_DRAWABLE", "drawable" },
+ { GIMP_UNDO_DRAWABLE_MOD, "GIMP_UNDO_DRAWABLE_MOD", "drawable-mod" },
+ { GIMP_UNDO_MASK, "GIMP_UNDO_MASK", "mask" },
+ { GIMP_UNDO_ITEM_REORDER, "GIMP_UNDO_ITEM_REORDER", "item-reorder" },
+ { GIMP_UNDO_ITEM_RENAME, "GIMP_UNDO_ITEM_RENAME", "item-rename" },
+ { GIMP_UNDO_ITEM_DISPLACE, "GIMP_UNDO_ITEM_DISPLACE", "item-displace" },
+ { GIMP_UNDO_ITEM_VISIBILITY, "GIMP_UNDO_ITEM_VISIBILITY", "item-visibility" },
+ { GIMP_UNDO_ITEM_LINKED, "GIMP_UNDO_ITEM_LINKED", "item-linked" },
+ { GIMP_UNDO_ITEM_COLOR_TAG, "GIMP_UNDO_ITEM_COLOR_TAG", "item-color-tag" },
+ { GIMP_UNDO_ITEM_LOCK_CONTENT, "GIMP_UNDO_ITEM_LOCK_CONTENT", "item-lock-content" },
+ { GIMP_UNDO_ITEM_LOCK_POSITION, "GIMP_UNDO_ITEM_LOCK_POSITION", "item-lock-position" },
+ { GIMP_UNDO_LAYER_ADD, "GIMP_UNDO_LAYER_ADD", "layer-add" },
+ { GIMP_UNDO_LAYER_REMOVE, "GIMP_UNDO_LAYER_REMOVE", "layer-remove" },
+ { GIMP_UNDO_LAYER_MODE, "GIMP_UNDO_LAYER_MODE", "layer-mode" },
+ { GIMP_UNDO_LAYER_OPACITY, "GIMP_UNDO_LAYER_OPACITY", "layer-opacity" },
+ { GIMP_UNDO_LAYER_LOCK_ALPHA, "GIMP_UNDO_LAYER_LOCK_ALPHA", "layer-lock-alpha" },
+ { GIMP_UNDO_GROUP_LAYER_SUSPEND_RESIZE, "GIMP_UNDO_GROUP_LAYER_SUSPEND_RESIZE", "group-layer-suspend-resize" },
+ { GIMP_UNDO_GROUP_LAYER_RESUME_RESIZE, "GIMP_UNDO_GROUP_LAYER_RESUME_RESIZE", "group-layer-resume-resize" },
+ { GIMP_UNDO_GROUP_LAYER_SUSPEND_MASK, "GIMP_UNDO_GROUP_LAYER_SUSPEND_MASK", "group-layer-suspend-mask" },
+ { GIMP_UNDO_GROUP_LAYER_RESUME_MASK, "GIMP_UNDO_GROUP_LAYER_RESUME_MASK", "group-layer-resume-mask" },
+ { GIMP_UNDO_GROUP_LAYER_START_TRANSFORM, "GIMP_UNDO_GROUP_LAYER_START_TRANSFORM", "group-layer-start-transform" },
+ { GIMP_UNDO_GROUP_LAYER_END_TRANSFORM, "GIMP_UNDO_GROUP_LAYER_END_TRANSFORM", "group-layer-end-transform" },
+ { GIMP_UNDO_GROUP_LAYER_CONVERT, "GIMP_UNDO_GROUP_LAYER_CONVERT", "group-layer-convert" },
+ { GIMP_UNDO_TEXT_LAYER, "GIMP_UNDO_TEXT_LAYER", "text-layer" },
+ { GIMP_UNDO_TEXT_LAYER_MODIFIED, "GIMP_UNDO_TEXT_LAYER_MODIFIED", "text-layer-modified" },
+ { GIMP_UNDO_TEXT_LAYER_CONVERT, "GIMP_UNDO_TEXT_LAYER_CONVERT", "text-layer-convert" },
+ { GIMP_UNDO_LAYER_MASK_ADD, "GIMP_UNDO_LAYER_MASK_ADD", "layer-mask-add" },
+ { GIMP_UNDO_LAYER_MASK_REMOVE, "GIMP_UNDO_LAYER_MASK_REMOVE", "layer-mask-remove" },
+ { GIMP_UNDO_LAYER_MASK_APPLY, "GIMP_UNDO_LAYER_MASK_APPLY", "layer-mask-apply" },
+ { GIMP_UNDO_LAYER_MASK_SHOW, "GIMP_UNDO_LAYER_MASK_SHOW", "layer-mask-show" },
+ { GIMP_UNDO_CHANNEL_ADD, "GIMP_UNDO_CHANNEL_ADD", "channel-add" },
+ { GIMP_UNDO_CHANNEL_REMOVE, "GIMP_UNDO_CHANNEL_REMOVE", "channel-remove" },
+ { GIMP_UNDO_CHANNEL_COLOR, "GIMP_UNDO_CHANNEL_COLOR", "channel-color" },
+ { GIMP_UNDO_VECTORS_ADD, "GIMP_UNDO_VECTORS_ADD", "vectors-add" },
+ { GIMP_UNDO_VECTORS_REMOVE, "GIMP_UNDO_VECTORS_REMOVE", "vectors-remove" },
+ { GIMP_UNDO_VECTORS_MOD, "GIMP_UNDO_VECTORS_MOD", "vectors-mod" },
+ { GIMP_UNDO_FS_TO_LAYER, "GIMP_UNDO_FS_TO_LAYER", "fs-to-layer" },
+ { GIMP_UNDO_TRANSFORM_GRID, "GIMP_UNDO_TRANSFORM_GRID", "transform-grid" },
+ { GIMP_UNDO_PAINT, "GIMP_UNDO_PAINT", "paint" },
+ { GIMP_UNDO_INK, "GIMP_UNDO_INK", "ink" },
+ { GIMP_UNDO_FOREGROUND_SELECT, "GIMP_UNDO_FOREGROUND_SELECT", "foreground-select" },
+ { GIMP_UNDO_PARASITE_ATTACH, "GIMP_UNDO_PARASITE_ATTACH", "parasite-attach" },
+ { GIMP_UNDO_PARASITE_REMOVE, "GIMP_UNDO_PARASITE_REMOVE", "parasite-remove" },
+ { GIMP_UNDO_CANT, "GIMP_UNDO_CANT", "cant" },
+ { 0, NULL, NULL }
+ };
+
+ static const GimpEnumDesc descs[] =
+ {
+ { GIMP_UNDO_GROUP_NONE, NC_("undo-type", "<<invalid>>"), NULL },
+ { GIMP_UNDO_GROUP_IMAGE_SCALE, NC_("undo-type", "Scale image"), NULL },
+ { GIMP_UNDO_GROUP_IMAGE_RESIZE, NC_("undo-type", "Resize image"), NULL },
+ { GIMP_UNDO_GROUP_IMAGE_FLIP, NC_("undo-type", "Flip image"), NULL },
+ { GIMP_UNDO_GROUP_IMAGE_ROTATE, NC_("undo-type", "Rotate image"), NULL },
+ { GIMP_UNDO_GROUP_IMAGE_TRANSFORM, NC_("undo-type", "Transform image"), NULL },
+ { GIMP_UNDO_GROUP_IMAGE_CROP, NC_("undo-type", "Crop image"), NULL },
+ { GIMP_UNDO_GROUP_IMAGE_CONVERT, NC_("undo-type", "Convert image"), NULL },
+ { GIMP_UNDO_GROUP_IMAGE_ITEM_REMOVE, NC_("undo-type", "Remove item"), NULL },
+ { GIMP_UNDO_GROUP_IMAGE_ITEM_REORDER, NC_("undo-type", "Reorder item"), NULL },
+ { GIMP_UNDO_GROUP_IMAGE_LAYERS_MERGE, NC_("undo-type", "Merge layers"), NULL },
+ { GIMP_UNDO_GROUP_IMAGE_VECTORS_MERGE, NC_("undo-type", "Merge paths"), NULL },
+ { GIMP_UNDO_GROUP_IMAGE_QUICK_MASK, NC_("undo-type", "Quick Mask"), NULL },
+ { GIMP_UNDO_GROUP_IMAGE_GRID, NC_("undo-type", "Grid"), NULL },
+ { GIMP_UNDO_GROUP_GUIDE, NC_("undo-type", "Guide"), NULL },
+ { GIMP_UNDO_GROUP_SAMPLE_POINT, NC_("undo-type", "Sample Point"), NULL },
+ { GIMP_UNDO_GROUP_DRAWABLE, NC_("undo-type", "Layer/Channel"), NULL },
+ { GIMP_UNDO_GROUP_DRAWABLE_MOD, NC_("undo-type", "Layer/Channel modification"), NULL },
+ { GIMP_UNDO_GROUP_MASK, NC_("undo-type", "Selection mask"), NULL },
+ { GIMP_UNDO_GROUP_ITEM_VISIBILITY, NC_("undo-type", "Item visibility"), NULL },
+ { GIMP_UNDO_GROUP_ITEM_LINKED, NC_("undo-type", "Link/Unlink item"), NULL },
+ { GIMP_UNDO_GROUP_ITEM_PROPERTIES, NC_("undo-type", "Item properties"), NULL },
+ { GIMP_UNDO_GROUP_ITEM_DISPLACE, NC_("undo-type", "Move item"), NULL },
+ { GIMP_UNDO_GROUP_ITEM_SCALE, NC_("undo-type", "Scale item"), NULL },
+ { GIMP_UNDO_GROUP_ITEM_RESIZE, NC_("undo-type", "Resize item"), NULL },
+ { GIMP_UNDO_GROUP_LAYER_ADD, NC_("undo-type", "Add layer"), NULL },
+ { GIMP_UNDO_GROUP_LAYER_ADD_MASK, NC_("undo-type", "Add layer mask"), NULL },
+ { GIMP_UNDO_GROUP_LAYER_APPLY_MASK, NC_("undo-type", "Apply layer mask"), NULL },
+ { GIMP_UNDO_GROUP_FS_TO_LAYER, NC_("undo-type", "Floating selection to layer"), NULL },
+ { GIMP_UNDO_GROUP_FS_FLOAT, NC_("undo-type", "Float selection"), NULL },
+ { GIMP_UNDO_GROUP_FS_ANCHOR, NC_("undo-type", "Anchor floating selection"), NULL },
+ { GIMP_UNDO_GROUP_EDIT_PASTE, NC_("undo-type", "Paste"), NULL },
+ { GIMP_UNDO_GROUP_EDIT_CUT, NC_("undo-type", "Cut"), NULL },
+ { GIMP_UNDO_GROUP_TEXT, NC_("undo-type", "Text"), NULL },
+ { GIMP_UNDO_GROUP_TRANSFORM, NC_("undo-type", "Transform"), NULL },
+ { GIMP_UNDO_GROUP_PAINT, NC_("undo-type", "Paint"), NULL },
+ { GIMP_UNDO_GROUP_PARASITE_ATTACH, NC_("undo-type", "Attach parasite"), NULL },
+ { GIMP_UNDO_GROUP_PARASITE_REMOVE, NC_("undo-type", "Remove parasite"), NULL },
+ { GIMP_UNDO_GROUP_VECTORS_IMPORT, NC_("undo-type", "Import paths"), NULL },
+ { GIMP_UNDO_GROUP_MISC, NC_("undo-type", "Plug-In"), NULL },
+ { GIMP_UNDO_IMAGE_TYPE, NC_("undo-type", "Image type"), NULL },
+ { GIMP_UNDO_IMAGE_PRECISION, NC_("undo-type", "Image precision"), NULL },
+ { GIMP_UNDO_IMAGE_SIZE, NC_("undo-type", "Image size"), NULL },
+ { GIMP_UNDO_IMAGE_RESOLUTION, NC_("undo-type", "Image resolution change"), NULL },
+ { GIMP_UNDO_IMAGE_GRID, NC_("undo-type", "Grid"), NULL },
+ { GIMP_UNDO_IMAGE_METADATA, NC_("undo-type", "Change metadata"), NULL },
+ { GIMP_UNDO_IMAGE_COLORMAP, NC_("undo-type", "Change indexed palette"), NULL },
+ { GIMP_UNDO_IMAGE_COLOR_MANAGED, NC_("undo-type", "Change color managed state"), NULL },
+ { GIMP_UNDO_GUIDE, NC_("undo-type", "Guide"), NULL },
+ { GIMP_UNDO_SAMPLE_POINT, NC_("undo-type", "Sample Point"), NULL },
+ { GIMP_UNDO_DRAWABLE, NC_("undo-type", "Layer/Channel"), NULL },
+ { GIMP_UNDO_DRAWABLE_MOD, NC_("undo-type", "Layer/Channel modification"), NULL },
+ { GIMP_UNDO_MASK, NC_("undo-type", "Selection mask"), NULL },
+ { GIMP_UNDO_ITEM_REORDER, NC_("undo-type", "Reorder item"), NULL },
+ { GIMP_UNDO_ITEM_RENAME, NC_("undo-type", "Rename item"), NULL },
+ { GIMP_UNDO_ITEM_DISPLACE, NC_("undo-type", "Move item"), NULL },
+ { GIMP_UNDO_ITEM_VISIBILITY, NC_("undo-type", "Item visibility"), NULL },
+ { GIMP_UNDO_ITEM_LINKED, NC_("undo-type", "Link/Unlink item"), NULL },
+ { GIMP_UNDO_ITEM_COLOR_TAG, NC_("undo-type", "Item color tag"), NULL },
+ { GIMP_UNDO_ITEM_LOCK_CONTENT, NC_("undo-type", "Lock/Unlock content"), NULL },
+ { GIMP_UNDO_ITEM_LOCK_POSITION, NC_("undo-type", "Lock/Unlock position"), NULL },
+ { GIMP_UNDO_LAYER_ADD, NC_("undo-type", "New layer"), NULL },
+ { GIMP_UNDO_LAYER_REMOVE, NC_("undo-type", "Delete layer"), NULL },
+ { GIMP_UNDO_LAYER_MODE, NC_("undo-type", "Set layer mode"), NULL },
+ { GIMP_UNDO_LAYER_OPACITY, NC_("undo-type", "Set layer opacity"), NULL },
+ { GIMP_UNDO_LAYER_LOCK_ALPHA, NC_("undo-type", "Lock/Unlock alpha channel"), NULL },
+ { GIMP_UNDO_GROUP_LAYER_SUSPEND_RESIZE, NC_("undo-type", "Suspend group layer resize"), NULL },
+ { GIMP_UNDO_GROUP_LAYER_RESUME_RESIZE, NC_("undo-type", "Resume group layer resize"), NULL },
+ { GIMP_UNDO_GROUP_LAYER_SUSPEND_MASK, NC_("undo-type", "Suspend group layer mask"), NULL },
+ { GIMP_UNDO_GROUP_LAYER_RESUME_MASK, NC_("undo-type", "Resume group layer mask"), NULL },
+ { GIMP_UNDO_GROUP_LAYER_START_TRANSFORM, NC_("undo-type", "Start transforming group layer"), NULL },
+ { GIMP_UNDO_GROUP_LAYER_END_TRANSFORM, NC_("undo-type", "End transforming group layer"), NULL },
+ { GIMP_UNDO_GROUP_LAYER_CONVERT, NC_("undo-type", "Convert group layer"), NULL },
+ { GIMP_UNDO_TEXT_LAYER, NC_("undo-type", "Text layer"), NULL },
+ { GIMP_UNDO_TEXT_LAYER_MODIFIED, NC_("undo-type", "Text layer modification"), NULL },
+ { GIMP_UNDO_TEXT_LAYER_CONVERT, NC_("undo-type", "Convert text layer"), NULL },
+ { GIMP_UNDO_LAYER_MASK_ADD, NC_("undo-type", "Add layer mask"), NULL },
+ { GIMP_UNDO_LAYER_MASK_REMOVE, NC_("undo-type", "Delete layer mask"), NULL },
+ { GIMP_UNDO_LAYER_MASK_APPLY, NC_("undo-type", "Apply layer mask"), NULL },
+ { GIMP_UNDO_LAYER_MASK_SHOW, NC_("undo-type", "Show layer mask"), NULL },
+ { GIMP_UNDO_CHANNEL_ADD, NC_("undo-type", "New channel"), NULL },
+ { GIMP_UNDO_CHANNEL_REMOVE, NC_("undo-type", "Delete channel"), NULL },
+ { GIMP_UNDO_CHANNEL_COLOR, NC_("undo-type", "Channel color"), NULL },
+ { GIMP_UNDO_VECTORS_ADD, NC_("undo-type", "New path"), NULL },
+ { GIMP_UNDO_VECTORS_REMOVE, NC_("undo-type", "Delete path"), NULL },
+ { GIMP_UNDO_VECTORS_MOD, NC_("undo-type", "Path modification"), NULL },
+ { GIMP_UNDO_FS_TO_LAYER, NC_("undo-type", "Floating selection to layer"), NULL },
+ { GIMP_UNDO_TRANSFORM_GRID, NC_("undo-type", "Transform grid"), NULL },
+ { GIMP_UNDO_PAINT, NC_("undo-type", "Paint"), NULL },
+ { GIMP_UNDO_INK, NC_("undo-type", "Ink"), NULL },
+ { GIMP_UNDO_FOREGROUND_SELECT, NC_("undo-type", "Select foreground"), NULL },
+ { GIMP_UNDO_PARASITE_ATTACH, NC_("undo-type", "Attach parasite"), NULL },
+ { GIMP_UNDO_PARASITE_REMOVE, NC_("undo-type", "Remove parasite"), NULL },
+ { GIMP_UNDO_CANT, NC_("undo-type", "Not undoable"), NULL },
+ { 0, NULL, NULL }
+ };
+
+ static GType type = 0;
+
+ if (G_UNLIKELY (! type))
+ {
+ type = g_enum_register_static ("GimpUndoType", values);
+ gimp_type_set_translation_context (type, "undo-type");
+ gimp_enum_set_value_descriptions (type, descs);
+ }
+
+ return type;
+}
+
+GType
+gimp_view_size_get_type (void)
+{
+ static const GEnumValue values[] =
+ {
+ { GIMP_VIEW_SIZE_TINY, "GIMP_VIEW_SIZE_TINY", "tiny" },
+ { GIMP_VIEW_SIZE_EXTRA_SMALL, "GIMP_VIEW_SIZE_EXTRA_SMALL", "extra-small" },
+ { GIMP_VIEW_SIZE_SMALL, "GIMP_VIEW_SIZE_SMALL", "small" },
+ { GIMP_VIEW_SIZE_MEDIUM, "GIMP_VIEW_SIZE_MEDIUM", "medium" },
+ { GIMP_VIEW_SIZE_LARGE, "GIMP_VIEW_SIZE_LARGE", "large" },
+ { GIMP_VIEW_SIZE_EXTRA_LARGE, "GIMP_VIEW_SIZE_EXTRA_LARGE", "extra-large" },
+ { GIMP_VIEW_SIZE_HUGE, "GIMP_VIEW_SIZE_HUGE", "huge" },
+ { GIMP_VIEW_SIZE_ENORMOUS, "GIMP_VIEW_SIZE_ENORMOUS", "enormous" },
+ { GIMP_VIEW_SIZE_GIGANTIC, "GIMP_VIEW_SIZE_GIGANTIC", "gigantic" },
+ { 0, NULL, NULL }
+ };
+
+ static const GimpEnumDesc descs[] =
+ {
+ { GIMP_VIEW_SIZE_TINY, NC_("view-size", "Tiny"), NULL },
+ { GIMP_VIEW_SIZE_EXTRA_SMALL, NC_("view-size", "Very small"), NULL },
+ { GIMP_VIEW_SIZE_SMALL, NC_("view-size", "Small"), NULL },
+ { GIMP_VIEW_SIZE_MEDIUM, NC_("view-size", "Medium"), NULL },
+ { GIMP_VIEW_SIZE_LARGE, NC_("view-size", "Large"), NULL },
+ { GIMP_VIEW_SIZE_EXTRA_LARGE, NC_("view-size", "Very large"), NULL },
+ { GIMP_VIEW_SIZE_HUGE, NC_("view-size", "Huge"), NULL },
+ { GIMP_VIEW_SIZE_ENORMOUS, NC_("view-size", "Enormous"), NULL },
+ { GIMP_VIEW_SIZE_GIGANTIC, NC_("view-size", "Gigantic"), NULL },
+ { 0, NULL, NULL }
+ };
+
+ static GType type = 0;
+
+ if (G_UNLIKELY (! type))
+ {
+ type = g_enum_register_static ("GimpViewSize", values);
+ gimp_type_set_translation_context (type, "view-size");
+ gimp_enum_set_value_descriptions (type, descs);
+ }
+
+ return type;
+}
+
+GType
+gimp_view_type_get_type (void)
+{
+ static const GEnumValue values[] =
+ {
+ { GIMP_VIEW_TYPE_LIST, "GIMP_VIEW_TYPE_LIST", "list" },
+ { GIMP_VIEW_TYPE_GRID, "GIMP_VIEW_TYPE_GRID", "grid" },
+ { 0, NULL, NULL }
+ };
+
+ static const GimpEnumDesc descs[] =
+ {
+ { GIMP_VIEW_TYPE_LIST, NC_("view-type", "View as list"), NULL },
+ { GIMP_VIEW_TYPE_GRID, NC_("view-type", "View as grid"), NULL },
+ { 0, NULL, NULL }
+ };
+
+ static GType type = 0;
+
+ if (G_UNLIKELY (! type))
+ {
+ type = g_enum_register_static ("GimpViewType", values);
+ gimp_type_set_translation_context (type, "view-type");
+ gimp_enum_set_value_descriptions (type, descs);
+ }
+
+ return type;
+}
+
+
+/* Generated data ends here */
+
diff --git a/app/core/core-enums.h b/app/core/core-enums.h
new file mode 100644
index 0000000..8826145
--- /dev/null
+++ b/app/core/core-enums.h
@@ -0,0 +1,701 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __CORE_ENUMS_H__
+#define __CORE_ENUMS_H__
+
+
+#if 0
+ This file is parsed by two scripts, enumgen.pl in pdb,
+ and gimp-mkenums. All enums that are not marked with
+ /*< pdb-skip >*/ are exported to libgimp and the PDB. Enums that are
+ not marked with /*< skip >*/ are registered with the GType system.
+ If you want the enum to be skipped by both scripts, you have to use
+ /*< pdb-skip, skip >*/.
+
+ The same syntax applies to enum values.
+#endif
+
+
+/*
+ * these enums are registered with the type system
+ */
+
+
+#define GIMP_TYPE_ALIGN_REFERENCE_TYPE (gimp_align_reference_type_get_type ())
+
+GType gimp_align_reference_type_get_type (void) G_GNUC_CONST;
+
+typedef enum /*< pdb-skip >*/
+{
+ GIMP_ALIGN_REFERENCE_FIRST, /*< desc="First item" >*/
+ GIMP_ALIGN_REFERENCE_IMAGE, /*< desc="Image" >*/
+ GIMP_ALIGN_REFERENCE_SELECTION, /*< desc="Selection" >*/
+ GIMP_ALIGN_REFERENCE_ACTIVE_LAYER, /*< desc="Active layer" >*/
+ GIMP_ALIGN_REFERENCE_ACTIVE_CHANNEL, /*< desc="Active channel" >*/
+ GIMP_ALIGN_REFERENCE_ACTIVE_PATH /*< desc="Active path" >*/
+} GimpAlignReferenceType;
+
+
+#define GIMP_TYPE_ALIGNMENT_TYPE (gimp_alignment_type_get_type ())
+
+GType gimp_alignment_type_get_type (void) G_GNUC_CONST;
+
+typedef enum /*< pdb-skip >*/
+{
+ GIMP_ALIGN_LEFT,
+ GIMP_ALIGN_HCENTER,
+ GIMP_ALIGN_RIGHT,
+ GIMP_ALIGN_TOP,
+ GIMP_ALIGN_VCENTER,
+ GIMP_ALIGN_BOTTOM,
+ GIMP_ARRANGE_LEFT,
+ GIMP_ARRANGE_HCENTER,
+ GIMP_ARRANGE_RIGHT,
+ GIMP_ARRANGE_TOP,
+ GIMP_ARRANGE_VCENTER,
+ GIMP_ARRANGE_BOTTOM,
+ GIMP_ARRANGE_HFILL,
+ GIMP_ARRANGE_VFILL
+} GimpAlignmentType;
+
+
+#define GIMP_TYPE_CHANNEL_BORDER_STYLE (gimp_channel_border_style_get_type ())
+
+GType gimp_channel_border_style_get_type (void) G_GNUC_CONST;
+
+typedef enum /*< pdb-skip >*/
+{
+ GIMP_CHANNEL_BORDER_STYLE_HARD, /*< desc="Hard" >*/
+ GIMP_CHANNEL_BORDER_STYLE_SMOOTH, /*< desc="Smooth" >*/
+ GIMP_CHANNEL_BORDER_STYLE_FEATHERED /*< desc="Feathered" >*/
+} GimpChannelBorderStyle;
+
+
+/* Note: when appending values here, don't forget to update
+ * GimpColorFrame and other places use this enum to create combo
+ * boxes.
+ */
+#define GIMP_TYPE_COLOR_PICK_MODE (gimp_color_pick_mode_get_type ())
+
+GType gimp_color_pick_mode_get_type (void) G_GNUC_CONST;
+
+typedef enum /*< pdb-skip >*/
+{
+ GIMP_COLOR_PICK_MODE_PIXEL, /*< desc="Pixel" >*/
+ GIMP_COLOR_PICK_MODE_RGB_PERCENT, /*< desc="RGB (%)" >*/
+ GIMP_COLOR_PICK_MODE_RGB_U8, /*< desc="RGB (0..255)" >*/
+ GIMP_COLOR_PICK_MODE_HSV, /*< desc="HSV" >*/
+ GIMP_COLOR_PICK_MODE_LCH, /*< desc="CIE LCh" >*/
+ GIMP_COLOR_PICK_MODE_LAB, /*< desc="CIE LAB" >*/
+ GIMP_COLOR_PICK_MODE_CMYK, /*< desc="CMYK" >*/
+ GIMP_COLOR_PICK_MODE_XYY, /*< desc="CIE xyY" >*/
+ GIMP_COLOR_PICK_MODE_YUV, /*< desc="CIE Yu'v'" >*/
+
+ GIMP_COLOR_PICK_MODE_LAST = GIMP_COLOR_PICK_MODE_YUV /*< skip >*/
+} GimpColorPickMode;
+
+
+#define GIMP_TYPE_COLOR_PROFILE_POLICY (gimp_color_profile_policy_get_type ())
+
+GType gimp_color_profile_policy_get_type (void) G_GNUC_CONST;
+
+typedef enum /*< pdb-skip >*/
+{
+ GIMP_COLOR_PROFILE_POLICY_ASK, /*< desc="Ask what to do" >*/
+ GIMP_COLOR_PROFILE_POLICY_KEEP, /*< desc="Keep embedded profile" >*/
+ GIMP_COLOR_PROFILE_POLICY_CONVERT /*< desc="Convert to built-in sRGB or grayscale profile" >*/
+} GimpColorProfilePolicy;
+
+
+#define GIMP_TYPE_COMPONENT_MASK (gimp_component_mask_get_type ())
+
+GType gimp_component_mask_get_type (void) G_GNUC_CONST;
+
+typedef enum /*< pdb-skip >*/
+{
+ GIMP_COMPONENT_MASK_RED = 1 << 0,
+ GIMP_COMPONENT_MASK_GREEN = 1 << 1,
+ GIMP_COMPONENT_MASK_BLUE = 1 << 2,
+ GIMP_COMPONENT_MASK_ALPHA = 1 << 3,
+
+ GIMP_COMPONENT_MASK_ALL = (GIMP_COMPONENT_MASK_RED |
+ GIMP_COMPONENT_MASK_GREEN |
+ GIMP_COMPONENT_MASK_BLUE |
+ GIMP_COMPONENT_MASK_ALPHA)
+} GimpComponentMask;
+
+
+#define GIMP_TYPE_CONTAINER_POLICY (gimp_container_policy_get_type ())
+
+GType gimp_container_policy_get_type (void) G_GNUC_CONST;
+
+typedef enum /*< pdb-skip >*/
+{
+ GIMP_CONTAINER_POLICY_STRONG,
+ GIMP_CONTAINER_POLICY_WEAK
+} GimpContainerPolicy;
+
+
+#define GIMP_TYPE_CONVERT_DITHER_TYPE (gimp_convert_dither_type_get_type ())
+
+GType gimp_convert_dither_type_get_type (void) G_GNUC_CONST;
+
+typedef enum
+{
+ GIMP_CONVERT_DITHER_NONE, /*< desc="None" >*/
+ GIMP_CONVERT_DITHER_FS, /*< desc="Floyd-Steinberg (normal)" >*/
+ GIMP_CONVERT_DITHER_FS_LOWBLEED, /*< desc="Floyd-Steinberg (reduced color bleeding)" >*/
+ GIMP_CONVERT_DITHER_FIXED, /*< desc="Positioned" >*/
+ GIMP_CONVERT_DITHER_NODESTRUCT /*< pdb-skip, skip >*/
+} GimpConvertDitherType;
+
+
+#define GIMP_TYPE_CONVOLUTION_TYPE (gimp_convolution_type_get_type ())
+
+GType gimp_convolution_type_get_type (void) G_GNUC_CONST;
+
+typedef enum /*< pdb-skip >*/
+{
+ GIMP_NORMAL_CONVOL, /* Negative numbers truncated */
+ GIMP_ABSOLUTE_CONVOL, /* Absolute value */
+ GIMP_NEGATIVE_CONVOL /* add 127 to values */
+} GimpConvolutionType;
+
+
+#define GIMP_TYPE_CURVE_POINT_TYPE (gimp_curve_point_type_get_type ())
+
+GType gimp_curve_point_type_get_type (void) G_GNUC_CONST;
+
+typedef enum /*< pdb-skip >*/
+{
+ GIMP_CURVE_POINT_SMOOTH, /*< desc="Smooth" >*/
+ GIMP_CURVE_POINT_CORNER /*< desc="Corner" >*/
+} GimpCurvePointType;
+
+
+#define GIMP_TYPE_CURVE_TYPE (gimp_curve_type_get_type ())
+
+GType gimp_curve_type_get_type (void) G_GNUC_CONST;
+
+typedef enum /*< pdb-skip >*/
+{
+ GIMP_CURVE_SMOOTH, /*< desc="Smooth" >*/
+ GIMP_CURVE_FREE /*< desc="Freehand" >*/
+} GimpCurveType;
+
+
+#define GIMP_TYPE_DASH_PRESET (gimp_dash_preset_get_type ())
+
+GType gimp_dash_preset_get_type (void) G_GNUC_CONST;
+
+typedef enum /*< pdb-skip >*/
+{
+ GIMP_DASH_CUSTOM, /*< desc="Custom" >*/
+ GIMP_DASH_LINE, /*< desc="Line" >*/
+ GIMP_DASH_LONG_DASH, /*< desc="Long dashes" >*/
+ GIMP_DASH_MEDIUM_DASH, /*< desc="Medium dashes" >*/
+ GIMP_DASH_SHORT_DASH, /*< desc="Short dashes" >*/
+ GIMP_DASH_SPARSE_DOTS, /*< desc="Sparse dots" >*/
+ GIMP_DASH_NORMAL_DOTS, /*< desc="Normal dots" >*/
+ GIMP_DASH_DENSE_DOTS, /*< desc="Dense dots" >*/
+ GIMP_DASH_STIPPLES, /*< desc="Stipples" >*/
+ GIMP_DASH_DASH_DOT, /*< desc="Dash, dot" >*/
+ GIMP_DASH_DASH_DOT_DOT /*< desc="Dash, dot, dot" >*/
+} GimpDashPreset;
+
+
+#define GIMP_TYPE_DEBUG_POLICY (gimp_debug_policy_get_type ())
+
+GType gimp_debug_policy_get_type (void) G_GNUC_CONST;
+
+typedef enum /*< pdb-skip >*/
+{
+ GIMP_DEBUG_POLICY_WARNING, /*< desc="Debug warnings, critical errors and crashes" >*/
+ GIMP_DEBUG_POLICY_CRITICAL, /*< desc="Debug critical errors and crashes" >*/
+ GIMP_DEBUG_POLICY_FATAL, /*< desc="Debug crashes only" >*/
+ GIMP_DEBUG_POLICY_NEVER /*< desc="Never debug GIMP" >*/
+} GimpDebugPolicy;
+
+
+#define GIMP_TYPE_DIRTY_MASK (gimp_dirty_mask_get_type ())
+
+GType gimp_dirty_mask_get_type (void) G_GNUC_CONST;
+
+typedef enum /*< pdb-skip >*/
+{
+ GIMP_DIRTY_NONE = 0,
+
+ GIMP_DIRTY_IMAGE = 1 << 0,
+ GIMP_DIRTY_IMAGE_SIZE = 1 << 1,
+ GIMP_DIRTY_IMAGE_META = 1 << 2,
+ GIMP_DIRTY_IMAGE_STRUCTURE = 1 << 3,
+ GIMP_DIRTY_ITEM = 1 << 4,
+ GIMP_DIRTY_ITEM_META = 1 << 5,
+ GIMP_DIRTY_DRAWABLE = 1 << 6,
+ GIMP_DIRTY_VECTORS = 1 << 7,
+ GIMP_DIRTY_SELECTION = 1 << 8,
+ GIMP_DIRTY_ACTIVE_DRAWABLE = 1 << 9,
+
+ GIMP_DIRTY_ALL = 0xffff
+} GimpDirtyMask;
+
+
+#define GIMP_TYPE_DYNAMICS_OUTPUT_TYPE (gimp_dynamics_output_type_get_type ())
+
+GType gimp_dynamics_output_type_get_type (void) G_GNUC_CONST;
+
+typedef enum /*< pdb-skip >*/
+{
+ GIMP_DYNAMICS_OUTPUT_OPACITY, /*< desc="Opacity" >*/
+ GIMP_DYNAMICS_OUTPUT_SIZE, /*< desc="Size" >*/
+ GIMP_DYNAMICS_OUTPUT_ANGLE, /*< desc="Angle" >*/
+ GIMP_DYNAMICS_OUTPUT_COLOR, /*< desc="Color" >*/
+ GIMP_DYNAMICS_OUTPUT_HARDNESS, /*< desc="Hardness" >*/
+ GIMP_DYNAMICS_OUTPUT_FORCE, /*< desc="Force" >*/
+ GIMP_DYNAMICS_OUTPUT_ASPECT_RATIO, /*< desc="Aspect ratio" >*/
+ GIMP_DYNAMICS_OUTPUT_SPACING, /*< desc="Spacing" >*/
+ GIMP_DYNAMICS_OUTPUT_RATE, /*< desc="Rate" >*/
+ GIMP_DYNAMICS_OUTPUT_FLOW, /*< desc="Flow" >*/
+ GIMP_DYNAMICS_OUTPUT_JITTER, /*< desc="Jitter" >*/
+} GimpDynamicsOutputType;
+
+
+#define GIMP_TYPE_FILL_STYLE (gimp_fill_style_get_type ())
+
+GType gimp_fill_style_get_type (void) G_GNUC_CONST;
+
+typedef enum /*< pdb-skip >*/
+{
+ GIMP_FILL_STYLE_SOLID, /*< desc="Solid color" >*/
+ GIMP_FILL_STYLE_PATTERN /*< desc="Pattern" >*/
+} GimpFillStyle;
+
+
+#define GIMP_TYPE_FILTER_REGION (gimp_filter_region_get_type ())
+
+GType gimp_filter_region_get_type (void) G_GNUC_CONST;
+
+typedef enum /*< pdb-skip >*/
+{
+ GIMP_FILTER_REGION_SELECTION, /*< desc="Use the selection as input" >*/
+ GIMP_FILTER_REGION_DRAWABLE /*< desc="Use the entire layer as input" >*/
+} GimpFilterRegion;
+
+
+#define GIMP_TYPE_GRADIENT_COLOR (gimp_gradient_color_get_type ())
+
+GType gimp_gradient_color_get_type (void) G_GNUC_CONST;
+
+typedef enum /*< pdb-skip >*/
+{
+ GIMP_GRADIENT_COLOR_FIXED, /*< desc="Fixed" >*/
+ GIMP_GRADIENT_COLOR_FOREGROUND, /*< desc="Foreground color", abbrev="FG" >*/
+ GIMP_GRADIENT_COLOR_FOREGROUND_TRANSPARENT, /*< desc="Foreground color (transparent)", abbrev="FG (t)" >*/
+ GIMP_GRADIENT_COLOR_BACKGROUND, /*< desc="Background color", abbrev="BG" >*/
+ GIMP_GRADIENT_COLOR_BACKGROUND_TRANSPARENT /*< desc="Background color (transparent)", abbrev="BG (t)" >*/
+} GimpGradientColor;
+
+
+#define GIMP_TYPE_GRAVITY_TYPE (gimp_gravity_type_get_type ())
+
+GType gimp_gravity_type_get_type (void) G_GNUC_CONST;
+
+typedef enum /*< pdb-skip >*/
+{
+ GIMP_GRAVITY_NONE,
+ GIMP_GRAVITY_NORTH_WEST,
+ GIMP_GRAVITY_NORTH,
+ GIMP_GRAVITY_NORTH_EAST,
+ GIMP_GRAVITY_WEST,
+ GIMP_GRAVITY_CENTER,
+ GIMP_GRAVITY_EAST,
+ GIMP_GRAVITY_SOUTH_WEST,
+ GIMP_GRAVITY_SOUTH,
+ GIMP_GRAVITY_SOUTH_EAST
+} GimpGravityType;
+
+
+#define GIMP_TYPE_GUIDE_STYLE (gimp_guide_style_get_type ())
+
+GType gimp_guide_style_get_type (void) G_GNUC_CONST;
+
+typedef enum /*< pdb-skip >*/
+{
+ GIMP_GUIDE_STYLE_NONE,
+ GIMP_GUIDE_STYLE_NORMAL,
+ GIMP_GUIDE_STYLE_MIRROR,
+ GIMP_GUIDE_STYLE_MANDALA,
+ GIMP_GUIDE_STYLE_SPLIT_VIEW
+} GimpGuideStyle;
+
+
+#define GIMP_TYPE_HISTOGRAM_CHANNEL (gimp_histogram_channel_get_type ())
+
+GType gimp_histogram_channel_get_type (void) G_GNUC_CONST;
+
+typedef enum
+{
+ GIMP_HISTOGRAM_VALUE = 0, /*< desc="Value" >*/
+ GIMP_HISTOGRAM_RED = 1, /*< desc="Red" >*/
+ GIMP_HISTOGRAM_GREEN = 2, /*< desc="Green" >*/
+ GIMP_HISTOGRAM_BLUE = 3, /*< desc="Blue" >*/
+ GIMP_HISTOGRAM_ALPHA = 4, /*< desc="Alpha" >*/
+ GIMP_HISTOGRAM_LUMINANCE = 5, /*< desc="Luminance" >*/
+ GIMP_HISTOGRAM_RGB = 6 /*< desc="RGB", pdb-skip >*/
+} GimpHistogramChannel;
+
+
+#define GIMP_TYPE_ITEM_SET (gimp_item_set_get_type ())
+
+GType gimp_item_set_get_type (void) G_GNUC_CONST;
+
+typedef enum /*< pdb-skip >*/
+{
+ GIMP_ITEM_SET_NONE, /*< desc="None" >*/
+ GIMP_ITEM_SET_ALL, /*< desc="All layers" >*/
+ GIMP_ITEM_SET_IMAGE_SIZED, /*< desc="Image-sized layers" >*/
+ GIMP_ITEM_SET_VISIBLE, /*< desc="All visible layers" >*/
+ GIMP_ITEM_SET_LINKED /*< desc="All linked layers" >*/
+} GimpItemSet;
+
+
+#define GIMP_TYPE_MATTING_ENGINE (gimp_matting_engine_get_type ())
+
+GType gimp_matting_engine_get_type (void) G_GNUC_CONST;
+
+typedef enum /*< pdb-skip >*/
+{
+ GIMP_MATTING_ENGINE_GLOBAL, /*< desc="Matting Global" >*/
+ GIMP_MATTING_ENGINE_LEVIN, /*< desc="Matting Levin" >*/
+} GimpMattingEngine;
+
+
+#define GIMP_TYPE_MESSAGE_SEVERITY (gimp_message_severity_get_type ())
+
+GType gimp_message_severity_get_type (void) G_GNUC_CONST;
+
+typedef enum /*< pdb-skip >*/
+{
+ GIMP_MESSAGE_INFO, /*< desc="Message" >*/
+ GIMP_MESSAGE_WARNING, /*< desc="Warning" >*/
+ GIMP_MESSAGE_ERROR, /*< desc="Error" >*/
+ GIMP_MESSAGE_BUG_WARNING, /*< desc="WARNING" >*/
+ GIMP_MESSAGE_BUG_CRITICAL /*< desc="CRITICAL" >*/
+} GimpMessageSeverity;
+
+
+#define GIMP_TYPE_PASTE_TYPE (gimp_paste_type_get_type ())
+
+GType gimp_paste_type_get_type (void) G_GNUC_CONST;
+
+typedef enum /*< pdb-skip >*/
+{
+ GIMP_PASTE_TYPE_FLOATING,
+ GIMP_PASTE_TYPE_FLOATING_IN_PLACE,
+ GIMP_PASTE_TYPE_FLOATING_INTO,
+ GIMP_PASTE_TYPE_FLOATING_INTO_IN_PLACE,
+ GIMP_PASTE_TYPE_NEW_LAYER,
+ GIMP_PASTE_TYPE_NEW_LAYER_IN_PLACE
+} GimpPasteType;
+
+
+#define GIMP_TYPE_THUMBNAIL_SIZE (gimp_thumbnail_size_get_type ())
+
+GType gimp_thumbnail_size_get_type (void) G_GNUC_CONST;
+
+typedef enum /*< pdb-skip >*/
+{
+ GIMP_THUMBNAIL_SIZE_NONE = 0, /*< desc="No thumbnails" >*/
+ GIMP_THUMBNAIL_SIZE_NORMAL = 128, /*< desc="Normal (128x128)" >*/
+ GIMP_THUMBNAIL_SIZE_LARGE = 256 /*< desc="Large (256x256)" >*/
+} GimpThumbnailSize;
+
+
+#define GIMP_TYPE_UNDO_EVENT (gimp_undo_event_get_type ())
+
+GType gimp_undo_event_get_type (void) G_GNUC_CONST;
+
+typedef enum /*< pdb-skip >*/
+{
+ GIMP_UNDO_EVENT_UNDO_PUSHED, /* a new undo has been added to the undo stack */
+ GIMP_UNDO_EVENT_UNDO_EXPIRED, /* an undo has been freed from the undo stack */
+ GIMP_UNDO_EVENT_REDO_EXPIRED, /* a redo has been freed from the redo stack */
+ GIMP_UNDO_EVENT_UNDO, /* an undo has been executed */
+ GIMP_UNDO_EVENT_REDO, /* a redo has been executed */
+ GIMP_UNDO_EVENT_UNDO_FREE, /* all undo and redo info has been cleared */
+ GIMP_UNDO_EVENT_UNDO_FREEZE, /* undo has been frozen */
+ GIMP_UNDO_EVENT_UNDO_THAW /* undo has been thawn */
+} GimpUndoEvent;
+
+
+#define GIMP_TYPE_UNDO_MODE (gimp_undo_mode_get_type ())
+
+GType gimp_undo_mode_get_type (void) G_GNUC_CONST;
+
+typedef enum /*< pdb-skip >*/
+{
+ GIMP_UNDO_MODE_UNDO,
+ GIMP_UNDO_MODE_REDO
+} GimpUndoMode;
+
+
+#define GIMP_TYPE_UNDO_TYPE (gimp_undo_type_get_type ())
+
+GType gimp_undo_type_get_type (void) G_GNUC_CONST;
+
+typedef enum /*< pdb-skip >*/
+{
+ /* Type NO_UNDO_GROUP (0) is special - in the gimpimage structure it
+ * means there is no undo group currently being added to.
+ */
+ GIMP_UNDO_GROUP_NONE = 0, /*< desc="<<invalid>>" >*/
+
+ GIMP_UNDO_GROUP_FIRST = GIMP_UNDO_GROUP_NONE, /*< skip >*/
+
+ GIMP_UNDO_GROUP_IMAGE_SCALE, /*< desc="Scale image" >*/
+ GIMP_UNDO_GROUP_IMAGE_RESIZE, /*< desc="Resize image" >*/
+ GIMP_UNDO_GROUP_IMAGE_FLIP, /*< desc="Flip image" >*/
+ GIMP_UNDO_GROUP_IMAGE_ROTATE, /*< desc="Rotate image" >*/
+ GIMP_UNDO_GROUP_IMAGE_TRANSFORM, /*< desc="Transform image" >*/
+ GIMP_UNDO_GROUP_IMAGE_CROP, /*< desc="Crop image" >*/
+ GIMP_UNDO_GROUP_IMAGE_CONVERT, /*< desc="Convert image" >*/
+ GIMP_UNDO_GROUP_IMAGE_ITEM_REMOVE, /*< desc="Remove item" >*/
+ GIMP_UNDO_GROUP_IMAGE_ITEM_REORDER, /*< desc="Reorder item" >*/
+ GIMP_UNDO_GROUP_IMAGE_LAYERS_MERGE, /*< desc="Merge layers" >*/
+ GIMP_UNDO_GROUP_IMAGE_VECTORS_MERGE, /*< desc="Merge paths" >*/
+ GIMP_UNDO_GROUP_IMAGE_QUICK_MASK, /*< desc="Quick Mask" >*/
+ GIMP_UNDO_GROUP_IMAGE_GRID, /*< desc="Grid" >*/
+ GIMP_UNDO_GROUP_GUIDE, /*< desc="Guide" >*/
+ GIMP_UNDO_GROUP_SAMPLE_POINT, /*< desc="Sample Point" >*/
+ GIMP_UNDO_GROUP_DRAWABLE, /*< desc="Layer/Channel" >*/
+ GIMP_UNDO_GROUP_DRAWABLE_MOD, /*< desc="Layer/Channel modification" >*/
+ GIMP_UNDO_GROUP_MASK, /*< desc="Selection mask" >*/
+ GIMP_UNDO_GROUP_ITEM_VISIBILITY, /*< desc="Item visibility" >*/
+ GIMP_UNDO_GROUP_ITEM_LINKED, /*< desc="Link/Unlink item" >*/
+ GIMP_UNDO_GROUP_ITEM_PROPERTIES, /*< desc="Item properties" >*/
+ GIMP_UNDO_GROUP_ITEM_DISPLACE, /*< desc="Move item" >*/
+ GIMP_UNDO_GROUP_ITEM_SCALE, /*< desc="Scale item" >*/
+ GIMP_UNDO_GROUP_ITEM_RESIZE, /*< desc="Resize item" >*/
+ GIMP_UNDO_GROUP_LAYER_ADD, /*< desc="Add layer" >*/
+ GIMP_UNDO_GROUP_LAYER_ADD_MASK, /*< desc="Add layer mask" >*/
+ GIMP_UNDO_GROUP_LAYER_APPLY_MASK, /*< desc="Apply layer mask" >*/
+ GIMP_UNDO_GROUP_FS_TO_LAYER, /*< desc="Floating selection to layer" >*/
+ GIMP_UNDO_GROUP_FS_FLOAT, /*< desc="Float selection" >*/
+ GIMP_UNDO_GROUP_FS_ANCHOR, /*< desc="Anchor floating selection" >*/
+ GIMP_UNDO_GROUP_EDIT_PASTE, /*< desc="Paste" >*/
+ GIMP_UNDO_GROUP_EDIT_CUT, /*< desc="Cut" >*/
+ GIMP_UNDO_GROUP_TEXT, /*< desc="Text" >*/
+ GIMP_UNDO_GROUP_TRANSFORM, /*< desc="Transform" >*/
+ GIMP_UNDO_GROUP_PAINT, /*< desc="Paint" >*/
+ GIMP_UNDO_GROUP_PARASITE_ATTACH, /*< desc="Attach parasite" >*/
+ GIMP_UNDO_GROUP_PARASITE_REMOVE, /*< desc="Remove parasite" >*/
+ GIMP_UNDO_GROUP_VECTORS_IMPORT, /*< desc="Import paths" >*/
+ GIMP_UNDO_GROUP_MISC, /*< desc="Plug-In" >*/
+
+ GIMP_UNDO_GROUP_LAST = GIMP_UNDO_GROUP_MISC, /*< skip >*/
+
+ /* Undo types which actually do something */
+
+ GIMP_UNDO_IMAGE_TYPE, /*< desc="Image type" >*/
+ GIMP_UNDO_IMAGE_PRECISION, /*< desc="Image precision" >*/
+ GIMP_UNDO_IMAGE_SIZE, /*< desc="Image size" >*/
+ GIMP_UNDO_IMAGE_RESOLUTION, /*< desc="Image resolution change" >*/
+ GIMP_UNDO_IMAGE_GRID, /*< desc="Grid" >*/
+ GIMP_UNDO_IMAGE_METADATA, /*< desc="Change metadata" >*/
+ GIMP_UNDO_IMAGE_COLORMAP, /*< desc="Change indexed palette" >*/
+ GIMP_UNDO_IMAGE_COLOR_MANAGED, /*< desc="Change color managed state" >*/
+ GIMP_UNDO_GUIDE, /*< desc="Guide" >*/
+ GIMP_UNDO_SAMPLE_POINT, /*< desc="Sample Point" >*/
+ GIMP_UNDO_DRAWABLE, /*< desc="Layer/Channel" >*/
+ GIMP_UNDO_DRAWABLE_MOD, /*< desc="Layer/Channel modification" >*/
+ GIMP_UNDO_MASK, /*< desc="Selection mask" >*/
+ GIMP_UNDO_ITEM_REORDER, /*< desc="Reorder item" >*/
+ GIMP_UNDO_ITEM_RENAME, /*< desc="Rename item" >*/
+ GIMP_UNDO_ITEM_DISPLACE, /*< desc="Move item" >*/
+ GIMP_UNDO_ITEM_VISIBILITY, /*< desc="Item visibility" >*/
+ GIMP_UNDO_ITEM_LINKED, /*< desc="Link/Unlink item" >*/
+ GIMP_UNDO_ITEM_COLOR_TAG, /*< desc="Item color tag" >*/
+ GIMP_UNDO_ITEM_LOCK_CONTENT, /*< desc="Lock/Unlock content" >*/
+ GIMP_UNDO_ITEM_LOCK_POSITION, /*< desc="Lock/Unlock position" >*/
+ GIMP_UNDO_LAYER_ADD, /*< desc="New layer" >*/
+ GIMP_UNDO_LAYER_REMOVE, /*< desc="Delete layer" >*/
+ GIMP_UNDO_LAYER_MODE, /*< desc="Set layer mode" >*/
+ GIMP_UNDO_LAYER_OPACITY, /*< desc="Set layer opacity" >*/
+ GIMP_UNDO_LAYER_LOCK_ALPHA, /*< desc="Lock/Unlock alpha channel" >*/
+ GIMP_UNDO_GROUP_LAYER_SUSPEND_RESIZE, /*< desc="Suspend group layer resize" >*/
+ GIMP_UNDO_GROUP_LAYER_RESUME_RESIZE, /*< desc="Resume group layer resize" >*/
+ GIMP_UNDO_GROUP_LAYER_SUSPEND_MASK, /*< desc="Suspend group layer mask" >*/
+ GIMP_UNDO_GROUP_LAYER_RESUME_MASK, /*< desc="Resume group layer mask" >*/
+ GIMP_UNDO_GROUP_LAYER_START_TRANSFORM, /*< desc="Start transforming group layer" >*/
+ GIMP_UNDO_GROUP_LAYER_END_TRANSFORM, /*< desc="End transforming group layer" >*/
+ GIMP_UNDO_GROUP_LAYER_CONVERT, /*< desc="Convert group layer" >*/
+ GIMP_UNDO_TEXT_LAYER, /*< desc="Text layer" >*/
+ GIMP_UNDO_TEXT_LAYER_MODIFIED, /*< desc="Text layer modification" >*/
+ GIMP_UNDO_TEXT_LAYER_CONVERT, /*< desc="Convert text layer" >*/
+ GIMP_UNDO_LAYER_MASK_ADD, /*< desc="Add layer mask" >*/
+ GIMP_UNDO_LAYER_MASK_REMOVE, /*< desc="Delete layer mask" >*/
+ GIMP_UNDO_LAYER_MASK_APPLY, /*< desc="Apply layer mask" >*/
+ GIMP_UNDO_LAYER_MASK_SHOW, /*< desc="Show layer mask" >*/
+ GIMP_UNDO_CHANNEL_ADD, /*< desc="New channel" >*/
+ GIMP_UNDO_CHANNEL_REMOVE, /*< desc="Delete channel" >*/
+ GIMP_UNDO_CHANNEL_COLOR, /*< desc="Channel color" >*/
+ GIMP_UNDO_VECTORS_ADD, /*< desc="New path" >*/
+ GIMP_UNDO_VECTORS_REMOVE, /*< desc="Delete path" >*/
+ GIMP_UNDO_VECTORS_MOD, /*< desc="Path modification" >*/
+ GIMP_UNDO_FS_TO_LAYER, /*< desc="Floating selection to layer" >*/
+ GIMP_UNDO_TRANSFORM_GRID, /*< desc="Transform grid" >*/
+ GIMP_UNDO_PAINT, /*< desc="Paint" >*/
+ GIMP_UNDO_INK, /*< desc="Ink" >*/
+ GIMP_UNDO_FOREGROUND_SELECT, /*< desc="Select foreground" >*/
+ GIMP_UNDO_PARASITE_ATTACH, /*< desc="Attach parasite" >*/
+ GIMP_UNDO_PARASITE_REMOVE, /*< desc="Remove parasite" >*/
+
+ GIMP_UNDO_CANT /*< desc="Not undoable" >*/
+} GimpUndoType;
+
+
+#define GIMP_TYPE_VIEW_SIZE (gimp_view_size_get_type ())
+
+GType gimp_view_size_get_type (void) G_GNUC_CONST;
+
+typedef enum /*< pdb-skip >*/
+{
+ GIMP_VIEW_SIZE_TINY = 12, /*< desc="Tiny" >*/
+ GIMP_VIEW_SIZE_EXTRA_SMALL = 16, /*< desc="Very small" >*/
+ GIMP_VIEW_SIZE_SMALL = 24, /*< desc="Small" >*/
+ GIMP_VIEW_SIZE_MEDIUM = 32, /*< desc="Medium" >*/
+ GIMP_VIEW_SIZE_LARGE = 48, /*< desc="Large" >*/
+ GIMP_VIEW_SIZE_EXTRA_LARGE = 64, /*< desc="Very large" >*/
+ GIMP_VIEW_SIZE_HUGE = 96, /*< desc="Huge" >*/
+ GIMP_VIEW_SIZE_ENORMOUS = 128, /*< desc="Enormous" >*/
+ GIMP_VIEW_SIZE_GIGANTIC = 192 /*< desc="Gigantic" >*/
+} GimpViewSize;
+
+
+#define GIMP_TYPE_VIEW_TYPE (gimp_view_type_get_type ())
+
+GType gimp_view_type_get_type (void) G_GNUC_CONST;
+
+typedef enum /*< pdb-skip >*/
+{
+ GIMP_VIEW_TYPE_LIST, /*< desc="View as list" >*/
+ GIMP_VIEW_TYPE_GRID /*< desc="View as grid" >*/
+} GimpViewType;
+
+
+/*
+ * non-registered enums; register them if needed
+ */
+
+
+typedef enum /*< pdb-skip, skip >*/
+{
+ GIMP_CONTEXT_PROP_FIRST = 2,
+
+ GIMP_CONTEXT_PROP_IMAGE = GIMP_CONTEXT_PROP_FIRST,
+ GIMP_CONTEXT_PROP_DISPLAY = 3,
+ GIMP_CONTEXT_PROP_TOOL = 4,
+ GIMP_CONTEXT_PROP_PAINT_INFO = 5,
+ GIMP_CONTEXT_PROP_FOREGROUND = 6,
+ GIMP_CONTEXT_PROP_BACKGROUND = 7,
+ GIMP_CONTEXT_PROP_OPACITY = 8,
+ GIMP_CONTEXT_PROP_PAINT_MODE = 9,
+ GIMP_CONTEXT_PROP_BRUSH = 10,
+ GIMP_CONTEXT_PROP_DYNAMICS = 11,
+ GIMP_CONTEXT_PROP_MYBRUSH = 12,
+ GIMP_CONTEXT_PROP_PATTERN = 13,
+ GIMP_CONTEXT_PROP_GRADIENT = 14,
+ GIMP_CONTEXT_PROP_PALETTE = 15,
+ GIMP_CONTEXT_PROP_FONT = 16,
+ GIMP_CONTEXT_PROP_TOOL_PRESET = 17,
+ GIMP_CONTEXT_PROP_BUFFER = 18,
+ GIMP_CONTEXT_PROP_IMAGEFILE = 19,
+ GIMP_CONTEXT_PROP_TEMPLATE = 20,
+
+ GIMP_CONTEXT_PROP_LAST = GIMP_CONTEXT_PROP_TEMPLATE
+} GimpContextPropType;
+
+
+typedef enum /*< pdb-skip, skip >*/
+{
+ GIMP_CONTEXT_PROP_MASK_IMAGE = 1 << 2,
+ GIMP_CONTEXT_PROP_MASK_DISPLAY = 1 << 3,
+ GIMP_CONTEXT_PROP_MASK_TOOL = 1 << 4,
+ GIMP_CONTEXT_PROP_MASK_PAINT_INFO = 1 << 5,
+ GIMP_CONTEXT_PROP_MASK_FOREGROUND = 1 << 6,
+ GIMP_CONTEXT_PROP_MASK_BACKGROUND = 1 << 7,
+ GIMP_CONTEXT_PROP_MASK_OPACITY = 1 << 8,
+ GIMP_CONTEXT_PROP_MASK_PAINT_MODE = 1 << 9,
+ GIMP_CONTEXT_PROP_MASK_BRUSH = 1 << 10,
+ GIMP_CONTEXT_PROP_MASK_DYNAMICS = 1 << 11,
+ GIMP_CONTEXT_PROP_MASK_MYBRUSH = 1 << 12,
+ GIMP_CONTEXT_PROP_MASK_PATTERN = 1 << 13,
+ GIMP_CONTEXT_PROP_MASK_GRADIENT = 1 << 14,
+ GIMP_CONTEXT_PROP_MASK_PALETTE = 1 << 15,
+ GIMP_CONTEXT_PROP_MASK_FONT = 1 << 16,
+ GIMP_CONTEXT_PROP_MASK_TOOL_PRESET = 1 << 17,
+ GIMP_CONTEXT_PROP_MASK_BUFFER = 1 << 18,
+ GIMP_CONTEXT_PROP_MASK_IMAGEFILE = 1 << 19,
+ GIMP_CONTEXT_PROP_MASK_TEMPLATE = 1 << 20,
+
+ /* aliases */
+ GIMP_CONTEXT_PROP_MASK_PAINT = (GIMP_CONTEXT_PROP_MASK_FOREGROUND |
+ GIMP_CONTEXT_PROP_MASK_BACKGROUND |
+ GIMP_CONTEXT_PROP_MASK_OPACITY |
+ GIMP_CONTEXT_PROP_MASK_PAINT_MODE |
+ GIMP_CONTEXT_PROP_MASK_BRUSH |
+ GIMP_CONTEXT_PROP_MASK_DYNAMICS |
+ GIMP_CONTEXT_PROP_MASK_PATTERN |
+ GIMP_CONTEXT_PROP_MASK_GRADIENT),
+
+ GIMP_CONTEXT_PROP_MASK_ALL = (GIMP_CONTEXT_PROP_MASK_IMAGE |
+ GIMP_CONTEXT_PROP_MASK_DISPLAY |
+ GIMP_CONTEXT_PROP_MASK_TOOL |
+ GIMP_CONTEXT_PROP_MASK_PAINT_INFO |
+ GIMP_CONTEXT_PROP_MASK_MYBRUSH |
+ GIMP_CONTEXT_PROP_MASK_PALETTE |
+ GIMP_CONTEXT_PROP_MASK_FONT |
+ GIMP_CONTEXT_PROP_MASK_TOOL_PRESET |
+ GIMP_CONTEXT_PROP_MASK_BUFFER |
+ GIMP_CONTEXT_PROP_MASK_IMAGEFILE |
+ GIMP_CONTEXT_PROP_MASK_TEMPLATE |
+ GIMP_CONTEXT_PROP_MASK_PAINT)
+} GimpContextPropMask;
+
+
+typedef enum /*< pdb-skip, skip >*/
+{
+ GIMP_IMAGE_SCALE_OK,
+ GIMP_IMAGE_SCALE_TOO_SMALL,
+ GIMP_IMAGE_SCALE_TOO_BIG
+} GimpImageScaleCheckType;
+
+
+typedef enum /*< pdb-skip, skip >*/
+{
+ GIMP_ITEM_TYPE_LAYERS = 1 << 0,
+ GIMP_ITEM_TYPE_CHANNELS = 1 << 1,
+ GIMP_ITEM_TYPE_VECTORS = 1 << 2,
+
+ GIMP_ITEM_TYPE_ALL = (GIMP_ITEM_TYPE_LAYERS |
+ GIMP_ITEM_TYPE_CHANNELS |
+ GIMP_ITEM_TYPE_VECTORS)
+} GimpItemTypeMask;
+
+
+#endif /* __CORE_ENUMS_H__ */
diff --git a/app/core/core-types.h b/app/core/core-types.h
new file mode 100644
index 0000000..bc331b4
--- /dev/null
+++ b/app/core/core-types.h
@@ -0,0 +1,303 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __CORE_TYPES_H__
+#define __CORE_TYPES_H__
+
+
+#include "libgimpbase/gimpbasetypes.h"
+#include "libgimpmath/gimpmathtypes.h"
+#include "libgimpcolor/gimpcolortypes.h"
+#include "libgimpmodule/gimpmoduletypes.h"
+#include "libgimpthumb/gimpthumb-types.h"
+
+#include "config/config-types.h"
+
+#include "core/core-enums.h"
+
+
+/* former base/ defines */
+
+#define MAX_CHANNELS 4
+
+#define RED 0
+#define GREEN 1
+#define BLUE 2
+#define ALPHA 3
+
+#define GRAY 0
+#define ALPHA_G 1
+
+#define INDEXED 0
+#define ALPHA_I 1
+
+
+/* defines */
+
+#define GIMP_COORDS_MIN_PRESSURE 0.0
+#define GIMP_COORDS_MAX_PRESSURE 1.0
+#define GIMP_COORDS_DEFAULT_PRESSURE 1.0
+
+#define GIMP_COORDS_MIN_TILT -1.0
+#define GIMP_COORDS_MAX_TILT 1.0
+#define GIMP_COORDS_DEFAULT_TILT 0.0
+
+#define GIMP_COORDS_MIN_WHEEL 0.0
+#define GIMP_COORDS_MAX_WHEEL 1.0
+#define GIMP_COORDS_DEFAULT_WHEEL 0.5
+
+#define GIMP_COORDS_DEFAULT_VELOCITY 0.0
+
+#define GIMP_COORDS_DEFAULT_DIRECTION 0.0
+
+#define GIMP_COORDS_DEFAULT_XSCALE 1.0
+#define GIMP_COORDS_DEFAULT_YSCALE 1.0
+
+#define GIMP_COORDS_DEFAULT_VALUES { 0.0, 0.0, \
+ GIMP_COORDS_DEFAULT_PRESSURE, \
+ GIMP_COORDS_DEFAULT_TILT, \
+ GIMP_COORDS_DEFAULT_TILT, \
+ GIMP_COORDS_DEFAULT_WHEEL, \
+ GIMP_COORDS_DEFAULT_VELOCITY, \
+ GIMP_COORDS_DEFAULT_DIRECTION,\
+ GIMP_COORDS_DEFAULT_XSCALE, \
+ GIMP_COORDS_DEFAULT_YSCALE }
+
+
+/* base classes */
+
+typedef struct _GimpObject GimpObject;
+typedef struct _GimpViewable GimpViewable;
+typedef struct _GimpFilter GimpFilter;
+typedef struct _GimpItem GimpItem;
+typedef struct _GimpAuxItem GimpAuxItem;
+
+typedef struct _Gimp Gimp;
+typedef struct _GimpImage GimpImage;
+
+
+/* containers */
+
+typedef struct _GimpContainer GimpContainer;
+typedef struct _GimpList GimpList;
+typedef struct _GimpDocumentList GimpDocumentList;
+typedef struct _GimpDrawableStack GimpDrawableStack;
+typedef struct _GimpFilteredContainer GimpFilteredContainer;
+typedef struct _GimpFilterStack GimpFilterStack;
+typedef struct _GimpItemStack GimpItemStack;
+typedef struct _GimpLayerStack GimpLayerStack;
+typedef struct _GimpTaggedContainer GimpTaggedContainer;
+typedef struct _GimpTreeProxy GimpTreeProxy;
+
+
+/* not really a container */
+
+typedef struct _GimpItemTree GimpItemTree;
+
+
+/* context objects */
+
+typedef struct _GimpContext GimpContext;
+typedef struct _GimpFillOptions GimpFillOptions;
+typedef struct _GimpStrokeOptions GimpStrokeOptions;
+typedef struct _GimpToolOptions GimpToolOptions;
+
+
+/* info objects */
+
+typedef struct _GimpPaintInfo GimpPaintInfo;
+typedef struct _GimpToolGroup GimpToolGroup;
+typedef struct _GimpToolInfo GimpToolInfo;
+typedef struct _GimpToolItem GimpToolItem;
+
+
+/* data objects */
+
+typedef struct _GimpDataFactory GimpDataFactory;
+typedef struct _GimpDataLoaderFactory GimpDataLoaderFactory;
+typedef struct _GimpData GimpData;
+typedef struct _GimpBrush GimpBrush;
+typedef struct _GimpBrushCache GimpBrushCache;
+typedef struct _GimpBrushClipboard GimpBrushClipboard;
+typedef struct _GimpBrushGenerated GimpBrushGenerated;
+typedef struct _GimpBrushPipe GimpBrushPipe;
+typedef struct _GimpCurve GimpCurve;
+typedef struct _GimpDynamics GimpDynamics;
+typedef struct _GimpDynamicsOutput GimpDynamicsOutput;
+typedef struct _GimpGradient GimpGradient;
+typedef struct _GimpMybrush GimpMybrush;
+typedef struct _GimpPalette GimpPalette;
+typedef struct _GimpPaletteMru GimpPaletteMru;
+typedef struct _GimpPattern GimpPattern;
+typedef struct _GimpPatternClipboard GimpPatternClipboard;
+typedef struct _GimpToolPreset GimpToolPreset;
+typedef struct _GimpTagCache GimpTagCache;
+
+
+/* drawable objects */
+
+typedef struct _GimpDrawable GimpDrawable;
+typedef struct _GimpChannel GimpChannel;
+typedef struct _GimpLayerMask GimpLayerMask;
+typedef struct _GimpSelection GimpSelection;
+typedef struct _GimpLayer GimpLayer;
+typedef struct _GimpGroupLayer GimpGroupLayer;
+
+
+/* auxillary image items */
+
+typedef struct _GimpGuide GimpGuide;
+typedef struct _GimpSamplePoint GimpSamplePoint;
+
+
+/* undo objects */
+
+typedef struct _GimpUndo GimpUndo;
+typedef struct _GimpUndoStack GimpUndoStack;
+typedef struct _GimpUndoAccumulator GimpUndoAccumulator;
+
+
+/* Symmetry transformations */
+
+typedef struct _GimpSymmetry GimpSymmetry;
+typedef struct _GimpMirror GimpMirror;
+typedef struct _GimpTiling GimpTiling;
+typedef struct _GimpMandala GimpMandala;
+
+
+/* misc objects */
+
+typedef struct _GimpAsync GimpAsync;
+typedef struct _GimpAsyncSet GimpAsyncSet;
+typedef struct _GimpBuffer GimpBuffer;
+typedef struct _GimpDrawableFilter GimpDrawableFilter;
+typedef struct _GimpEnvironTable GimpEnvironTable;
+typedef struct _GimpHistogram GimpHistogram;
+typedef struct _GimpIdTable GimpIdTable;
+typedef struct _GimpImagefile GimpImagefile;
+typedef struct _GimpImageProxy GimpImageProxy;
+typedef struct _GimpInterpreterDB GimpInterpreterDB;
+typedef struct _GimpLineArt GimpLineArt;
+typedef struct _GimpObjectQueue GimpObjectQueue;
+typedef struct _GimpParasiteList GimpParasiteList;
+typedef struct _GimpPdbProgress GimpPdbProgress;
+typedef struct _GimpProjection GimpProjection;
+typedef struct _GimpSettings GimpSettings;
+typedef struct _GimpSubProgress GimpSubProgress;
+typedef struct _GimpTag GimpTag;
+typedef struct _GimpTreeHandler GimpTreeHandler;
+typedef struct _GimpTriviallyCancelableWaitable GimpTriviallyCancelableWaitable;
+typedef struct _GimpUncancelableWaitable GimpUncancelableWaitable;
+
+
+/* interfaces */
+
+typedef struct _GimpCancelable GimpCancelable; /* dummy typedef */
+typedef struct _GimpPickable GimpPickable; /* dummy typedef */
+typedef struct _GimpProgress GimpProgress; /* dummy typedef */
+typedef struct _GimpProjectable GimpProjectable; /* dummy typedef */
+typedef struct _GimpTagged GimpTagged; /* dummy typedef */
+typedef struct _GimpWaitable GimpWaitable; /* dummy typedef */
+
+
+/* non-object types */
+
+typedef struct _GimpBacktrace GimpBacktrace;
+typedef struct _GimpBoundSeg GimpBoundSeg;
+typedef struct _GimpChunkIterator GimpChunkIterator;
+typedef struct _GimpCoords GimpCoords;
+typedef struct _GimpGradientSegment GimpGradientSegment;
+typedef struct _GimpPaletteEntry GimpPaletteEntry;
+typedef struct _GimpScanConvert GimpScanConvert;
+typedef struct _GimpTempBuf GimpTempBuf;
+typedef guint32 GimpTattoo;
+
+/* The following hack is made so that we can reuse the definition
+ * the cairo definition of cairo_path_t without having to translate
+ * between our own version of a bezier description and cairos version.
+ *
+ * to avoid having to include <cairo.h> in each and every file
+ * including this file we only use the "real" definition when cairo.h
+ * already has been included and use something else.
+ *
+ * Note that if you really want to work with GimpBezierDesc (except just
+ * passing pointers to it around) you also need to include <cairo.h>.
+ */
+#ifdef CAIRO_VERSION
+typedef cairo_path_t GimpBezierDesc;
+#else
+typedef void * GimpBezierDesc;
+#endif
+
+
+/* functions */
+
+typedef void (* GimpInitStatusFunc) (const gchar *text1,
+ const gchar *text2,
+ gdouble percentage);
+
+typedef gboolean (* GimpObjectFilterFunc) (GimpObject *object,
+ gpointer user_data);
+
+typedef gint64 (* GimpMemsizeFunc) (gpointer instance,
+ gint64 *gui_size);
+
+typedef void (* GimpRunAsyncFunc) (GimpAsync *async,
+ gpointer user_data);
+
+
+/* structs */
+
+struct _GimpCoords
+{
+ gdouble x;
+ gdouble y;
+ gdouble pressure;
+ gdouble xtilt;
+ gdouble ytilt;
+ gdouble wheel;
+ gdouble velocity;
+ gdouble direction;
+ gdouble xscale; /* the view scale */
+ gdouble yscale;
+ gdouble angle; /* the view rotation angle */
+ gboolean reflect; /* whether the view is reflected */
+ gboolean extended;
+};
+
+/* temp hack as replacement for GdkSegment */
+
+typedef struct _GimpSegment GimpSegment;
+
+struct _GimpSegment
+{
+ gint x1;
+ gint y1;
+ gint x2;
+ gint y2;
+};
+
+
+#include "gegl/gimp-gegl-types.h"
+#include "paint/paint-types.h"
+#include "text/text-types.h"
+#include "vectors/vectors-types.h"
+#include "pdb/pdb-types.h"
+#include "plug-in/plug-in-types.h"
+
+
+#endif /* __CORE_TYPES_H__ */
diff --git a/app/core/gimp-atomic.c b/app/core/gimp-atomic.c
new file mode 100644
index 0000000..c5a54b8
--- /dev/null
+++ b/app/core/gimp-atomic.c
@@ -0,0 +1,95 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimp-atomic.c
+ * Copyright (C) 2018 Ell
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "core-types.h"
+
+#include "gimp-atomic.h"
+
+
+/* GSList */
+
+
+static GSList gimp_atomic_slist_sentinel;
+
+
+void
+gimp_atomic_slist_push_head (GSList * volatile *list,
+ gpointer data)
+{
+ GSList *old_head;
+ GSList *new_head;
+
+ g_return_if_fail (list != NULL);
+
+ new_head = g_slist_alloc ();
+
+ new_head->data = data;
+
+ do
+ {
+ do
+ {
+ old_head = g_atomic_pointer_get (list);
+ }
+ while (old_head == &gimp_atomic_slist_sentinel);
+
+ new_head->next = old_head;
+ }
+ while (! g_atomic_pointer_compare_and_exchange (list, old_head, new_head));
+}
+
+gpointer
+gimp_atomic_slist_pop_head (GSList * volatile *list)
+{
+ GSList *old_head;
+ GSList *new_head;
+ gpointer data;
+
+ g_return_val_if_fail (list != NULL, NULL);
+
+ do
+ {
+ do
+ {
+ old_head = g_atomic_pointer_get (list);
+ }
+ while (old_head == &gimp_atomic_slist_sentinel);
+
+ if (! old_head)
+ return NULL;
+ }
+ while (! g_atomic_pointer_compare_and_exchange (list,
+ old_head,
+ &gimp_atomic_slist_sentinel));
+
+ new_head = old_head->next;
+ data = old_head->data;
+
+ g_atomic_pointer_set (list, new_head);
+
+ g_slist_free1 (old_head);
+
+ return data;
+}
diff --git a/app/core/gimp-atomic.h b/app/core/gimp-atomic.h
new file mode 100644
index 0000000..c1d71cc
--- /dev/null
+++ b/app/core/gimp-atomic.h
@@ -0,0 +1,32 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimp-atomic.h
+ * Copyright (C) 2018 Ell
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_ATOMIC_H__
+#define __GIMP_ATOMIC_H__
+
+
+/* GSList */
+
+void gimp_atomic_slist_push_head (GSList * volatile *list,
+ gpointer data);
+gpointer gimp_atomic_slist_pop_head (GSList * volatile *list);
+
+
+#endif /* __GIMP_ATOMIC_H__ */
diff --git a/app/core/gimp-batch.c b/app/core/gimp-batch.c
new file mode 100644
index 0000000..e7908a2
--- /dev/null
+++ b/app/core/gimp-batch.c
@@ -0,0 +1,203 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+#include <stdlib.h>
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpbase/gimpbase.h"
+
+#include "core-types.h"
+
+#include "gimp.h"
+#include "gimp-batch.h"
+#include "gimpparamspecs.h"
+
+#include "pdb/gimppdb.h"
+#include "pdb/gimpprocedure.h"
+
+#include "gimp-intl.h"
+
+
+#define BATCH_DEFAULT_EVAL_PROC "plug-in-script-fu-eval"
+
+
+static void gimp_batch_exit_after_callback (Gimp *gimp) G_GNUC_NORETURN;
+
+static void gimp_batch_run_cmd (Gimp *gimp,
+ const gchar *proc_name,
+ GimpProcedure *procedure,
+ GimpRunMode run_mode,
+ const gchar *cmd);
+
+
+void
+gimp_batch_run (Gimp *gimp,
+ const gchar *batch_interpreter,
+ const gchar **batch_commands)
+{
+ gulong exit_id;
+
+ if (! batch_commands || ! batch_commands[0])
+ return;
+
+ exit_id = g_signal_connect_after (gimp, "exit",
+ G_CALLBACK (gimp_batch_exit_after_callback),
+ NULL);
+
+ if (! batch_interpreter)
+ {
+ batch_interpreter = g_getenv ("GIMP_BATCH_INTERPRETER");
+
+ if (! batch_interpreter)
+ {
+ batch_interpreter = BATCH_DEFAULT_EVAL_PROC;
+
+ if (gimp->be_verbose)
+ g_printerr ("No batch interpreter specified, using the default "
+ "'%s'.\n", batch_interpreter);
+ }
+ }
+
+ /* script-fu text console, hardcoded for backward compatibility */
+
+ if (strcmp (batch_interpreter, "plug-in-script-fu-eval") == 0 &&
+ strcmp (batch_commands[0], "-") == 0)
+ {
+ const gchar *proc_name = "plug-in-script-fu-text-console";
+ GimpProcedure *procedure = gimp_pdb_lookup_procedure (gimp->pdb,
+ proc_name);
+
+ if (procedure)
+ gimp_batch_run_cmd (gimp, proc_name, procedure,
+ GIMP_RUN_NONINTERACTIVE, NULL);
+ else
+ g_message (_("The batch interpreter '%s' is not available. "
+ "Batch mode disabled."), proc_name);
+ }
+ else
+ {
+ GimpProcedure *eval_proc = gimp_pdb_lookup_procedure (gimp->pdb,
+ batch_interpreter);
+
+ if (eval_proc)
+ {
+ gint i;
+
+ for (i = 0; batch_commands[i]; i++)
+ gimp_batch_run_cmd (gimp, batch_interpreter, eval_proc,
+ GIMP_RUN_NONINTERACTIVE, batch_commands[i]);
+ }
+ else
+ {
+ g_message (_("The batch interpreter '%s' is not available. "
+ "Batch mode disabled."), batch_interpreter);
+ }
+ }
+
+ g_signal_handler_disconnect (gimp, exit_id);
+}
+
+
+/*
+ * The purpose of this handler is to exit GIMP cleanly when the batch
+ * procedure calls the gimp-exit procedure. Without this callback, the
+ * message "batch command experienced an execution error" would appear
+ * and gimp would hang forever.
+ */
+static void
+gimp_batch_exit_after_callback (Gimp *gimp)
+{
+ if (gimp->be_verbose)
+ g_print ("EXIT: %s\n", G_STRFUNC);
+
+ gegl_exit ();
+
+ exit (EXIT_SUCCESS);
+}
+
+static void
+gimp_batch_run_cmd (Gimp *gimp,
+ const gchar *proc_name,
+ GimpProcedure *procedure,
+ GimpRunMode run_mode,
+ const gchar *cmd)
+{
+ GimpValueArray *args;
+ GimpValueArray *return_vals;
+ GError *error = NULL;
+ gint i = 0;
+
+ args = gimp_procedure_get_arguments (procedure);
+
+ if (procedure->num_args > i &&
+ GIMP_IS_PARAM_SPEC_INT32 (procedure->args[i]))
+ g_value_set_int (gimp_value_array_index (args, i++), run_mode);
+
+ if (procedure->num_args > i &&
+ GIMP_IS_PARAM_SPEC_STRING (procedure->args[i]))
+ g_value_set_static_string (gimp_value_array_index (args, i++), cmd);
+
+ return_vals =
+ gimp_pdb_execute_procedure_by_name_args (gimp->pdb,
+ gimp_get_user_context (gimp),
+ NULL, &error,
+ proc_name, args);
+
+ switch (g_value_get_enum (gimp_value_array_index (return_vals, 0)))
+ {
+ case GIMP_PDB_EXECUTION_ERROR:
+ if (error)
+ {
+ g_printerr ("batch command experienced an execution error:\n"
+ "%s\n", error->message);
+ }
+ else
+ {
+ g_printerr ("batch command experienced an execution error\n");
+ }
+ break;
+
+ case GIMP_PDB_CALLING_ERROR:
+ if (error)
+ {
+ g_printerr ("batch command experienced a calling error:\n"
+ "%s\n", error->message);
+ }
+ else
+ {
+ g_printerr ("batch command experienced a calling error\n");
+ }
+ break;
+
+ case GIMP_PDB_SUCCESS:
+ g_printerr ("batch command executed successfully\n");
+ break;
+ }
+
+ gimp_value_array_unref (return_vals);
+ gimp_value_array_unref (args);
+
+ if (error)
+ g_error_free (error);
+
+ return;
+}
diff --git a/app/core/gimp-batch.h b/app/core/gimp-batch.h
new file mode 100644
index 0000000..3e6945c
--- /dev/null
+++ b/app/core/gimp-batch.h
@@ -0,0 +1,27 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_BATCH_H__
+#define __GIMP_BATCH_H__
+
+
+void gimp_batch_run (Gimp *gimp,
+ const gchar *batch_interpreter,
+ const gchar **batch_commands);
+
+
+#endif /* __GIMP_BATCH_H__ */
diff --git a/app/core/gimp-cairo.c b/app/core/gimp-cairo.c
new file mode 100644
index 0000000..9d5574f
--- /dev/null
+++ b/app/core/gimp-cairo.c
@@ -0,0 +1,217 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimp-cairo.c
+ * Copyright (C) 2010-2012 Michael Natterer <mitch@gimp.org>
+ *
+ * Some code here is based on code from librsvg that was originally
+ * written by Raph Levien <raph@artofcode.com> for Gill.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <cairo.h>
+#include <gegl.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+#include "libgimpmath/gimpmath.h"
+#include "libgimpcolor/gimpcolor.h"
+
+#include "core-types.h"
+
+#include "gimp-cairo.h"
+
+
+#define REV (2.0 * G_PI)
+
+
+static cairo_user_data_key_t surface_data_key = { 0, };
+
+
+cairo_pattern_t *
+gimp_cairo_pattern_create_stipple (const GimpRGB *fg,
+ const GimpRGB *bg,
+ gint index,
+ gdouble offset_x,
+ gdouble offset_y)
+{
+ cairo_surface_t *surface;
+ cairo_pattern_t *pattern;
+ guchar *data;
+ guchar *d;
+ guchar fg_r, fg_g, fg_b, fg_a;
+ guchar bg_r, bg_g, bg_b, bg_a;
+ gint x, y;
+
+ g_return_val_if_fail (fg != NULL, NULL);
+ g_return_val_if_fail (bg != NULL, NULL);
+
+ data = g_malloc (8 * 8 * 4);
+
+ gimp_rgba_get_uchar (fg, &fg_r, &fg_g, &fg_b, &fg_a);
+ gimp_rgba_get_uchar (bg, &bg_r, &bg_g, &bg_b, &bg_a);
+
+ d = data;
+
+ for (y = 0; y < 8; y++)
+ {
+ for (x = 0; x < 8; x++)
+ {
+ if ((x + y + index) % 8 >= 4)
+ GIMP_CAIRO_ARGB32_SET_PIXEL (d, fg_r, fg_g, fg_b, fg_a);
+ else
+ GIMP_CAIRO_ARGB32_SET_PIXEL (d, bg_r, bg_g, bg_b, bg_a);
+
+ d += 4;
+ }
+ }
+
+ surface = cairo_image_surface_create_for_data (data,
+ CAIRO_FORMAT_ARGB32,
+ 8, 8, 8 * 4);
+ cairo_surface_set_user_data (surface, &surface_data_key,
+ data, (cairo_destroy_func_t) g_free);
+
+ pattern = cairo_pattern_create_for_surface (surface);
+ cairo_pattern_set_extend (pattern, CAIRO_EXTEND_REPEAT);
+
+ cairo_surface_destroy (surface);
+
+ if (offset_x != 0.0 || offset_y != 0.0)
+ {
+ cairo_matrix_t matrix;
+
+ cairo_matrix_init_translate (&matrix,
+ fmod (offset_x, 8),
+ fmod (offset_y, 8));
+ cairo_pattern_set_matrix (pattern, &matrix);
+ }
+
+ return pattern;
+}
+
+void
+gimp_cairo_arc (cairo_t *cr,
+ gdouble center_x,
+ gdouble center_y,
+ gdouble radius,
+ gdouble start_angle,
+ gdouble slice_angle)
+{
+ g_return_if_fail (cr != NULL);
+
+ if (slice_angle >= 0)
+ {
+ cairo_arc_negative (cr, center_x, center_y, radius,
+ - start_angle,
+ - start_angle - slice_angle);
+ }
+ else
+ {
+ cairo_arc (cr, center_x, center_y, radius,
+ - start_angle,
+ - start_angle - slice_angle);
+ }
+}
+
+void
+gimp_cairo_rounded_rectangle (cairo_t *cr,
+ gdouble x,
+ gdouble y,
+ gdouble width,
+ gdouble height,
+ gdouble corner_radius)
+{
+ g_return_if_fail (cr != NULL);
+
+ if (width < 0.0)
+ {
+ x += width;
+ width = -width;
+ }
+
+ if (height < 0.0)
+ {
+ y += height;
+ height = -height;
+ }
+
+ corner_radius = CLAMP (corner_radius, 0.0, MIN (width, height) / 2.0);
+
+ if (corner_radius == 0.0)
+ {
+ cairo_rectangle (cr, x, y, width, height);
+
+ return;
+ }
+
+ cairo_new_sub_path (cr);
+
+ cairo_arc (cr,
+ x + corner_radius, y + corner_radius,
+ corner_radius,
+ 0.50 * REV, 0.75 * REV);
+ cairo_line_to (cr,
+ x + width - corner_radius, y);
+
+ cairo_arc (cr,
+ x + width - corner_radius, y + corner_radius,
+ corner_radius,
+ 0.75 * REV, 1.00 * REV);
+ cairo_line_to (cr,
+ x + width, y + height - corner_radius);
+
+ cairo_arc (cr,
+ x + width - corner_radius, y + height - corner_radius,
+ corner_radius,
+ 0.00 * REV, 0.25 * REV);
+ cairo_line_to (cr,
+ x + corner_radius, y + height);
+
+ cairo_arc (cr,
+ x + corner_radius, y + height - corner_radius,
+ corner_radius,
+ 0.25 * REV, 0.50 * REV);
+ cairo_line_to (cr,
+ x, y + corner_radius);
+
+ cairo_close_path (cr);
+}
+
+void
+gimp_cairo_segments (cairo_t *cr,
+ GimpSegment *segs,
+ gint n_segs)
+{
+ gint i;
+
+ g_return_if_fail (cr != NULL);
+ g_return_if_fail (segs != NULL && n_segs > 0);
+
+ for (i = 0; i < n_segs; i++)
+ {
+ if (segs[i].x1 == segs[i].x2)
+ {
+ cairo_move_to (cr, segs[i].x1 + 0.5, segs[i].y1 + 0.5);
+ cairo_line_to (cr, segs[i].x2 + 0.5, segs[i].y2 - 0.5);
+ }
+ else
+ {
+ cairo_move_to (cr, segs[i].x1 + 0.5, segs[i].y1 + 0.5);
+ cairo_line_to (cr, segs[i].x2 - 0.5, segs[i].y2 + 0.5);
+ }
+ }
+}
diff --git a/app/core/gimp-cairo.h b/app/core/gimp-cairo.h
new file mode 100644
index 0000000..b767740
--- /dev/null
+++ b/app/core/gimp-cairo.h
@@ -0,0 +1,51 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimp-cairo.h
+ * Copyright (C) 2010-2012 Michael Natterer <mitch@gimp.org>
+ *
+ * Some code here is based on code from librsvg that was originally
+ * written by Raph Levien <raph@artofcode.com> for Gill.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __APP_GIMP_CAIRO_H__
+#define __APP_GIMP_CAIRO_H__
+
+
+cairo_pattern_t * gimp_cairo_pattern_create_stipple (const GimpRGB *fg,
+ const GimpRGB *bg,
+ gint index,
+ gdouble offset_x,
+ gdouble offset_y);
+
+void gimp_cairo_arc (cairo_t *cr,
+ gdouble center_x,
+ gdouble center_y,
+ gdouble radius,
+ gdouble start_angle,
+ gdouble slice_angle);
+void gimp_cairo_rounded_rectangle (cairo_t *cr,
+ gdouble x,
+ gdouble y,
+ gdouble width,
+ gdouble height,
+ gdouble corner_radius);
+void gimp_cairo_segments (cairo_t *cr,
+ GimpSegment *segs,
+ gint n_segs);
+
+
+#endif /* __APP_GIMP_CAIRO_H__ */
diff --git a/app/core/gimp-contexts.c b/app/core/gimp-contexts.c
new file mode 100644
index 0000000..59d1e53
--- /dev/null
+++ b/app/core/gimp-contexts.c
@@ -0,0 +1,161 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-1999 Spencer Kimball and Peter Mattis
+ *
+ * gimp-contexts.c
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpbase/gimpbase.h"
+#include "libgimpconfig/gimpconfig.h"
+
+#include "core-types.h"
+
+#include "gimp.h"
+#include "gimperror.h"
+#include "gimp-contexts.h"
+#include "gimpcontext.h"
+
+#include "config/gimpconfig-file.h"
+
+#include "gimp-intl.h"
+
+
+void
+gimp_contexts_init (Gimp *gimp)
+{
+ GimpContext *context;
+
+ g_return_if_fail (GIMP_IS_GIMP (gimp));
+
+ /* the default context contains the user's saved preferences
+ *
+ * TODO: load from disk
+ */
+ context = gimp_context_new (gimp, "Default", NULL);
+ gimp_set_default_context (gimp, context);
+ g_object_unref (context);
+
+ /* the initial user_context is a straight copy of the default context
+ */
+ context = gimp_context_new (gimp, "User", context);
+ gimp_set_user_context (gimp, context);
+ g_object_unref (context);
+}
+
+void
+gimp_contexts_exit (Gimp *gimp)
+{
+ g_return_if_fail (GIMP_IS_GIMP (gimp));
+
+ gimp_set_user_context (gimp, NULL);
+ gimp_set_default_context (gimp, NULL);
+}
+
+gboolean
+gimp_contexts_load (Gimp *gimp,
+ GError **error)
+{
+ GFile *file;
+ GError *my_error = NULL;
+ gboolean success;
+
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ file = gimp_directory_file ("contextrc", NULL);
+
+ if (gimp->be_verbose)
+ g_print ("Parsing '%s'\n", gimp_file_get_utf8_name (file));
+
+ success = gimp_config_deserialize_gfile (GIMP_CONFIG (gimp_get_user_context (gimp)),
+ file,
+ NULL, &my_error);
+
+ g_object_unref (file);
+
+ if (! success)
+ {
+ if (my_error->code == GIMP_CONFIG_ERROR_OPEN_ENOENT)
+ {
+ g_clear_error (&my_error);
+ success = TRUE;
+ }
+ else
+ {
+ g_propagate_error (error, my_error);
+ }
+ }
+
+ return success;
+}
+
+gboolean
+gimp_contexts_save (Gimp *gimp,
+ GError **error)
+{
+ GFile *file;
+ gboolean success;
+
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ file = gimp_directory_file ("contextrc", NULL);
+
+ if (gimp->be_verbose)
+ g_print ("Writing '%s'\n", gimp_file_get_utf8_name (file));
+
+ success = gimp_config_serialize_to_gfile (GIMP_CONFIG (gimp_get_user_context (gimp)),
+ file,
+ "GIMP user context",
+ "end of user context",
+ NULL, error);
+
+ g_object_unref (file);
+
+ return success;
+}
+
+gboolean
+gimp_contexts_clear (Gimp *gimp,
+ GError **error)
+{
+ GFile *file;
+ GError *my_error = NULL;
+ gboolean success = TRUE;
+
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), FALSE);
+
+ file = gimp_directory_file ("contextrc", NULL);
+
+ if (! g_file_delete (file, NULL, &my_error) &&
+ my_error->code != G_IO_ERROR_NOT_FOUND)
+ {
+ success = FALSE;
+
+ g_set_error (error, GIMP_ERROR, GIMP_FAILED,
+ _("Deleting \"%s\" failed: %s"),
+ gimp_file_get_utf8_name (file), my_error->message);
+ }
+
+ g_clear_error (&my_error);
+ g_object_unref (file);
+
+ return success;
+}
diff --git a/app/core/gimp-contexts.h b/app/core/gimp-contexts.h
new file mode 100644
index 0000000..9aa402b
--- /dev/null
+++ b/app/core/gimp-contexts.h
@@ -0,0 +1,36 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimp-contexts.h
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_CONTEXTS_H__
+#define __GIMP_CONTEXTS_H__
+
+
+void gimp_contexts_init (Gimp *gimp);
+void gimp_contexts_exit (Gimp *gimp);
+
+gboolean gimp_contexts_load (Gimp *gimp,
+ GError **error);
+gboolean gimp_contexts_save (Gimp *gimp,
+ GError **error);
+
+gboolean gimp_contexts_clear (Gimp *gimp,
+ GError **error);
+
+
+#endif /* __GIMP_CONTEXTS_H__ */
diff --git a/app/core/gimp-data-factories.c b/app/core/gimp-data-factories.c
new file mode 100644
index 0000000..69b5e2a
--- /dev/null
+++ b/app/core/gimp-data-factories.c
@@ -0,0 +1,433 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-2002 Spencer Kimball, Peter Mattis, and others
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpbase/gimpbase.h"
+#include "libgimpconfig/gimpconfig.h"
+
+#include "core-types.h"
+
+#include "config/gimprc.h"
+
+#include "gimp.h"
+#include "gimp-data-factories.h"
+#include "gimp-gradients.h"
+#include "gimp-memsize.h"
+#include "gimp-palettes.h"
+#include "gimpcontainer.h"
+#include "gimpbrush-load.h"
+#include "gimpbrush.h"
+#include "gimpbrushclipboard.h"
+#include "gimpbrushgenerated-load.h"
+#include "gimpbrushpipe-load.h"
+#include "gimpdataloaderfactory.h"
+#include "gimpdynamics.h"
+#include "gimpdynamics-load.h"
+#include "gimpgradient-load.h"
+#include "gimpgradient.h"
+#include "gimpmybrush-load.h"
+#include "gimpmybrush.h"
+#include "gimppalette-load.h"
+#include "gimppalette.h"
+#include "gimppattern-load.h"
+#include "gimppattern.h"
+#include "gimppatternclipboard.h"
+#include "gimptagcache.h"
+#include "gimptoolpreset.h"
+#include "gimptoolpreset-load.h"
+
+#include "text/gimpfontfactory.h"
+
+#include "gimp-intl.h"
+
+
+void
+gimp_data_factories_init (Gimp *gimp)
+{
+ g_return_if_fail (GIMP_IS_GIMP (gimp));
+
+ gimp->brush_factory =
+ gimp_data_loader_factory_new (gimp,
+ GIMP_TYPE_BRUSH,
+ "brush-path",
+ "brush-path-writable",
+ gimp_brush_new,
+ gimp_brush_get_standard);
+ gimp_object_set_static_name (GIMP_OBJECT (gimp->brush_factory),
+ "brush factory");
+ gimp_data_loader_factory_add_loader (gimp->brush_factory,
+ "GIMP Brush",
+ gimp_brush_load,
+ GIMP_BRUSH_FILE_EXTENSION,
+ TRUE);
+ gimp_data_loader_factory_add_loader (gimp->brush_factory,
+ "GIMP Brush Pixmap",
+ gimp_brush_load,
+ GIMP_BRUSH_PIXMAP_FILE_EXTENSION,
+ FALSE);
+ gimp_data_loader_factory_add_loader (gimp->brush_factory,
+ "Photoshop ABR Brush",
+ gimp_brush_load_abr,
+ GIMP_BRUSH_PS_FILE_EXTENSION,
+ FALSE);
+ gimp_data_loader_factory_add_loader (gimp->brush_factory,
+ "Paint Shop Pro JBR Brush",
+ gimp_brush_load_abr,
+ GIMP_BRUSH_PSP_FILE_EXTENSION,
+ FALSE);
+ gimp_data_loader_factory_add_loader (gimp->brush_factory,
+ "GIMP Generated Brush",
+ gimp_brush_generated_load,
+ GIMP_BRUSH_GENERATED_FILE_EXTENSION,
+ TRUE);
+ gimp_data_loader_factory_add_loader (gimp->brush_factory,
+ "GIMP Brush Pipe",
+ gimp_brush_pipe_load,
+ GIMP_BRUSH_PIPE_FILE_EXTENSION,
+ TRUE);
+
+ gimp->dynamics_factory =
+ gimp_data_loader_factory_new (gimp,
+ GIMP_TYPE_DYNAMICS,
+ "dynamics-path",
+ "dynamics-path-writable",
+ gimp_dynamics_new,
+ gimp_dynamics_get_standard);
+ gimp_object_set_static_name (GIMP_OBJECT (gimp->dynamics_factory),
+ "dynamics factory");
+ gimp_data_loader_factory_add_loader (gimp->dynamics_factory,
+ "GIMP Paint Dynamics",
+ gimp_dynamics_load,
+ GIMP_DYNAMICS_FILE_EXTENSION,
+ TRUE);
+
+ gimp->mybrush_factory =
+ gimp_data_loader_factory_new (gimp,
+ GIMP_TYPE_MYBRUSH,
+ "mypaint-brush-path",
+ "mypaint-brush-path-writable",
+ NULL,
+ NULL);
+ gimp_object_set_static_name (GIMP_OBJECT (gimp->mybrush_factory),
+ "mypaint brush factory");
+ gimp_data_loader_factory_add_loader (gimp->mybrush_factory,
+ "MyPaint Brush",
+ gimp_mybrush_load,
+ GIMP_MYBRUSH_FILE_EXTENSION,
+ FALSE);
+
+ gimp->pattern_factory =
+ gimp_data_loader_factory_new (gimp,
+ GIMP_TYPE_PATTERN,
+ "pattern-path",
+ "pattern-path-writable",
+ NULL,
+ gimp_pattern_get_standard);
+ gimp_object_set_static_name (GIMP_OBJECT (gimp->pattern_factory),
+ "pattern factory");
+ gimp_data_loader_factory_add_loader (gimp->pattern_factory,
+ "GIMP Pattern",
+ gimp_pattern_load,
+ GIMP_PATTERN_FILE_EXTENSION,
+ TRUE);
+ gimp_data_loader_factory_add_fallback (gimp->pattern_factory,
+ "Pattern from GdkPixbuf",
+ gimp_pattern_load_pixbuf);
+
+ gimp->gradient_factory =
+ gimp_data_loader_factory_new (gimp,
+ GIMP_TYPE_GRADIENT,
+ "gradient-path",
+ "gradient-path-writable",
+ gimp_gradient_new,
+ gimp_gradient_get_standard);
+ gimp_object_set_static_name (GIMP_OBJECT (gimp->gradient_factory),
+ "gradient factory");
+ gimp_data_loader_factory_add_loader (gimp->gradient_factory,
+ "GIMP Gradient",
+ gimp_gradient_load,
+ GIMP_GRADIENT_FILE_EXTENSION,
+ TRUE);
+ gimp_data_loader_factory_add_loader (gimp->gradient_factory,
+ "SVG Gradient",
+ gimp_gradient_load_svg,
+ GIMP_GRADIENT_SVG_FILE_EXTENSION,
+ FALSE);
+
+ gimp->palette_factory =
+ gimp_data_loader_factory_new (gimp,
+ GIMP_TYPE_PALETTE,
+ "palette-path",
+ "palette-path-writable",
+ gimp_palette_new,
+ gimp_palette_get_standard);
+ gimp_object_set_static_name (GIMP_OBJECT (gimp->palette_factory),
+ "palette factory");
+ gimp_data_loader_factory_add_loader (gimp->palette_factory,
+ "GIMP Palette",
+ gimp_palette_load,
+ GIMP_PALETTE_FILE_EXTENSION,
+ TRUE);
+
+ gimp->font_factory =
+ gimp_font_factory_new (gimp,
+ "font-path");
+ gimp_object_set_static_name (GIMP_OBJECT (gimp->font_factory),
+ "font factory");
+
+ gimp->tool_preset_factory =
+ gimp_data_loader_factory_new (gimp,
+ GIMP_TYPE_TOOL_PRESET,
+ "tool-preset-path",
+ "tool-preset-path-writable",
+ gimp_tool_preset_new,
+ NULL);
+ gimp_object_set_static_name (GIMP_OBJECT (gimp->tool_preset_factory),
+ "tool preset factory");
+ gimp_data_loader_factory_add_loader (gimp->tool_preset_factory,
+ "GIMP Tool Preset",
+ gimp_tool_preset_load,
+ GIMP_TOOL_PRESET_FILE_EXTENSION,
+ TRUE);
+
+ gimp->tag_cache = gimp_tag_cache_new ();
+}
+
+void
+gimp_data_factories_add_builtin (Gimp *gimp)
+{
+ GimpData *data;
+
+ g_return_if_fail (GIMP_IS_GIMP (gimp));
+
+ /* add the builtin FG -> BG etc. gradients */
+ gimp_gradients_init (gimp);
+
+ /* add the color history palette */
+ gimp_palettes_init (gimp);
+
+ /* add the clipboard brushes */
+ data = gimp_brush_clipboard_new (gimp, FALSE);
+ gimp_data_make_internal (data, "gimp-brush-clipboard-image");
+ gimp_container_add (gimp_data_factory_get_container (gimp->brush_factory),
+ GIMP_OBJECT (data));
+ g_object_unref (data);
+
+ data = gimp_brush_clipboard_new (gimp, TRUE);
+ gimp_data_make_internal (data, "gimp-brush-clipboard-mask");
+ gimp_container_add (gimp_data_factory_get_container (gimp->brush_factory),
+ GIMP_OBJECT (data));
+ g_object_unref (data);
+
+ /* add the clipboard pattern */
+ data = gimp_pattern_clipboard_new (gimp);
+ gimp_data_make_internal (data, "gimp-pattern-clipboard-image");
+ gimp_container_add (gimp_data_factory_get_container (gimp->pattern_factory),
+ GIMP_OBJECT (data));
+ g_object_unref (data);
+}
+
+void
+gimp_data_factories_clear (Gimp *gimp)
+{
+ g_return_if_fail (GIMP_IS_GIMP (gimp));
+
+ if (gimp->brush_factory)
+ gimp_data_factory_data_free (gimp->brush_factory);
+
+ if (gimp->dynamics_factory)
+ gimp_data_factory_data_free (gimp->dynamics_factory);
+
+ if (gimp->mybrush_factory)
+ gimp_data_factory_data_free (gimp->mybrush_factory);
+
+ if (gimp->pattern_factory)
+ gimp_data_factory_data_free (gimp->pattern_factory);
+
+ if (gimp->gradient_factory)
+ gimp_data_factory_data_free (gimp->gradient_factory);
+
+ if (gimp->palette_factory)
+ gimp_data_factory_data_free (gimp->palette_factory);
+
+ if (gimp->font_factory)
+ gimp_data_factory_data_free (gimp->font_factory);
+
+ if (gimp->tool_preset_factory)
+ gimp_data_factory_data_free (gimp->tool_preset_factory);
+}
+
+void
+gimp_data_factories_exit (Gimp *gimp)
+{
+ g_return_if_fail (GIMP_IS_GIMP (gimp));
+
+ g_clear_object (&gimp->brush_factory);
+ g_clear_object (&gimp->dynamics_factory);
+ g_clear_object (&gimp->mybrush_factory);
+ g_clear_object (&gimp->pattern_factory);
+ g_clear_object (&gimp->gradient_factory);
+ g_clear_object (&gimp->palette_factory);
+ g_clear_object (&gimp->font_factory);
+ g_clear_object (&gimp->tool_preset_factory);
+ g_clear_object (&gimp->tag_cache);
+}
+
+gint64
+gimp_data_factories_get_memsize (Gimp *gimp,
+ gint64 *gui_size)
+{
+ gint64 memsize = 0;
+
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), 0);
+
+ memsize += gimp_object_get_memsize (GIMP_OBJECT (gimp->named_buffers),
+ gui_size);
+ memsize += gimp_object_get_memsize (GIMP_OBJECT (gimp->brush_factory),
+ gui_size);
+ memsize += gimp_object_get_memsize (GIMP_OBJECT (gimp->dynamics_factory),
+ gui_size);
+ memsize += gimp_object_get_memsize (GIMP_OBJECT (gimp->mybrush_factory),
+ gui_size);
+ memsize += gimp_object_get_memsize (GIMP_OBJECT (gimp->pattern_factory),
+ gui_size);
+ memsize += gimp_object_get_memsize (GIMP_OBJECT (gimp->gradient_factory),
+ gui_size);
+ memsize += gimp_object_get_memsize (GIMP_OBJECT (gimp->palette_factory),
+ gui_size);
+ memsize += gimp_object_get_memsize (GIMP_OBJECT (gimp->font_factory),
+ gui_size);
+ memsize += gimp_object_get_memsize (GIMP_OBJECT (gimp->tool_preset_factory),
+ gui_size);
+
+ memsize += gimp_object_get_memsize (GIMP_OBJECT (gimp->tag_cache),
+ gui_size);
+
+ return memsize;
+}
+
+void
+gimp_data_factories_data_clean (Gimp *gimp)
+{
+ g_return_if_fail (GIMP_IS_GIMP (gimp));
+
+ gimp_data_factory_data_clean (gimp->brush_factory);
+ gimp_data_factory_data_clean (gimp->dynamics_factory);
+ gimp_data_factory_data_clean (gimp->mybrush_factory);
+ gimp_data_factory_data_clean (gimp->pattern_factory);
+ gimp_data_factory_data_clean (gimp->gradient_factory);
+ gimp_data_factory_data_clean (gimp->palette_factory);
+ gimp_data_factory_data_clean (gimp->font_factory);
+ gimp_data_factory_data_clean (gimp->tool_preset_factory);
+}
+
+void
+gimp_data_factories_load (Gimp *gimp,
+ GimpInitStatusFunc status_callback)
+{
+ g_return_if_fail (GIMP_IS_GIMP (gimp));
+
+ /* initialize the list of gimp brushes */
+ status_callback (NULL, _("Brushes"), 0.1);
+ gimp_data_factory_data_init (gimp->brush_factory, gimp->user_context,
+ gimp->no_data);
+
+ /* initialize the list of gimp dynamics */
+ status_callback (NULL, _("Dynamics"), 0.15);
+ gimp_data_factory_data_init (gimp->dynamics_factory, gimp->user_context,
+ gimp->no_data);
+
+ /* initialize the list of mypaint brushes */
+ status_callback (NULL, _("MyPaint Brushes"), 0.2);
+ gimp_data_factory_data_init (gimp->mybrush_factory, gimp->user_context,
+ gimp->no_data);
+
+ /* initialize the list of gimp patterns */
+ status_callback (NULL, _("Patterns"), 0.3);
+ gimp_data_factory_data_init (gimp->pattern_factory, gimp->user_context,
+ gimp->no_data);
+
+ /* initialize the list of gimp palettes */
+ status_callback (NULL, _("Palettes"), 0.4);
+ gimp_data_factory_data_init (gimp->palette_factory, gimp->user_context,
+ gimp->no_data);
+
+ /* initialize the list of gimp gradients */
+ status_callback (NULL, _("Gradients"), 0.5);
+ gimp_data_factory_data_init (gimp->gradient_factory, gimp->user_context,
+ gimp->no_data);
+
+ /* initialize the color history */
+ status_callback (NULL, _("Color History"), 0.55);
+ gimp_palettes_load (gimp);
+
+ /* initialize the list of gimp fonts */
+ status_callback (NULL, _("Fonts"), 0.6);
+ gimp_data_factory_data_init (gimp->font_factory, gimp->user_context,
+ gimp->no_fonts);
+
+ /* initialize the list of gimp tool presets if we have a GUI */
+ if (! gimp->no_interface)
+ {
+ status_callback (NULL, _("Tool Presets"), 0.7);
+ gimp_data_factory_data_init (gimp->tool_preset_factory, gimp->user_context,
+ gimp->no_data);
+ }
+
+ /* update tag cache */
+ status_callback (NULL, _("Updating tag cache"), 0.75);
+ gimp_tag_cache_load (gimp->tag_cache);
+ gimp_tag_cache_add_container (gimp->tag_cache,
+ gimp_data_factory_get_container (gimp->brush_factory));
+ gimp_tag_cache_add_container (gimp->tag_cache,
+ gimp_data_factory_get_container (gimp->dynamics_factory));
+ gimp_tag_cache_add_container (gimp->tag_cache,
+ gimp_data_factory_get_container (gimp->mybrush_factory));
+ gimp_tag_cache_add_container (gimp->tag_cache,
+ gimp_data_factory_get_container (gimp->pattern_factory));
+ gimp_tag_cache_add_container (gimp->tag_cache,
+ gimp_data_factory_get_container (gimp->gradient_factory));
+ gimp_tag_cache_add_container (gimp->tag_cache,
+ gimp_data_factory_get_container (gimp->palette_factory));
+ gimp_tag_cache_add_container (gimp->tag_cache,
+ gimp_data_factory_get_container (gimp->font_factory));
+ gimp_tag_cache_add_container (gimp->tag_cache,
+ gimp_data_factory_get_container (gimp->tool_preset_factory));
+}
+
+void
+gimp_data_factories_save (Gimp *gimp)
+{
+ g_return_if_fail (GIMP_IS_GIMP (gimp));
+
+ gimp_tag_cache_save (gimp->tag_cache);
+
+ gimp_data_factory_data_save (gimp->brush_factory);
+ gimp_data_factory_data_save (gimp->dynamics_factory);
+ gimp_data_factory_data_save (gimp->mybrush_factory);
+ gimp_data_factory_data_save (gimp->pattern_factory);
+ gimp_data_factory_data_save (gimp->gradient_factory);
+ gimp_data_factory_data_save (gimp->palette_factory);
+ gimp_data_factory_data_save (gimp->font_factory);
+ gimp_data_factory_data_save (gimp->tool_preset_factory);
+
+ gimp_palettes_save (gimp);
+}
diff --git a/app/core/gimp-data-factories.h b/app/core/gimp-data-factories.h
new file mode 100644
index 0000000..d5b273d
--- /dev/null
+++ b/app/core/gimp-data-factories.h
@@ -0,0 +1,36 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-2002 Spencer Kimball, Peter Mattis, and others
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_DATA_FACTORIES_H__
+#define __GIMP_DATA_FACTORIES_H__
+
+
+void gimp_data_factories_init (Gimp *gimp);
+void gimp_data_factories_add_builtin (Gimp *gimp);
+void gimp_data_factories_clear (Gimp *gimp);
+void gimp_data_factories_exit (Gimp *gimp);
+
+gint64 gimp_data_factories_get_memsize (Gimp *gimp,
+ gint64 *gui_size);
+void gimp_data_factories_data_clean (Gimp *gimp);
+
+void gimp_data_factories_load (Gimp *gimp,
+ GimpInitStatusFunc status_callback);
+void gimp_data_factories_save (Gimp *gimp);
+
+
+#endif /* __GIMP_DATA_FACTORIES_H__ */
diff --git a/app/core/gimp-edit.c b/app/core/gimp-edit.c
new file mode 100644
index 0000000..372ad2d
--- /dev/null
+++ b/app/core/gimp-edit.c
@@ -0,0 +1,771 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <cairo.h>
+#include <gegl.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+#include "libgimpbase/gimpbase.h"
+#include "libgimpcolor/gimpcolor.h"
+
+#include "core-types.h"
+
+#include "gimp.h"
+#include "gimp-edit.h"
+#include "gimpbuffer.h"
+#include "gimpcontext.h"
+#include "gimpgrouplayer.h"
+#include "gimpimage.h"
+#include "gimpimage-duplicate.h"
+#include "gimpimage-new.h"
+#include "gimpimage-undo.h"
+#include "gimplayer-floating-selection.h"
+#include "gimplayer-new.h"
+#include "gimplist.h"
+#include "gimppickable.h"
+#include "gimpselection.h"
+
+#include "gimp-intl.h"
+
+
+/* local function protypes */
+
+static GimpBuffer * gimp_edit_extract (GimpImage *image,
+ GimpPickable *pickable,
+ GimpContext *context,
+ gboolean cut_pixels,
+ GError **error);
+
+
+/* public functions */
+
+GimpObject *
+gimp_edit_cut (GimpImage *image,
+ GimpDrawable *drawable,
+ GimpContext *context,
+ GError **error)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), NULL);
+ g_return_val_if_fail (gimp_item_is_attached (GIMP_ITEM (drawable)), NULL);
+ g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ if (GIMP_IS_LAYER (drawable) &&
+ gimp_channel_is_empty (gimp_image_get_mask (image)))
+ {
+ GimpImage *clip_image;
+ gint off_x, off_y;
+
+ gimp_item_get_offset (GIMP_ITEM (drawable), &off_x, &off_y);
+
+ clip_image = gimp_image_new_from_drawable (image->gimp, drawable);
+ g_object_set_data (G_OBJECT (clip_image), "offset-x",
+ GINT_TO_POINTER (off_x));
+ g_object_set_data (G_OBJECT (clip_image), "offset-y",
+ GINT_TO_POINTER (off_y));
+ gimp_container_remove (image->gimp->images, GIMP_OBJECT (clip_image));
+ gimp_set_clipboard_image (image->gimp, clip_image);
+ g_object_unref (clip_image);
+
+ gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_EDIT_CUT,
+ C_("undo-type", "Cut Layer"));
+
+ gimp_image_remove_layer (image, GIMP_LAYER (drawable),
+ TRUE, NULL);
+
+ gimp_image_undo_group_end (image);
+
+ return GIMP_OBJECT (gimp_get_clipboard_image (image->gimp));
+ }
+ else
+ {
+ GimpBuffer *buffer;
+
+ buffer = gimp_edit_extract (image, GIMP_PICKABLE (drawable),
+ context, TRUE, error);
+
+ if (buffer)
+ {
+ gimp_set_clipboard_buffer (image->gimp, buffer);
+ g_object_unref (buffer);
+
+ return GIMP_OBJECT (gimp_get_clipboard_buffer (image->gimp));
+ }
+ }
+
+ return NULL;
+}
+
+GimpObject *
+gimp_edit_copy (GimpImage *image,
+ GimpDrawable *drawable,
+ GimpContext *context,
+ GError **error)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), NULL);
+ g_return_val_if_fail (gimp_item_is_attached (GIMP_ITEM (drawable)), NULL);
+ g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ if (GIMP_IS_LAYER (drawable) &&
+ gimp_channel_is_empty (gimp_image_get_mask (image)))
+ {
+ GimpImage *clip_image;
+ gint off_x, off_y;
+
+ gimp_item_get_offset (GIMP_ITEM (drawable), &off_x, &off_y);
+
+ clip_image = gimp_image_new_from_drawable (image->gimp, drawable);
+ g_object_set_data (G_OBJECT (clip_image), "offset-x",
+ GINT_TO_POINTER (off_x));
+ g_object_set_data (G_OBJECT (clip_image), "offset-y",
+ GINT_TO_POINTER (off_y));
+ gimp_container_remove (image->gimp->images, GIMP_OBJECT (clip_image));
+ gimp_set_clipboard_image (image->gimp, clip_image);
+ g_object_unref (clip_image);
+
+ return GIMP_OBJECT (gimp_get_clipboard_image (image->gimp));
+ }
+ else
+ {
+ GimpBuffer *buffer;
+
+ buffer = gimp_edit_extract (image, GIMP_PICKABLE (drawable),
+ context, FALSE, error);
+
+ if (buffer)
+ {
+ gimp_set_clipboard_buffer (image->gimp, buffer);
+ g_object_unref (buffer);
+
+ return GIMP_OBJECT (gimp_get_clipboard_buffer (image->gimp));
+ }
+ }
+
+ return NULL;
+}
+
+GimpBuffer *
+gimp_edit_copy_visible (GimpImage *image,
+ GimpContext *context,
+ GError **error)
+{
+ GimpBuffer *buffer;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+ g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ buffer = gimp_edit_extract (image, GIMP_PICKABLE (image),
+ context, FALSE, error);
+
+ if (buffer)
+ {
+ gimp_set_clipboard_buffer (image->gimp, buffer);
+ g_object_unref (buffer);
+
+ return gimp_get_clipboard_buffer (image->gimp);
+ }
+
+ return NULL;
+}
+
+static gboolean
+gimp_edit_paste_is_in_place (GimpPasteType paste_type)
+{
+ switch (paste_type)
+ {
+ case GIMP_PASTE_TYPE_FLOATING:
+ case GIMP_PASTE_TYPE_FLOATING_INTO:
+ case GIMP_PASTE_TYPE_NEW_LAYER:
+ return FALSE;
+
+ case GIMP_PASTE_TYPE_FLOATING_IN_PLACE:
+ case GIMP_PASTE_TYPE_FLOATING_INTO_IN_PLACE:
+ case GIMP_PASTE_TYPE_NEW_LAYER_IN_PLACE:
+ return TRUE;
+ }
+
+ g_return_val_if_reached (FALSE);
+}
+
+static gboolean
+gimp_edit_paste_is_floating (GimpPasteType paste_type)
+{
+ switch (paste_type)
+ {
+ case GIMP_PASTE_TYPE_FLOATING:
+ case GIMP_PASTE_TYPE_FLOATING_INTO:
+ case GIMP_PASTE_TYPE_FLOATING_IN_PLACE:
+ case GIMP_PASTE_TYPE_FLOATING_INTO_IN_PLACE:
+ return TRUE;
+
+ case GIMP_PASTE_TYPE_NEW_LAYER:
+ case GIMP_PASTE_TYPE_NEW_LAYER_IN_PLACE:
+ return FALSE;
+ }
+
+ g_return_val_if_reached (FALSE);
+}
+
+static GimpLayer *
+gimp_edit_paste_get_layer (GimpImage *image,
+ GimpDrawable *drawable,
+ GimpObject *paste,
+ GimpPasteType *paste_type)
+{
+ GimpLayer *layer = NULL;
+ const Babl *floating_format;
+
+ /* change paste type to NEW_LAYER for cases where we can't attach a
+ * floating selection
+ */
+ if (! drawable ||
+ gimp_viewable_get_children (GIMP_VIEWABLE (drawable)) ||
+ gimp_item_is_content_locked (GIMP_ITEM (drawable)))
+ {
+ if (gimp_edit_paste_is_in_place (*paste_type))
+ *paste_type = GIMP_PASTE_TYPE_NEW_LAYER_IN_PLACE;
+ else
+ *paste_type = GIMP_PASTE_TYPE_NEW_LAYER;
+ }
+
+ /* floating pastes always have the pasted-to drawable's format with
+ * alpha; if drawable == NULL, user is pasting into an empty image
+ */
+ if (drawable && gimp_edit_paste_is_floating (*paste_type))
+ floating_format = gimp_drawable_get_format_with_alpha (drawable);
+ else
+ floating_format = gimp_image_get_layer_format (image, TRUE);
+
+ if (GIMP_IS_IMAGE (paste))
+ {
+ GType layer_type;
+
+ layer = gimp_image_get_layer_iter (GIMP_IMAGE (paste))->data;
+
+ switch (*paste_type)
+ {
+ case GIMP_PASTE_TYPE_FLOATING:
+ case GIMP_PASTE_TYPE_FLOATING_IN_PLACE:
+ case GIMP_PASTE_TYPE_FLOATING_INTO:
+ case GIMP_PASTE_TYPE_FLOATING_INTO_IN_PLACE:
+ /* when pasting as floating make sure gimp_item_convert()
+ * will turn group layers into normal layers, otherwise use
+ * the same layer type so e.g. text information gets
+ * preserved. See issue #2667.
+ */
+ if (GIMP_IS_GROUP_LAYER (layer))
+ layer_type = GIMP_TYPE_LAYER;
+ else
+ layer_type = G_TYPE_FROM_INSTANCE (layer);
+ break;
+
+ case GIMP_PASTE_TYPE_NEW_LAYER:
+ case GIMP_PASTE_TYPE_NEW_LAYER_IN_PLACE:
+ layer_type = G_TYPE_FROM_INSTANCE (layer);
+ break;
+
+ default:
+ g_return_val_if_reached (NULL);
+ }
+
+ layer = GIMP_LAYER (gimp_item_convert (GIMP_ITEM (layer),
+ image, layer_type));
+
+ switch (*paste_type)
+ {
+ case GIMP_PASTE_TYPE_FLOATING:
+ case GIMP_PASTE_TYPE_FLOATING_IN_PLACE:
+ case GIMP_PASTE_TYPE_FLOATING_INTO:
+ case GIMP_PASTE_TYPE_FLOATING_INTO_IN_PLACE:
+ /* when pasting as floating selection, get rid of the layer mask,
+ * and make sure the layer has the right format
+ */
+ if (gimp_layer_get_mask (layer))
+ gimp_layer_apply_mask (layer, GIMP_MASK_DISCARD, FALSE);
+
+ if (gimp_drawable_get_format (GIMP_DRAWABLE (layer)) !=
+ floating_format)
+ {
+ gimp_drawable_convert_type (GIMP_DRAWABLE (layer), image,
+ gimp_drawable_get_base_type (drawable),
+ gimp_drawable_get_precision (drawable),
+ TRUE,
+ NULL,
+ GEGL_DITHER_NONE, GEGL_DITHER_NONE,
+ FALSE, NULL);
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+ else if (GIMP_IS_BUFFER (paste))
+ {
+ layer = gimp_layer_new_from_buffer (GIMP_BUFFER (paste), image,
+ floating_format,
+ _("Pasted Layer"),
+ GIMP_OPACITY_OPAQUE,
+ gimp_image_get_default_new_layer_mode (image));
+ }
+
+ return layer;
+}
+
+static void
+gimp_edit_paste_get_viewport_offset (GimpImage *image,
+ GimpDrawable *drawable,
+ GimpObject *paste,
+ gint viewport_x,
+ gint viewport_y,
+ gint viewport_width,
+ gint viewport_height,
+ gint *offset_x,
+ gint *offset_y)
+{
+ gint image_width;
+ gint image_height;
+ gint width;
+ gint height;
+ gboolean clamp_to_image = TRUE;
+
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+ g_return_if_fail (drawable == NULL || GIMP_IS_DRAWABLE (drawable));
+ g_return_if_fail (drawable == NULL ||
+ gimp_item_is_attached (GIMP_ITEM (drawable)));
+ g_return_if_fail (GIMP_IS_VIEWABLE (paste));
+ g_return_if_fail (offset_x != NULL);
+ g_return_if_fail (offset_y != NULL);
+
+ image_width = gimp_image_get_width (image);
+ image_height = gimp_image_get_height (image);
+
+ gimp_viewable_get_size (GIMP_VIEWABLE (paste), &width, &height);
+
+ if (viewport_width == image_width &&
+ viewport_height == image_height)
+ {
+ /* if the whole image is visible, act as if there was no viewport */
+
+ viewport_x = 0;
+ viewport_y = 0;
+ viewport_width = 0;
+ viewport_height = 0;
+ }
+
+ if (drawable)
+ {
+ /* if pasting to a drawable */
+
+ GimpContainer *children;
+ gint off_x, off_y;
+ gint target_x, target_y;
+ gint target_width, target_height;
+ gint paste_x, paste_y;
+ gint paste_width, paste_height;
+ gboolean have_mask;
+
+ have_mask = ! gimp_channel_is_empty (gimp_image_get_mask (image));
+
+ gimp_item_get_offset (GIMP_ITEM (drawable), &off_x, &off_y);
+
+ children = gimp_viewable_get_children (GIMP_VIEWABLE (drawable));
+
+ if (children && gimp_container_get_n_children (children) == 0)
+ {
+ /* treat empty layer groups as image-sized, use the selection
+ * as target
+ */
+ gimp_item_bounds (GIMP_ITEM (gimp_image_get_mask (image)),
+ &target_x, &target_y,
+ &target_width, &target_height);
+ }
+ else
+ {
+ gimp_item_mask_intersect (GIMP_ITEM (drawable),
+ &target_x, &target_y,
+ &target_width, &target_height);
+ }
+
+ if (! have_mask && /* if we have no mask */
+ viewport_width > 0 && /* and we have a viewport */
+ viewport_height > 0 &&
+ (width < target_width || /* and the paste is smaller than the target */
+ height < target_height) &&
+
+ /* and the viewport intersects with the target */
+ gimp_rectangle_intersect (viewport_x, viewport_y,
+ viewport_width, viewport_height,
+ off_x, off_y, /* target_x,y are 0 */
+ target_width, target_height,
+ &paste_x, &paste_y,
+ &paste_width, &paste_height))
+ {
+ /* center on the viewport */
+
+ *offset_x = paste_x + (paste_width - width) / 2;
+ *offset_y = paste_y + (paste_height- height) / 2;
+ }
+ else
+ {
+ /* otherwise center on the target */
+
+ *offset_x = off_x + target_x + (target_width - width) / 2;
+ *offset_y = off_y + target_y + (target_height - height) / 2;
+
+ /* and keep it that way */
+ clamp_to_image = FALSE;
+ }
+ }
+ else if (viewport_width > 0 && /* if we have a viewport */
+ viewport_height > 0 &&
+ (width < image_width || /* and the paste is */
+ height < image_height)) /* smaller than the image */
+ {
+ /* center on the viewport */
+
+ *offset_x = viewport_x + (viewport_width - width) / 2;
+ *offset_y = viewport_y + (viewport_height - height) / 2;
+ }
+ else
+ {
+ /* otherwise center on the image */
+
+ *offset_x = (image_width - width) / 2;
+ *offset_y = (image_height - height) / 2;
+
+ /* and keep it that way */
+ clamp_to_image = FALSE;
+ }
+
+ if (clamp_to_image)
+ {
+ /* Ensure that the pasted layer is always within the image, if it
+ * fits and aligned at top left if it doesn't. (See bug #142944).
+ */
+ *offset_x = MIN (*offset_x, image_width - width);
+ *offset_y = MIN (*offset_y, image_height - height);
+ *offset_x = MAX (*offset_x, 0);
+ *offset_y = MAX (*offset_y, 0);
+ }
+}
+
+static void
+gimp_edit_paste_get_paste_offset (GimpImage *image,
+ GimpDrawable *drawable,
+ GimpObject *paste,
+ gint *offset_x,
+ gint *offset_y)
+{
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+ g_return_if_fail (drawable == NULL || GIMP_IS_DRAWABLE (drawable));
+ g_return_if_fail (drawable == NULL ||
+ gimp_item_is_attached (GIMP_ITEM (drawable)));
+ g_return_if_fail (GIMP_IS_VIEWABLE (paste));
+ g_return_if_fail (offset_x != NULL);
+ g_return_if_fail (offset_y != NULL);
+
+ if (GIMP_IS_IMAGE (paste))
+ {
+ *offset_x = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (paste),
+ "offset-x"));
+ *offset_y = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (paste),
+ "offset-y"));
+ }
+ else if (GIMP_IS_BUFFER (paste))
+ {
+ GimpBuffer *buffer = GIMP_BUFFER (paste);
+
+ *offset_x = buffer->offset_x;
+ *offset_y = buffer->offset_y;
+ }
+}
+
+static GimpLayer *
+gimp_edit_paste_paste (GimpImage *image,
+ GimpDrawable *drawable,
+ GimpLayer *layer,
+ GimpPasteType paste_type,
+ gint offset_x,
+ gint offset_y)
+{
+ gimp_item_translate (GIMP_ITEM (layer), offset_x, offset_y, FALSE);
+
+ gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_EDIT_PASTE,
+ C_("undo-type", "Paste"));
+
+ switch (paste_type)
+ {
+ case GIMP_PASTE_TYPE_FLOATING:
+ case GIMP_PASTE_TYPE_FLOATING_IN_PLACE:
+ /* if there is a selection mask clear it - this might not
+ * always be desired, but in general, it seems like the correct
+ * behavior
+ */
+ if (! gimp_channel_is_empty (gimp_image_get_mask (image)))
+ gimp_channel_clear (gimp_image_get_mask (image), NULL, TRUE);
+
+ /* fall thru */
+
+ case GIMP_PASTE_TYPE_FLOATING_INTO:
+ case GIMP_PASTE_TYPE_FLOATING_INTO_IN_PLACE:
+ floating_sel_attach (layer, drawable);
+ break;
+
+ case GIMP_PASTE_TYPE_NEW_LAYER:
+ case GIMP_PASTE_TYPE_NEW_LAYER_IN_PLACE:
+ {
+ GimpLayer *parent = NULL;
+ gint position = 0;
+
+ /* always add on top of a passed layer, where we would attach
+ * a floating selection
+ */
+ if (GIMP_IS_LAYER (drawable))
+ {
+ parent = gimp_layer_get_parent (GIMP_LAYER (drawable));
+ position = gimp_item_get_index (GIMP_ITEM (drawable));
+ }
+
+ gimp_image_add_layer (image, layer, parent, position, TRUE);
+ }
+ break;
+ }
+
+ gimp_image_undo_group_end (image);
+
+ return layer;
+}
+
+GimpLayer *
+gimp_edit_paste (GimpImage *image,
+ GimpDrawable *drawable,
+ GimpObject *paste,
+ GimpPasteType paste_type,
+ gint viewport_x,
+ gint viewport_y,
+ gint viewport_width,
+ gint viewport_height)
+{
+ GimpLayer *layer;
+ gint offset_x = 0;
+ gint offset_y = 0;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+ g_return_val_if_fail (drawable == NULL || GIMP_IS_DRAWABLE (drawable), NULL);
+ g_return_val_if_fail (drawable == NULL ||
+ gimp_item_is_attached (GIMP_ITEM (drawable)), NULL);
+ g_return_val_if_fail (GIMP_IS_IMAGE (paste) || GIMP_IS_BUFFER (paste), NULL);
+
+ layer = gimp_edit_paste_get_layer (image, drawable, paste, &paste_type);
+
+ if (! layer)
+ return NULL;
+
+ if (gimp_edit_paste_is_in_place (paste_type))
+ {
+ gimp_edit_paste_get_paste_offset (image, drawable, paste,
+ &offset_x,
+ &offset_y);
+ }
+ else
+ {
+ gimp_edit_paste_get_viewport_offset (image, drawable, GIMP_OBJECT (layer),
+ viewport_x,
+ viewport_y,
+ viewport_width,
+ viewport_height,
+ &offset_x,
+ &offset_y);
+ }
+
+ return gimp_edit_paste_paste (image, drawable, layer, paste_type,
+ offset_x, offset_y);
+}
+
+GimpImage *
+gimp_edit_paste_as_new_image (Gimp *gimp,
+ GimpObject *paste)
+{
+ GimpImage *image = NULL;
+
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
+ g_return_val_if_fail (GIMP_IS_IMAGE (paste) || GIMP_IS_BUFFER (paste), NULL);
+
+ if (GIMP_IS_IMAGE (paste))
+ {
+ image = gimp_image_duplicate (GIMP_IMAGE (paste));
+ }
+ else if (GIMP_IS_BUFFER (paste))
+ {
+ image = gimp_image_new_from_buffer (gimp, GIMP_BUFFER (paste));
+ }
+
+ return image;
+}
+
+const gchar *
+gimp_edit_named_cut (GimpImage *image,
+ const gchar *name,
+ GimpDrawable *drawable,
+ GimpContext *context,
+ GError **error)
+{
+ GimpBuffer *buffer;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+ g_return_val_if_fail (name != NULL, NULL);
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), NULL);
+ g_return_val_if_fail (gimp_item_is_attached (GIMP_ITEM (drawable)), NULL);
+ g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ buffer = gimp_edit_extract (image, GIMP_PICKABLE (drawable),
+ context, TRUE, error);
+
+ if (buffer)
+ {
+ gimp_object_set_name (GIMP_OBJECT (buffer), name);
+ gimp_container_add (image->gimp->named_buffers, GIMP_OBJECT (buffer));
+ g_object_unref (buffer);
+
+ return gimp_object_get_name (buffer);
+ }
+
+ return NULL;
+}
+
+const gchar *
+gimp_edit_named_copy (GimpImage *image,
+ const gchar *name,
+ GimpDrawable *drawable,
+ GimpContext *context,
+ GError **error)
+{
+ GimpBuffer *buffer;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+ g_return_val_if_fail (name != NULL, NULL);
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), NULL);
+ g_return_val_if_fail (gimp_item_is_attached (GIMP_ITEM (drawable)), NULL);
+ g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ buffer = gimp_edit_extract (image, GIMP_PICKABLE (drawable),
+ context, FALSE, error);
+
+ if (buffer)
+ {
+ gimp_object_set_name (GIMP_OBJECT (buffer), name);
+ gimp_container_add (image->gimp->named_buffers, GIMP_OBJECT (buffer));
+ g_object_unref (buffer);
+
+ return gimp_object_get_name (buffer);
+ }
+
+ return NULL;
+}
+
+const gchar *
+gimp_edit_named_copy_visible (GimpImage *image,
+ const gchar *name,
+ GimpContext *context,
+ GError **error)
+{
+ GimpBuffer *buffer;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+ g_return_val_if_fail (name != NULL, NULL);
+ g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ buffer = gimp_edit_extract (image, GIMP_PICKABLE (image),
+ context, FALSE, error);
+
+ if (buffer)
+ {
+ gimp_object_set_name (GIMP_OBJECT (buffer), name);
+ gimp_container_add (image->gimp->named_buffers, GIMP_OBJECT (buffer));
+ g_object_unref (buffer);
+
+ return gimp_object_get_name (buffer);
+ }
+
+ return NULL;
+}
+
+
+/* private functions */
+
+static GimpBuffer *
+gimp_edit_extract (GimpImage *image,
+ GimpPickable *pickable,
+ GimpContext *context,
+ gboolean cut_pixels,
+ GError **error)
+{
+ GeglBuffer *buffer;
+ gint offset_x;
+ gint offset_y;
+
+ if (cut_pixels)
+ gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_EDIT_CUT,
+ C_("undo-type", "Cut"));
+
+ /* Cut/copy the mask portion from the image */
+ buffer = gimp_selection_extract (GIMP_SELECTION (gimp_image_get_mask (image)),
+ pickable, context,
+ cut_pixels, FALSE, FALSE,
+ &offset_x, &offset_y, error);
+
+ if (cut_pixels)
+ gimp_image_undo_group_end (image);
+
+ if (buffer)
+ {
+ GimpBuffer *gimp_buffer;
+ gdouble res_x;
+ gdouble res_y;
+
+ gimp_buffer = gimp_buffer_new (buffer, _("Global Buffer"),
+ offset_x, offset_y, FALSE);
+ g_object_unref (buffer);
+
+ gimp_image_get_resolution (image, &res_x, &res_y);
+ gimp_buffer_set_resolution (gimp_buffer, res_x, res_y);
+ gimp_buffer_set_unit (gimp_buffer, gimp_image_get_unit (image));
+
+ if (GIMP_IS_COLOR_MANAGED (pickable))
+ {
+ GimpColorProfile *profile =
+ gimp_color_managed_get_color_profile (GIMP_COLOR_MANAGED (pickable));
+
+ if (profile)
+ gimp_buffer_set_color_profile (gimp_buffer, profile);
+ }
+
+ return gimp_buffer;
+ }
+
+ return NULL;
+}
diff --git a/app/core/gimp-edit.h b/app/core/gimp-edit.h
new file mode 100644
index 0000000..c3d8ca5
--- /dev/null
+++ b/app/core/gimp-edit.h
@@ -0,0 +1,61 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_EDIT_H__
+#define __GIMP_EDIT_H__
+
+
+GimpObject * gimp_edit_cut (GimpImage *image,
+ GimpDrawable *drawable,
+ GimpContext *context,
+ GError **error);
+GimpObject * gimp_edit_copy (GimpImage *image,
+ GimpDrawable *drawable,
+ GimpContext *context,
+ GError **error);
+GimpBuffer * gimp_edit_copy_visible (GimpImage *image,
+ GimpContext *context,
+ GError **error);
+
+GimpLayer * gimp_edit_paste (GimpImage *image,
+ GimpDrawable *drawable,
+ GimpObject *paste,
+ GimpPasteType paste_type,
+ gint viewport_x,
+ gint viewport_y,
+ gint viewport_width,
+ gint viewport_height);
+GimpImage * gimp_edit_paste_as_new_image (Gimp *gimp,
+ GimpObject *paste);
+
+const gchar * gimp_edit_named_cut (GimpImage *image,
+ const gchar *name,
+ GimpDrawable *drawable,
+ GimpContext *context,
+ GError **error);
+const gchar * gimp_edit_named_copy (GimpImage *image,
+ const gchar *name,
+ GimpDrawable *drawable,
+ GimpContext *context,
+ GError **error);
+const gchar * gimp_edit_named_copy_visible (GimpImage *image,
+ const gchar *name,
+ GimpContext *context,
+ GError **error);
+
+
+#endif /* __GIMP_EDIT_H__ */
diff --git a/app/core/gimp-filter-history.c b/app/core/gimp-filter-history.c
new file mode 100644
index 0000000..afdd6ed
--- /dev/null
+++ b/app/core/gimp-filter-history.c
@@ -0,0 +1,160 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-2003 Spencer Kimball and Peter Mattis
+ *
+ * gimp-filter-history.c
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpbase/gimpbase.h"
+
+#include "core-types.h"
+
+#include "config/gimpcoreconfig.h"
+
+#include "gimp.h"
+#include "gimp-filter-history.h"
+
+#include "pdb/gimpprocedure.h"
+
+
+/* local function prototypes */
+
+static gint gimp_filter_history_compare (GimpProcedure *proc1,
+ GimpProcedure *proc2);
+
+
+/* public functions */
+
+gint
+gimp_filter_history_size (Gimp *gimp)
+{
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), 0);
+
+ return MAX (1, gimp->config->filter_history_size);
+}
+
+gint
+gimp_filter_history_length (Gimp *gimp)
+{
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), 0);
+
+ return g_list_length (gimp->filter_history);
+}
+
+GimpProcedure *
+gimp_filter_history_nth (Gimp *gimp,
+ gint n)
+{
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
+
+ return g_list_nth_data (gimp->filter_history, n);
+}
+
+void
+gimp_filter_history_add (Gimp *gimp,
+ GimpProcedure *procedure)
+{
+ GList *link;
+
+ g_return_if_fail (GIMP_IS_GIMP (gimp));
+ g_return_if_fail (GIMP_IS_PROCEDURE (procedure));
+
+ /* return early if the procedure is already at the top */
+ if (gimp->filter_history &&
+ gimp_filter_history_compare (gimp->filter_history->data, procedure) == 0)
+ return;
+
+ /* ref new first then unref old, they might be the same */
+ g_object_ref (procedure);
+
+ link = g_list_find_custom (gimp->filter_history, procedure,
+ (GCompareFunc) gimp_filter_history_compare);
+
+ if (link)
+ {
+ g_object_unref (link->data);
+ gimp->filter_history = g_list_delete_link (gimp->filter_history, link);
+ }
+
+ gimp->filter_history = g_list_prepend (gimp->filter_history, procedure);
+
+ link = g_list_nth (gimp->filter_history, gimp_filter_history_size (gimp));
+
+ if (link)
+ {
+ g_object_unref (link->data);
+ gimp->filter_history = g_list_delete_link (gimp->filter_history, link);
+ }
+
+ gimp_filter_history_changed (gimp);
+}
+
+void
+gimp_filter_history_remove (Gimp *gimp,
+ GimpProcedure *procedure)
+{
+ GList *link;
+
+ g_return_if_fail (GIMP_IS_GIMP (gimp));
+ g_return_if_fail (GIMP_IS_PROCEDURE (procedure));
+
+ link = g_list_find_custom (gimp->filter_history, procedure,
+ (GCompareFunc) gimp_filter_history_compare);
+
+ if (link)
+ {
+ g_object_unref (link->data);
+ gimp->filter_history = g_list_delete_link (gimp->filter_history, link);
+
+ gimp_filter_history_changed (gimp);
+ }
+}
+
+void
+gimp_filter_history_clear (Gimp *gimp)
+{
+ g_return_if_fail (GIMP_IS_GIMP (gimp));
+
+ if (gimp->filter_history)
+ {
+ g_list_free_full (gimp->filter_history, (GDestroyNotify) g_object_unref);
+ gimp->filter_history = NULL;
+
+ gimp_filter_history_changed (gimp);
+ }
+}
+
+
+/* private functions */
+
+static gint
+gimp_filter_history_compare (GimpProcedure *proc1,
+ GimpProcedure *proc2)
+{
+ /* the procedures can have the same name, but could still be two
+ * different filters using the same operation, so also compare
+ * their menu labels
+ */
+ return (gimp_procedure_name_compare (proc1, proc2) ||
+ strcmp (gimp_procedure_get_menu_label (proc1),
+ gimp_procedure_get_menu_label (proc2)));
+}
diff --git a/app/core/gimp-filter-history.h b/app/core/gimp-filter-history.h
new file mode 100644
index 0000000..e74b820
--- /dev/null
+++ b/app/core/gimp-filter-history.h
@@ -0,0 +1,35 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimp-filter-history.h
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_FILTER_HISTORY_H__
+#define __GIMP_FILTER_HISTORY_H__
+
+
+gint gimp_filter_history_size (Gimp *gimp);
+gint gimp_filter_history_length (Gimp *gimp);
+GimpProcedure * gimp_filter_history_nth (Gimp *gimp,
+ gint n);
+void gimp_filter_history_add (Gimp *gimp,
+ GimpProcedure *procedure);
+void gimp_filter_history_remove (Gimp *gimp,
+ GimpProcedure *procedure);
+void gimp_filter_history_clear (Gimp *gimp);
+
+
+#endif /* __GIMP_FILTER_HISTORY_H__ */
diff --git a/app/core/gimp-gradients.c b/app/core/gimp-gradients.c
new file mode 100644
index 0000000..2c4ec0f
--- /dev/null
+++ b/app/core/gimp-gradients.c
@@ -0,0 +1,174 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-2002 Spencer Kimball, Peter Mattis, and others
+ *
+ * gimp-gradients.c
+ * Copyright (C) 2002 Michael Natterer <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "core-types.h"
+
+#include "gimp.h"
+#include "gimp-gradients.h"
+#include "gimpcontext.h"
+#include "gimpcontainer.h"
+#include "gimpdatafactory.h"
+#include "gimpgradient.h"
+
+#include "gimp-intl.h"
+
+
+#define CUSTOM_KEY "gimp-gradient-custom"
+#define FG_BG_RGB_KEY "gimp-gradient-fg-bg-rgb"
+#define FG_BG_HARDEDGE_KEY "gimp-gradient-fg-bg-rgb-hardedge"
+#define FG_BG_HSV_CCW_KEY "gimp-gradient-fg-bg-hsv-ccw"
+#define FG_BG_HSV_CW_KEY "gimp-gradient-fg-bg-hsv-cw"
+#define FG_TRANSPARENT_KEY "gimp-gradient-fg-transparent"
+
+
+/* local function prototypes */
+
+static GimpGradient * gimp_gradients_add_gradient (Gimp *gimp,
+ const gchar *name,
+ const gchar *id);
+
+
+/* public functions */
+
+void
+gimp_gradients_init (Gimp *gimp)
+{
+ GimpGradient *gradient;
+
+ g_return_if_fail (GIMP_IS_GIMP (gimp));
+
+ /* Custom */
+ gradient = gimp_gradients_add_gradient (gimp,
+ _("Custom"),
+ CUSTOM_KEY);
+ g_object_set (gradient,
+ "writable", TRUE,
+ NULL);
+ gradient->segments->left_color_type = GIMP_GRADIENT_COLOR_FOREGROUND;
+ gradient->segments->right_color_type = GIMP_GRADIENT_COLOR_BACKGROUND;
+
+ /* FG to BG (RGB) */
+ gradient = gimp_gradients_add_gradient (gimp,
+ _("FG to BG (RGB)"),
+ FG_BG_RGB_KEY);
+ gradient->segments->left_color_type = GIMP_GRADIENT_COLOR_FOREGROUND;
+ gradient->segments->right_color_type = GIMP_GRADIENT_COLOR_BACKGROUND;
+ gimp_context_set_gradient (gimp->user_context, gradient);
+
+ /* FG to BG (Hardedge) */
+ gradient = gimp_gradients_add_gradient (gimp,
+ _("FG to BG (Hardedge)"),
+ FG_BG_HARDEDGE_KEY);
+ gradient->segments->left_color_type = GIMP_GRADIENT_COLOR_FOREGROUND;
+ gradient->segments->right_color_type = GIMP_GRADIENT_COLOR_BACKGROUND;
+ gradient->segments->type = GIMP_GRADIENT_SEGMENT_STEP;
+
+ /* FG to BG (HSV counter-clockwise) */
+ gradient = gimp_gradients_add_gradient (gimp,
+ _("FG to BG (HSV counter-clockwise)"),
+ FG_BG_HSV_CCW_KEY);
+ gradient->segments->left_color_type = GIMP_GRADIENT_COLOR_FOREGROUND;
+ gradient->segments->right_color_type = GIMP_GRADIENT_COLOR_BACKGROUND;
+ gradient->segments->color = GIMP_GRADIENT_SEGMENT_HSV_CCW;
+
+ /* FG to BG (HSV clockwise hue) */
+ gradient = gimp_gradients_add_gradient (gimp,
+ _("FG to BG (HSV clockwise hue)"),
+ FG_BG_HSV_CW_KEY);
+ gradient->segments->left_color_type = GIMP_GRADIENT_COLOR_FOREGROUND;
+ gradient->segments->right_color_type = GIMP_GRADIENT_COLOR_BACKGROUND;
+ gradient->segments->color = GIMP_GRADIENT_SEGMENT_HSV_CW;
+
+ /* FG to Transparent */
+ gradient = gimp_gradients_add_gradient (gimp,
+ _("FG to Transparent"),
+ FG_TRANSPARENT_KEY);
+ gradient->segments->left_color_type = GIMP_GRADIENT_COLOR_FOREGROUND;
+ gradient->segments->right_color_type = GIMP_GRADIENT_COLOR_FOREGROUND_TRANSPARENT;
+}
+
+GimpGradient *
+gimp_gradients_get_custom (Gimp *gimp)
+{
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
+
+ return g_object_get_data (G_OBJECT (gimp), CUSTOM_KEY);
+}
+
+GimpGradient *
+gimp_gradients_get_fg_bg_rgb (Gimp *gimp)
+{
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
+
+ return g_object_get_data (G_OBJECT (gimp), FG_BG_RGB_KEY);
+}
+
+GimpGradient *
+gimp_gradients_get_fg_bg_hsv_ccw (Gimp *gimp)
+{
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
+
+ return g_object_get_data (G_OBJECT (gimp), FG_BG_HSV_CCW_KEY);
+}
+
+GimpGradient *
+gimp_gradients_get_fg_bg_hsv_cw (Gimp *gimp)
+{
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
+
+ return g_object_get_data (G_OBJECT (gimp), FG_BG_HSV_CW_KEY);
+}
+
+GimpGradient *
+gimp_gradients_get_fg_transparent (Gimp *gimp)
+{
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
+
+ return g_object_get_data (G_OBJECT (gimp), FG_TRANSPARENT_KEY);
+}
+
+
+/* private functions */
+
+static GimpGradient *
+gimp_gradients_add_gradient (Gimp *gimp,
+ const gchar *name,
+ const gchar *id)
+{
+ GimpGradient *gradient;
+
+ gradient = GIMP_GRADIENT (gimp_gradient_new (gimp_get_user_context (gimp),
+ name));
+
+ gimp_data_make_internal (GIMP_DATA (gradient), id);
+
+ gimp_container_add (gimp_data_factory_get_container (gimp->gradient_factory),
+ GIMP_OBJECT (gradient));
+ g_object_unref (gradient);
+
+ g_object_set_data (G_OBJECT (gimp), id, gradient);
+
+ return gradient;
+}
diff --git a/app/core/gimp-gradients.h b/app/core/gimp-gradients.h
new file mode 100644
index 0000000..72f21a6
--- /dev/null
+++ b/app/core/gimp-gradients.h
@@ -0,0 +1,34 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-2002 Spencer Kimball, Peter Mattis, and others
+ *
+ * gimp-gradients.h
+ * Copyright (C) 2002 Michael Natterer <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_GRADIENTS__
+#define __GIMP_GRADIENTS__
+
+
+void gimp_gradients_init (Gimp *gimp);
+
+GimpGradient * gimp_gradients_get_custom (Gimp *gimp);
+GimpGradient * gimp_gradients_get_fg_bg_rgb (Gimp *gimp);
+GimpGradient * gimp_gradients_get_fg_bg_hsv_ccw (Gimp *gimp);
+GimpGradient * gimp_gradients_get_fg_bg_hsv_cw (Gimp *gimp);
+GimpGradient * gimp_gradients_get_fg_transparent (Gimp *gimp);
+
+
+#endif /* __GIMP_GRADIENTS__ */
diff --git a/app/core/gimp-gui.c b/app/core/gimp-gui.c
new file mode 100644
index 0000000..0b0fe42
--- /dev/null
+++ b/app/core/gimp-gui.c
@@ -0,0 +1,585 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-2002 Spencer Kimball, Peter Mattis, and others
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpbase/gimpbase.h"
+
+#include "core-types.h"
+
+#include "gimp.h"
+#include "gimp-gui.h"
+#include "gimpcontainer.h"
+#include "gimpcontext.h"
+#include "gimpimage.h"
+#include "gimpprogress.h"
+#include "gimpwaitable.h"
+
+#include "about.h"
+
+#include "gimp-intl.h"
+
+
+void
+gimp_gui_init (Gimp *gimp)
+{
+ g_return_if_fail (GIMP_IS_GIMP (gimp));
+
+ gimp->gui.ungrab = NULL;
+ gimp->gui.threads_enter = NULL;
+ gimp->gui.threads_leave = NULL;
+ gimp->gui.set_busy = NULL;
+ gimp->gui.unset_busy = NULL;
+ gimp->gui.show_message = NULL;
+ gimp->gui.help = NULL;
+ gimp->gui.get_program_class = NULL;
+ gimp->gui.get_display_name = NULL;
+ gimp->gui.get_user_time = NULL;
+ gimp->gui.get_theme_dir = NULL;
+ gimp->gui.get_icon_theme_dir = NULL;
+ gimp->gui.display_get_by_id = NULL;
+ gimp->gui.display_get_id = NULL;
+ gimp->gui.display_get_window_id = NULL;
+ gimp->gui.display_create = NULL;
+ gimp->gui.display_delete = NULL;
+ gimp->gui.displays_reconnect = NULL;
+ gimp->gui.progress_new = NULL;
+ gimp->gui.progress_free = NULL;
+ gimp->gui.pdb_dialog_set = NULL;
+ gimp->gui.pdb_dialog_close = NULL;
+ gimp->gui.recent_list_add_file = NULL;
+ gimp->gui.recent_list_load = NULL;
+ gimp->gui.get_mount_operation = NULL;
+ gimp->gui.query_profile_policy = NULL;
+}
+
+void
+gimp_gui_ungrab (Gimp *gimp)
+{
+ g_return_if_fail (GIMP_IS_GIMP (gimp));
+
+ if (gimp->gui.ungrab)
+ gimp->gui.ungrab (gimp);
+}
+
+void
+gimp_threads_enter (Gimp *gimp)
+{
+ g_return_if_fail (GIMP_IS_GIMP (gimp));
+
+ if (gimp->gui.threads_enter)
+ gimp->gui.threads_enter (gimp);
+}
+
+void
+gimp_threads_leave (Gimp *gimp)
+{
+ g_return_if_fail (GIMP_IS_GIMP (gimp));
+
+ if (gimp->gui.threads_leave)
+ gimp->gui.threads_leave (gimp);
+}
+
+void
+gimp_set_busy (Gimp *gimp)
+{
+ g_return_if_fail (GIMP_IS_GIMP (gimp));
+
+ /* FIXME: gimp_busy HACK */
+ gimp->busy++;
+
+ if (gimp->busy == 1)
+ {
+ if (gimp->gui.set_busy)
+ gimp->gui.set_busy (gimp);
+ }
+}
+
+static gboolean
+gimp_idle_unset_busy (gpointer data)
+{
+ Gimp *gimp = data;
+
+ gimp_unset_busy (gimp);
+
+ gimp->busy_idle_id = 0;
+
+ return FALSE;
+}
+
+void
+gimp_set_busy_until_idle (Gimp *gimp)
+{
+ g_return_if_fail (GIMP_IS_GIMP (gimp));
+
+ if (! gimp->busy_idle_id)
+ {
+ gimp_set_busy (gimp);
+
+ gimp->busy_idle_id = g_idle_add_full (G_PRIORITY_HIGH,
+ gimp_idle_unset_busy, gimp,
+ NULL);
+ }
+}
+
+void
+gimp_unset_busy (Gimp *gimp)
+{
+ g_return_if_fail (GIMP_IS_GIMP (gimp));
+ g_return_if_fail (gimp->busy > 0);
+
+ /* FIXME: gimp_busy HACK */
+ gimp->busy--;
+
+ if (gimp->busy == 0)
+ {
+ if (gimp->gui.unset_busy)
+ gimp->gui.unset_busy (gimp);
+ }
+}
+
+void
+gimp_show_message (Gimp *gimp,
+ GObject *handler,
+ GimpMessageSeverity severity,
+ const gchar *domain,
+ const gchar *message)
+{
+ const gchar *desc = (severity == GIMP_MESSAGE_ERROR) ? "Error" : "Message";
+
+ g_return_if_fail (GIMP_IS_GIMP (gimp));
+ g_return_if_fail (handler == NULL || G_IS_OBJECT (handler));
+ g_return_if_fail (message != NULL);
+
+ if (! domain)
+ domain = GIMP_ACRONYM;
+
+ if (! gimp->console_messages)
+ {
+ if (gimp->gui.show_message)
+ {
+ gimp->gui.show_message (gimp, handler, severity,
+ domain, message);
+ return;
+ }
+ else if (GIMP_IS_PROGRESS (handler) &&
+ gimp_progress_message (GIMP_PROGRESS (handler), gimp,
+ severity, domain, message))
+ {
+ /* message has been handled by GimpProgress */
+ return;
+ }
+ }
+
+ gimp_enum_get_value (GIMP_TYPE_MESSAGE_SEVERITY, severity,
+ NULL, NULL, &desc, NULL);
+ g_printerr ("%s-%s: %s\n\n", domain, desc, message);
+}
+
+void
+gimp_wait (Gimp *gimp,
+ GimpWaitable *waitable,
+ const gchar *format,
+ ...)
+{
+ va_list args;
+ gchar *message;
+
+ g_return_if_fail (GIMP_IS_GIMP (gimp));
+ g_return_if_fail (GIMP_IS_WAITABLE (waitable));
+ g_return_if_fail (format != NULL);
+
+ if (gimp_waitable_wait_for (waitable, 0.5 * G_TIME_SPAN_SECOND))
+ return;
+
+ va_start (args, format);
+
+ message = g_strdup_vprintf (format, args);
+
+ va_end (args);
+
+ if (! gimp->console_messages &&
+ gimp->gui.wait &&
+ gimp->gui.wait (gimp, waitable, message))
+ {
+ return;
+ }
+
+ /* Translator: This message is displayed while GIMP is waiting for
+ * some operation to finish. The %s argument is a message describing
+ * the operation.
+ */
+ g_printerr (_("Please wait: %s\n"), message);
+
+ gimp_waitable_wait (waitable);
+
+ g_free (message);
+}
+
+void
+gimp_help (Gimp *gimp,
+ GimpProgress *progress,
+ const gchar *help_domain,
+ const gchar *help_id)
+{
+ g_return_if_fail (GIMP_IS_GIMP (gimp));
+ g_return_if_fail (progress == NULL || GIMP_IS_PROGRESS (progress));
+
+ if (gimp->gui.help)
+ gimp->gui.help (gimp, progress, help_domain, help_id);
+}
+
+const gchar *
+gimp_get_program_class (Gimp *gimp)
+{
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
+
+ if (gimp->gui.get_program_class)
+ return gimp->gui.get_program_class (gimp);
+
+ return NULL;
+}
+
+gchar *
+gimp_get_display_name (Gimp *gimp,
+ gint display_ID,
+ GObject **screen,
+ gint *monitor)
+{
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
+ g_return_val_if_fail (screen != NULL, NULL);
+ g_return_val_if_fail (monitor != NULL, NULL);
+
+ if (gimp->gui.get_display_name)
+ return gimp->gui.get_display_name (gimp, display_ID, screen, monitor);
+
+ *screen = NULL;
+ *monitor = 0;
+
+ return NULL;
+}
+
+/**
+ * gimp_get_user_time:
+ * @gimp:
+ *
+ * Returns the timestamp of the last user interaction. The timestamp is
+ * taken from events caused by user interaction such as key presses or
+ * pointer movements. See gdk_x11_display_get_user_time().
+ *
+ * Return value: the timestamp of the last user interaction
+ */
+guint32
+gimp_get_user_time (Gimp *gimp)
+{
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), 0);
+
+ if (gimp->gui.get_user_time)
+ return gimp->gui.get_user_time (gimp);
+
+ return 0;
+}
+
+GFile *
+gimp_get_theme_dir (Gimp *gimp)
+{
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
+
+ if (gimp->gui.get_theme_dir)
+ return gimp->gui.get_theme_dir (gimp);
+
+ return NULL;
+}
+
+GFile *
+gimp_get_icon_theme_dir (Gimp *gimp)
+{
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
+
+ if (gimp->gui.get_icon_theme_dir)
+ return gimp->gui.get_icon_theme_dir (gimp);
+
+ return NULL;
+}
+
+GimpObject *
+gimp_get_window_strategy (Gimp *gimp)
+{
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
+
+ if (gimp->gui.get_window_strategy)
+ return gimp->gui.get_window_strategy (gimp);
+
+ return NULL;
+}
+
+GimpObject *
+gimp_get_empty_display (Gimp *gimp)
+{
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
+
+ if (gimp->gui.get_empty_display)
+ return gimp->gui.get_empty_display (gimp);
+
+ return NULL;
+}
+
+GimpObject *
+gimp_get_display_by_ID (Gimp *gimp,
+ gint ID)
+{
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
+
+ if (gimp->gui.display_get_by_id)
+ return gimp->gui.display_get_by_id (gimp, ID);
+
+ return NULL;
+}
+
+gint
+gimp_get_display_ID (Gimp *gimp,
+ GimpObject *display)
+{
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), -1);
+ g_return_val_if_fail (GIMP_IS_OBJECT (display), -1);
+
+ if (gimp->gui.display_get_id)
+ return gimp->gui.display_get_id (display);
+
+ return -1;
+}
+
+guint32
+gimp_get_display_window_id (Gimp *gimp,
+ GimpObject *display)
+{
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), -1);
+ g_return_val_if_fail (GIMP_IS_OBJECT (display), -1);
+
+ if (gimp->gui.display_get_window_id)
+ return gimp->gui.display_get_window_id (display);
+
+ return -1;
+}
+
+GimpObject *
+gimp_create_display (Gimp *gimp,
+ GimpImage *image,
+ GimpUnit unit,
+ gdouble scale,
+ GObject *screen,
+ gint monitor)
+{
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
+ g_return_val_if_fail (image == NULL || GIMP_IS_IMAGE (image), NULL);
+ g_return_val_if_fail (screen == NULL || G_IS_OBJECT (screen), NULL);
+
+ if (gimp->gui.display_create)
+ return gimp->gui.display_create (gimp, image, unit, scale, screen, monitor);
+
+ return NULL;
+}
+
+void
+gimp_delete_display (Gimp *gimp,
+ GimpObject *display)
+{
+ g_return_if_fail (GIMP_IS_GIMP (gimp));
+ g_return_if_fail (GIMP_IS_OBJECT (display));
+
+ if (gimp->gui.display_delete)
+ gimp->gui.display_delete (display);
+}
+
+void
+gimp_reconnect_displays (Gimp *gimp,
+ GimpImage *old_image,
+ GimpImage *new_image)
+{
+ g_return_if_fail (GIMP_IS_GIMP (gimp));
+ g_return_if_fail (GIMP_IS_IMAGE (old_image));
+ g_return_if_fail (GIMP_IS_IMAGE (new_image));
+
+ if (gimp->gui.displays_reconnect)
+ gimp->gui.displays_reconnect (gimp, old_image, new_image);
+}
+
+GimpProgress *
+gimp_new_progress (Gimp *gimp,
+ GimpObject *display)
+{
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
+ g_return_val_if_fail (display == NULL || GIMP_IS_OBJECT (display), NULL);
+
+ if (gimp->gui.progress_new)
+ return gimp->gui.progress_new (gimp, display);
+
+ return NULL;
+}
+
+void
+gimp_free_progress (Gimp *gimp,
+ GimpProgress *progress)
+{
+ g_return_if_fail (GIMP_IS_GIMP (gimp));
+ g_return_if_fail (GIMP_IS_PROGRESS (progress));
+
+ if (gimp->gui.progress_free)
+ gimp->gui.progress_free (gimp, progress);
+}
+
+gboolean
+gimp_pdb_dialog_new (Gimp *gimp,
+ GimpContext *context,
+ GimpProgress *progress,
+ GimpContainer *container,
+ const gchar *title,
+ const gchar *callback_name,
+ const gchar *object_name,
+ ...)
+{
+ gboolean retval = FALSE;
+
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), FALSE);
+ g_return_val_if_fail (GIMP_IS_CONTEXT (context), FALSE);
+ g_return_val_if_fail (progress == NULL || GIMP_IS_PROGRESS (progress), FALSE);
+ g_return_val_if_fail (GIMP_IS_CONTAINER (container), FALSE);
+ g_return_val_if_fail (title != NULL, FALSE);
+ g_return_val_if_fail (callback_name != NULL, FALSE);
+
+ if (gimp->gui.pdb_dialog_new)
+ {
+ va_list args;
+
+ va_start (args, object_name);
+
+ retval = gimp->gui.pdb_dialog_new (gimp, context, progress,
+ container, title,
+ callback_name, object_name,
+ args);
+
+ va_end (args);
+ }
+
+ return retval;
+}
+
+gboolean
+gimp_pdb_dialog_set (Gimp *gimp,
+ GimpContainer *container,
+ const gchar *callback_name,
+ const gchar *object_name,
+ ...)
+{
+ gboolean retval = FALSE;
+
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), FALSE);
+ g_return_val_if_fail (GIMP_IS_CONTAINER (container), FALSE);
+ g_return_val_if_fail (callback_name != NULL, FALSE);
+ g_return_val_if_fail (object_name != NULL, FALSE);
+
+ if (gimp->gui.pdb_dialog_set)
+ {
+ va_list args;
+
+ va_start (args, object_name);
+
+ retval = gimp->gui.pdb_dialog_set (gimp, container, callback_name,
+ object_name, args);
+
+ va_end (args);
+ }
+
+ return retval;
+}
+
+gboolean
+gimp_pdb_dialog_close (Gimp *gimp,
+ GimpContainer *container,
+ const gchar *callback_name)
+{
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), FALSE);
+ g_return_val_if_fail (GIMP_IS_CONTAINER (container), FALSE);
+ g_return_val_if_fail (callback_name != NULL, FALSE);
+
+ if (gimp->gui.pdb_dialog_close)
+ return gimp->gui.pdb_dialog_close (gimp, container, callback_name);
+
+ return FALSE;
+}
+
+gboolean
+gimp_recent_list_add_file (Gimp *gimp,
+ GFile *file,
+ const gchar *mime_type)
+{
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), FALSE);
+ g_return_val_if_fail (G_IS_FILE (file), FALSE);
+
+ if (gimp->gui.recent_list_add_file)
+ return gimp->gui.recent_list_add_file (gimp, file, mime_type);
+
+ return FALSE;
+}
+
+void
+gimp_recent_list_load (Gimp *gimp)
+{
+ g_return_if_fail (GIMP_IS_GIMP (gimp));
+
+ if (gimp->gui.recent_list_load)
+ gimp->gui.recent_list_load (gimp);
+}
+
+GMountOperation *
+gimp_get_mount_operation (Gimp *gimp,
+ GimpProgress *progress)
+{
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), FALSE);
+ g_return_val_if_fail (progress == NULL || GIMP_IS_PROGRESS (progress), FALSE);
+
+ if (gimp->gui.get_mount_operation)
+ return gimp->gui.get_mount_operation (gimp, progress);
+
+ return g_mount_operation_new ();
+}
+
+GimpColorProfilePolicy
+gimp_query_profile_policy (Gimp *gimp,
+ GimpImage *image,
+ GimpContext *context,
+ GimpColorProfile **dest_profile,
+ GimpColorRenderingIntent *intent,
+ gboolean *bpc,
+ gboolean *dont_ask)
+{
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), GIMP_COLOR_PROFILE_POLICY_KEEP);
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), GIMP_COLOR_PROFILE_POLICY_KEEP);
+ g_return_val_if_fail (GIMP_IS_CONTEXT (context), GIMP_COLOR_PROFILE_POLICY_KEEP);
+ g_return_val_if_fail (dest_profile != NULL, GIMP_COLOR_PROFILE_POLICY_KEEP);
+
+ if (gimp->gui.query_profile_policy)
+ return gimp->gui.query_profile_policy (gimp, image, context,
+ dest_profile,
+ intent, bpc,
+ dont_ask);
+
+ return GIMP_COLOR_PROFILE_POLICY_KEEP;
+}
diff --git a/app/core/gimp-gui.h b/app/core/gimp-gui.h
new file mode 100644
index 0000000..49f2761
--- /dev/null
+++ b/app/core/gimp-gui.h
@@ -0,0 +1,211 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-1997 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_GUI_H__
+#define __GIMP_GUI_H__
+
+
+typedef struct _GimpGui GimpGui;
+
+struct _GimpGui
+{
+ void (* ungrab) (Gimp *gimp);
+
+ void (* threads_enter) (Gimp *gimp);
+ void (* threads_leave) (Gimp *gimp);
+
+ void (* set_busy) (Gimp *gimp);
+ void (* unset_busy) (Gimp *gimp);
+
+ void (* show_message) (Gimp *gimp,
+ GObject *handler,
+ GimpMessageSeverity severity,
+ const gchar *domain,
+ const gchar *message);
+ void (* help) (Gimp *gimp,
+ GimpProgress *progress,
+ const gchar *help_domain,
+ const gchar *help_id);
+
+ gboolean (* wait) (Gimp *gimp,
+ GimpWaitable *waitable,
+ const gchar *message);
+
+ const gchar * (* get_program_class) (Gimp *gimp);
+ gchar * (* get_display_name) (Gimp *gimp,
+ gint display_ID,
+ GObject **screen,
+ gint *monitor);
+ guint32 (* get_user_time) (Gimp *gimp);
+
+ GFile * (* get_theme_dir) (Gimp *gimp);
+ GFile * (* get_icon_theme_dir) (Gimp *gimp);
+
+ GimpObject * (* get_window_strategy) (Gimp *gimp);
+ GimpObject * (* get_empty_display) (Gimp *gimp);
+ GimpObject * (* display_get_by_id) (Gimp *gimp,
+ gint ID);
+ gint (* display_get_id) (GimpObject *display);
+ guint32 (* display_get_window_id) (GimpObject *display);
+ GimpObject * (* display_create) (Gimp *gimp,
+ GimpImage *image,
+ GimpUnit unit,
+ gdouble scale,
+ GObject *screen,
+ gint monitor);
+ void (* display_delete) (GimpObject *display);
+ void (* displays_reconnect) (Gimp *gimp,
+ GimpImage *old_image,
+ GimpImage *new_image);
+
+ GimpProgress * (* progress_new) (Gimp *gimp,
+ GimpObject *display);
+ void (* progress_free) (Gimp *gimp,
+ GimpProgress *progress);
+
+ gboolean (* pdb_dialog_new) (Gimp *gimp,
+ GimpContext *context,
+ GimpProgress *progress,
+ GimpContainer *container,
+ const gchar *title,
+ const gchar *callback_name,
+ const gchar *object_name,
+ va_list args);
+ gboolean (* pdb_dialog_set) (Gimp *gimp,
+ GimpContainer *container,
+ const gchar *callback_name,
+ const gchar *object_name,
+ va_list args);
+ gboolean (* pdb_dialog_close) (Gimp *gimp,
+ GimpContainer *container,
+ const gchar *callback_name);
+ gboolean (* recent_list_add_file) (Gimp *gimp,
+ GFile *file,
+ const gchar *mime_type);
+ void (* recent_list_load) (Gimp *gimp);
+
+ GMountOperation
+ * (* get_mount_operation) (Gimp *gimp,
+ GimpProgress *progress);
+
+ GimpColorProfilePolicy
+ (* query_profile_policy) (Gimp *gimp,
+ GimpImage *image,
+ GimpContext *context,
+ GimpColorProfile **dest_profile,
+ GimpColorRenderingIntent *intent,
+ gboolean *bpc,
+ gboolean *dont_ask);
+};
+
+
+void gimp_gui_init (Gimp *gimp);
+
+void gimp_gui_ungrab (Gimp *gimp);
+
+void gimp_threads_enter (Gimp *gimp);
+void gimp_threads_leave (Gimp *gimp);
+
+GimpObject * gimp_get_window_strategy (Gimp *gimp);
+GimpObject * gimp_get_empty_display (Gimp *gimp);
+GimpObject * gimp_get_display_by_ID (Gimp *gimp,
+ gint ID);
+gint gimp_get_display_ID (Gimp *gimp,
+ GimpObject *display);
+guint32 gimp_get_display_window_id (Gimp *gimp,
+ GimpObject *display);
+GimpObject * gimp_create_display (Gimp *gimp,
+ GimpImage *image,
+ GimpUnit unit,
+ gdouble scale,
+ GObject *screen,
+ gint monitor);
+void gimp_delete_display (Gimp *gimp,
+ GimpObject *display);
+void gimp_reconnect_displays (Gimp *gimp,
+ GimpImage *old_image,
+ GimpImage *new_image);
+
+void gimp_set_busy (Gimp *gimp);
+void gimp_set_busy_until_idle (Gimp *gimp);
+void gimp_unset_busy (Gimp *gimp);
+
+void gimp_show_message (Gimp *gimp,
+ GObject *handler,
+ GimpMessageSeverity severity,
+ const gchar *domain,
+ const gchar *message);
+void gimp_help (Gimp *gimp,
+ GimpProgress *progress,
+ const gchar *help_domain,
+ const gchar *help_id);
+
+void gimp_wait (Gimp *gimp,
+ GimpWaitable *waitable,
+ const gchar *format,
+ ...) G_GNUC_PRINTF (3, 4);
+
+GimpProgress * gimp_new_progress (Gimp *gimp,
+ GimpObject *display);
+void gimp_free_progress (Gimp *gimp,
+ GimpProgress *progress);
+
+const gchar * gimp_get_program_class (Gimp *gimp);
+gchar * gimp_get_display_name (Gimp *gimp,
+ gint display_ID,
+ GObject **screen,
+ gint *monitor);
+guint32 gimp_get_user_time (Gimp *gimp);
+GFile * gimp_get_theme_dir (Gimp *gimp);
+GFile * gimp_get_icon_theme_dir (Gimp *gimp);
+
+gboolean gimp_pdb_dialog_new (Gimp *gimp,
+ GimpContext *context,
+ GimpProgress *progress,
+ GimpContainer *container,
+ const gchar *title,
+ const gchar *callback_name,
+ const gchar *object_name,
+ ...) G_GNUC_NULL_TERMINATED;
+gboolean gimp_pdb_dialog_set (Gimp *gimp,
+ GimpContainer *container,
+ const gchar *callback_name,
+ const gchar *object_name,
+ ...) G_GNUC_NULL_TERMINATED;
+gboolean gimp_pdb_dialog_close (Gimp *gimp,
+ GimpContainer *container,
+ const gchar *callback_name);
+gboolean gimp_recent_list_add_file (Gimp *gimp,
+ GFile *file,
+ const gchar *mime_type);
+void gimp_recent_list_load (Gimp *gimp);
+
+GMountOperation
+ * gimp_get_mount_operation (Gimp *gimp,
+ GimpProgress *progress);
+
+GimpColorProfilePolicy
+ gimp_query_profile_policy (Gimp *gimp,
+ GimpImage *image,
+ GimpContext *context,
+ GimpColorProfile **dest_profile,
+ GimpColorRenderingIntent *intent,
+ gboolean *bpc,
+ gboolean *dont_ask);
+
+
+#endif /* __GIMP_GUI_H__ */
diff --git a/app/core/gimp-internal-data.c b/app/core/gimp-internal-data.c
new file mode 100644
index 0000000..6653946
--- /dev/null
+++ b/app/core/gimp-internal-data.c
@@ -0,0 +1,346 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-2002 Spencer Kimball, Peter Mattis, and others
+ *
+ * gimp-internal-data.c
+ * Copyright (C) 2017 Ell
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpbase/gimpbase.h"
+
+#include "core-types.h"
+
+#include "gimp.h"
+#include "gimp-gradients.h"
+#include "gimp-internal-data.h"
+#include "gimpdata.h"
+#include "gimpdataloaderfactory.h"
+#include "gimperror.h"
+#include "gimpgradient-load.h"
+
+#include "gimp-intl.h"
+
+
+#define GIMP_INTERNAL_DATA_DIRECTORY "internal-data"
+
+
+typedef GimpData * (* GimpDataGetFunc) (Gimp *gimp);
+
+
+typedef struct _GimpInternalDataFile GimpInternalDataFile;
+
+struct _GimpInternalDataFile
+{
+ const gchar *name;
+ GimpDataGetFunc get_func;
+ GimpDataLoadFunc load_func;
+};
+
+
+/* local function prototypes */
+
+static gboolean gimp_internal_data_create_directory (GError **error);
+
+static GFile * gimp_internal_data_get_file (const GimpInternalDataFile *data_file);
+
+static gboolean gimp_internal_data_load_data_file (Gimp *gimp,
+ const GimpInternalDataFile *data_file,
+ GError **error);
+static gboolean gimp_internal_data_save_data_file (Gimp *gimp,
+ const GimpInternalDataFile *data_file,
+ GError **error);
+static gboolean gimp_internal_data_delete_data_file (Gimp *gimp,
+ const GimpInternalDataFile *data_file,
+ GError **error);
+
+/* static variables */
+
+static const GimpInternalDataFile internal_data_files[] =
+{
+ /* Custom gradient */
+ {
+ .name = "custom" GIMP_GRADIENT_FILE_EXTENSION,
+ .get_func = (GimpDataGetFunc) gimp_gradients_get_custom,
+ .load_func = gimp_gradient_load
+ }
+};
+
+
+/* public functions */
+
+gboolean
+gimp_internal_data_load (Gimp *gimp,
+ GError **error)
+{
+ gint i;
+
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ for (i = 0; i < G_N_ELEMENTS (internal_data_files); i++)
+ {
+ const GimpInternalDataFile *data_file = &internal_data_files[i];
+
+ if (! gimp_internal_data_load_data_file (gimp, data_file, error))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+gboolean
+gimp_internal_data_save (Gimp *gimp,
+ GError **error)
+{
+ gint i;
+
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ if (! gimp_internal_data_create_directory (error))
+ return FALSE;
+
+ for (i = 0; i < G_N_ELEMENTS (internal_data_files); i++)
+ {
+ const GimpInternalDataFile *data_file = &internal_data_files[i];
+
+ if (! gimp_internal_data_save_data_file (gimp, data_file, error))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+gboolean
+gimp_internal_data_clear (Gimp *gimp,
+ GError **error)
+{
+ gint i;
+
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ for (i = 0; i < G_N_ELEMENTS (internal_data_files); i++)
+ {
+ const GimpInternalDataFile *data_file = &internal_data_files[i];
+
+ if (! gimp_internal_data_delete_data_file (gimp, data_file, error))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+
+/* private functions */
+
+static gboolean
+gimp_internal_data_create_directory (GError **error)
+{
+ GFile *directory;
+ GError *my_error = NULL;
+ gboolean success;
+
+ directory = gimp_directory_file (GIMP_INTERNAL_DATA_DIRECTORY, NULL);
+
+ success = g_file_make_directory (directory, NULL, &my_error);
+
+ g_object_unref (directory);
+
+ if (! success)
+ {
+ if (my_error->code == G_IO_ERROR_EXISTS)
+ {
+ g_clear_error (&my_error);
+ success = TRUE;
+ }
+ else
+ {
+ g_propagate_error (error, my_error);
+ }
+ }
+
+ return success;
+}
+
+static GFile *
+gimp_internal_data_get_file (const GimpInternalDataFile *data_file)
+{
+ return gimp_directory_file (GIMP_INTERNAL_DATA_DIRECTORY,
+ data_file->name,
+ NULL);
+}
+
+static gboolean
+gimp_internal_data_load_data_file (Gimp *gimp,
+ const GimpInternalDataFile *data_file,
+ GError **error)
+{
+ GFile *file;
+ GInputStream *input;
+ GimpData *data;
+ GList *list;
+ GError *my_error = NULL;
+
+ file = gimp_internal_data_get_file (data_file);
+
+ if (gimp->be_verbose)
+ g_print ("Parsing '%s'\n", gimp_file_get_utf8_name (file));
+
+ input = G_INPUT_STREAM (g_file_read (file, NULL, &my_error));
+
+ if (! input)
+ {
+ g_object_unref (file);
+
+ if (my_error->code == G_IO_ERROR_NOT_FOUND)
+ {
+ g_clear_error (&my_error);
+ return TRUE;
+ }
+ else
+ {
+ g_propagate_error (error, my_error);
+ return FALSE;
+ }
+ }
+
+ list = data_file->load_func (gimp->user_context, file, input, error);
+
+ g_object_unref (input);
+ g_object_unref (file);
+
+ if (! list)
+ return FALSE;
+
+ data = data_file->get_func (gimp);
+
+ gimp_data_copy (data, GIMP_DATA (list->data));
+
+ g_list_free_full (list, g_object_unref);
+
+ return TRUE;
+}
+
+static gboolean
+gimp_internal_data_save_data_file (Gimp *gimp,
+ const GimpInternalDataFile *data_file,
+ GError **error)
+{
+ GFile *file;
+ GOutputStream *output;
+ GimpData *data;
+ gboolean success;
+
+ file = gimp_internal_data_get_file (data_file);
+
+ if (gimp->be_verbose)
+ g_print ("Writing '%s'\n", gimp_file_get_utf8_name (file));
+
+ output = G_OUTPUT_STREAM (g_file_replace (file, NULL, FALSE,
+ G_FILE_CREATE_NONE,
+ NULL, error));
+
+ if (! output)
+ {
+ g_object_unref (file);
+
+ return FALSE;
+ }
+
+ data = data_file->get_func (gimp);
+
+ /* we bypass gimp_data_save() and call the data's save() virtual function
+ * directly, since gimp_data_save() is a nop for internal data.
+ *
+ * FIXME: we save the data whether it's dirty or not, since it might not
+ * exist on disk. currently, we only use this for cheap data, such as
+ * gradients, so this is not a big concern, but if we save more expensive
+ * data in the future, we should fix this.
+ */
+ gimp_assert (GIMP_DATA_GET_CLASS (data)->save);
+ success = GIMP_DATA_GET_CLASS (data)->save (data, output, error);
+
+ if (success)
+ {
+ if (! g_output_stream_close (output, NULL, error))
+ {
+ g_prefix_error (error,
+ _("Error saving '%s': "),
+ gimp_file_get_utf8_name (file));
+ success = FALSE;
+ }
+ }
+ else
+ {
+ GCancellable *cancellable = g_cancellable_new ();
+
+ g_cancellable_cancel (cancellable);
+ if (error && *error)
+ {
+ g_prefix_error (error,
+ _("Error saving '%s': "),
+ gimp_file_get_utf8_name (file));
+ }
+ else
+ {
+ g_set_error (error, GIMP_DATA_ERROR, GIMP_DATA_ERROR_WRITE,
+ _("Error saving '%s'"),
+ gimp_file_get_utf8_name (file));
+ }
+ g_output_stream_close (output, cancellable, NULL);
+ g_object_unref (cancellable);
+ }
+
+ g_object_unref (output);
+ g_object_unref (file);
+
+ return success;
+}
+
+static gboolean
+gimp_internal_data_delete_data_file (Gimp *gimp,
+ const GimpInternalDataFile *data_file,
+ GError **error)
+{
+ GFile *file;
+ GError *my_error = NULL;
+ gboolean success = TRUE;
+
+ file = gimp_internal_data_get_file (data_file);
+
+ if (gimp->be_verbose)
+ g_print ("Deleting '%s'\n", gimp_file_get_utf8_name (file));
+
+ if (! g_file_delete (file, NULL, &my_error) &&
+ my_error->code != G_IO_ERROR_NOT_FOUND)
+ {
+ success = FALSE;
+
+ g_set_error (error, GIMP_ERROR, GIMP_FAILED,
+ _("Deleting \"%s\" failed: %s"),
+ gimp_file_get_utf8_name (file), my_error->message);
+ }
+
+ g_clear_error (&my_error);
+ g_object_unref (file);
+
+ return success;
+}
diff --git a/app/core/gimp-internal-data.h b/app/core/gimp-internal-data.h
new file mode 100644
index 0000000..93f42dd
--- /dev/null
+++ b/app/core/gimp-internal-data.h
@@ -0,0 +1,34 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-2002 Spencer Kimball, Peter Mattis, and others
+ *
+ * gimp-internal-data.h
+ * Copyright (C) 2017 Ell
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_INTERNAL_DATA__
+#define __GIMP_INTERNAL_DATA__
+
+
+gboolean gimp_internal_data_load (Gimp *gimp,
+ GError **error);
+gboolean gimp_internal_data_save (Gimp *gimp,
+ GError **error);
+
+gboolean gimp_internal_data_clear (Gimp *gimp,
+ GError **error);
+
+
+#endif /* __GIMP_INTERNAL_DATA__ */
diff --git a/app/core/gimp-memsize.c b/app/core/gimp-memsize.c
new file mode 100644
index 0000000..df71737
--- /dev/null
+++ b/app/core/gimp-memsize.c
@@ -0,0 +1,341 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <cairo.h>
+#include <gegl.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+#include "libgimpbase/gimpbase.h"
+#include "libgimpmath/gimpmath.h"
+#include "libgimpcolor/gimpcolor.h"
+
+#include "core-types.h"
+
+#include "gimp-memsize.h"
+#include "gimpparamspecs.h"
+
+
+gint64
+gimp_g_type_instance_get_memsize (GTypeInstance *instance)
+{
+ if (instance)
+ {
+ GTypeQuery type_query;
+
+ g_type_query (G_TYPE_FROM_INSTANCE (instance), &type_query);
+
+ return type_query.instance_size;
+ }
+
+ return 0;
+}
+
+gint64
+gimp_g_object_get_memsize (GObject *object)
+{
+ if (object)
+ return gimp_g_type_instance_get_memsize ((GTypeInstance *) object);
+
+ return 0;
+}
+
+gint64
+gimp_g_hash_table_get_memsize (GHashTable *hash,
+ gint64 data_size)
+{
+ if (hash)
+ return (2 * sizeof (gint) +
+ 5 * sizeof (gpointer) +
+ g_hash_table_size (hash) * (3 * sizeof (gpointer) + data_size));
+
+ return 0;
+}
+
+typedef struct
+{
+ GimpMemsizeFunc func;
+ gint64 memsize;
+ gint64 gui_size;
+} HashMemsize;
+
+static void
+hash_memsize_foreach (gpointer key,
+ gpointer value,
+ HashMemsize *memsize)
+{
+ gint64 gui_size = 0;
+
+ memsize->memsize += memsize->func (value, &gui_size);
+ memsize->gui_size += gui_size;
+}
+
+gint64
+gimp_g_hash_table_get_memsize_foreach (GHashTable *hash,
+ GimpMemsizeFunc func,
+ gint64 *gui_size)
+{
+ HashMemsize memsize;
+
+ g_return_val_if_fail (func != NULL, 0);
+
+ if (! hash)
+ return 0;
+
+ memsize.func = func;
+ memsize.memsize = 0;
+ memsize.gui_size = 0;
+
+ g_hash_table_foreach (hash, (GHFunc) hash_memsize_foreach, &memsize);
+
+ if (gui_size)
+ *gui_size = memsize.gui_size;
+
+ return memsize.memsize + gimp_g_hash_table_get_memsize (hash, 0);
+}
+
+gint64
+gimp_g_slist_get_memsize (GSList *slist,
+ gint64 data_size)
+{
+ return g_slist_length (slist) * (data_size + sizeof (GSList));
+}
+
+gint64
+gimp_g_slist_get_memsize_foreach (GSList *slist,
+ GimpMemsizeFunc func,
+ gint64 *gui_size)
+{
+ GSList *l;
+ gint64 memsize = 0;
+
+ g_return_val_if_fail (func != NULL, 0);
+
+ for (l = slist; l; l = g_slist_next (l))
+ memsize += sizeof (GSList) + func (l->data, gui_size);
+
+ return memsize;
+}
+
+gint64
+gimp_g_list_get_memsize (GList *list,
+ gint64 data_size)
+{
+ return g_list_length (list) * (data_size + sizeof (GList));
+}
+
+gint64
+gimp_g_list_get_memsize_foreach (GList *list,
+ GimpMemsizeFunc func,
+ gint64 *gui_size)
+{
+ GList *l;
+ gint64 memsize = 0;
+
+ g_return_val_if_fail (func != NULL, 0);
+
+ for (l = list; l; l = g_list_next (l))
+ memsize += sizeof (GList) + func (l->data, gui_size);
+
+ return memsize;
+}
+
+gint64
+gimp_g_queue_get_memsize (GQueue *queue,
+ gint64 data_size)
+{
+ if (queue)
+ {
+ return sizeof (GQueue) +
+ g_queue_get_length (queue) * (data_size + sizeof (GList));
+ }
+
+ return 0;
+}
+
+gint64
+gimp_g_queue_get_memsize_foreach (GQueue *queue,
+ GimpMemsizeFunc func,
+ gint64 *gui_size)
+{
+ gint64 memsize = 0;
+
+ g_return_val_if_fail (func != NULL, 0);
+
+ if (queue)
+ {
+ GList *l;
+
+ memsize = sizeof (GQueue);
+
+ for (l = queue->head; l; l = g_list_next (l))
+ memsize += sizeof (GList) + func (l->data, gui_size);
+ }
+
+ return memsize;
+}
+
+gint64
+gimp_g_value_get_memsize (GValue *value)
+{
+ gint64 memsize = 0;
+
+ if (! value)
+ return 0;
+
+ if (G_VALUE_HOLDS_STRING (value))
+ {
+ memsize += gimp_string_get_memsize (g_value_get_string (value));
+ }
+ else if (G_VALUE_HOLDS_BOXED (value))
+ {
+ if (GIMP_VALUE_HOLDS_RGB (value))
+ {
+ memsize += sizeof (GimpRGB);
+ }
+ else if (GIMP_VALUE_HOLDS_MATRIX2 (value))
+ {
+ memsize += sizeof (GimpMatrix2);
+ }
+ else if (GIMP_VALUE_HOLDS_PARASITE (value))
+ {
+ memsize += gimp_parasite_get_memsize (g_value_get_boxed (value),
+ NULL);
+ }
+ else if (GIMP_VALUE_HOLDS_ARRAY (value) ||
+ GIMP_VALUE_HOLDS_INT8_ARRAY (value) ||
+ GIMP_VALUE_HOLDS_INT16_ARRAY (value) ||
+ GIMP_VALUE_HOLDS_INT32_ARRAY (value) ||
+ GIMP_VALUE_HOLDS_FLOAT_ARRAY (value))
+ {
+ GimpArray *array = g_value_get_boxed (value);
+
+ if (array)
+ memsize += sizeof (GimpArray) +
+ (array->static_data ? 0 : array->length);
+ }
+ else if (GIMP_VALUE_HOLDS_STRING_ARRAY (value))
+ {
+ GimpArray *array = g_value_get_boxed (value);
+
+ if (array)
+ {
+ memsize += sizeof (GimpArray);
+
+ if (! array->static_data)
+ {
+ gchar **tmp = (gchar **) array->data;
+ gint i;
+
+ memsize += array->length * sizeof (gchar *);
+
+ for (i = 0; i < array->length; i++)
+ memsize += gimp_string_get_memsize (tmp[i]);
+ }
+ }
+ }
+ else
+ {
+ g_printerr ("%s: unhandled boxed value type: %s\n",
+ G_STRFUNC, G_VALUE_TYPE_NAME (value));
+ }
+ }
+ else if (G_VALUE_HOLDS_OBJECT (value))
+ {
+ g_printerr ("%s: unhandled object value type: %s\n",
+ G_STRFUNC, G_VALUE_TYPE_NAME (value));
+ }
+
+ return memsize + sizeof (GValue);
+}
+
+gint64
+gimp_g_param_spec_get_memsize (GParamSpec *pspec)
+{
+ gint64 memsize = 0;
+
+ if (! pspec)
+ return 0;
+
+ if (! (pspec->flags & G_PARAM_STATIC_NAME))
+ memsize += gimp_string_get_memsize (g_param_spec_get_name (pspec));
+
+ if (! (pspec->flags & G_PARAM_STATIC_NICK))
+ memsize += gimp_string_get_memsize (g_param_spec_get_nick (pspec));
+
+ if (! (pspec->flags & G_PARAM_STATIC_BLURB))
+ memsize += gimp_string_get_memsize (g_param_spec_get_blurb (pspec));
+
+ return memsize + gimp_g_type_instance_get_memsize ((GTypeInstance *) pspec);
+}
+
+gint64
+gimp_gegl_buffer_get_memsize (GeglBuffer *buffer)
+{
+ if (buffer)
+ {
+ const Babl *format = gegl_buffer_get_format (buffer);
+
+ return ((gint64) babl_format_get_bytes_per_pixel (format) *
+ (gint64) gegl_buffer_get_width (buffer) *
+ (gint64) gegl_buffer_get_height (buffer) +
+ gimp_g_object_get_memsize (G_OBJECT (buffer)));
+ }
+
+ return 0;
+}
+
+gint64
+gimp_gegl_pyramid_get_memsize (GeglBuffer *buffer)
+{
+ if (buffer)
+ {
+ const Babl *format = gegl_buffer_get_format (buffer);
+
+ /* The pyramid levels constitute a geometric sum with a ratio of 1/4. */
+ return ((gint64) babl_format_get_bytes_per_pixel (format) *
+ (gint64) gegl_buffer_get_width (buffer) *
+ (gint64) gegl_buffer_get_height (buffer) * 1.33 +
+ gimp_g_object_get_memsize (G_OBJECT (buffer)));
+ }
+
+ return 0;
+}
+
+gint64
+gimp_string_get_memsize (const gchar *string)
+{
+ if (string)
+ return strlen (string) + 1;
+
+ return 0;
+}
+
+gint64
+gimp_parasite_get_memsize (GimpParasite *parasite,
+ gint64 *gui_size)
+{
+ if (parasite)
+ return (sizeof (GimpParasite) +
+ gimp_string_get_memsize (parasite->name) +
+ parasite->size);
+
+ return 0;
+}
diff --git a/app/core/gimp-memsize.h b/app/core/gimp-memsize.h
new file mode 100644
index 0000000..139dc02
--- /dev/null
+++ b/app/core/gimp-memsize.h
@@ -0,0 +1,60 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __APP_GIMP_MEMSIZE_H__
+#define __APP_GIMP_MEMSIZE_H__
+
+
+gint64 gimp_g_type_instance_get_memsize (GTypeInstance *instance);
+gint64 gimp_g_object_get_memsize (GObject *object);
+
+gint64 gimp_g_hash_table_get_memsize (GHashTable *hash,
+ gint64 data_size);
+gint64 gimp_g_hash_table_get_memsize_foreach (GHashTable *hash,
+ GimpMemsizeFunc func,
+ gint64 *gui_size);
+
+gint64 gimp_g_slist_get_memsize (GSList *slist,
+ gint64 data_size);
+gint64 gimp_g_slist_get_memsize_foreach (GSList *slist,
+ GimpMemsizeFunc func,
+ gint64 *gui_size);
+
+gint64 gimp_g_list_get_memsize (GList *list,
+ gint64 data_size);
+gint64 gimp_g_list_get_memsize_foreach (GList *list,
+ GimpMemsizeFunc func,
+ gint64 *gui_size);
+
+gint64 gimp_g_queue_get_memsize (GQueue *queue,
+ gint64 data_size);
+gint64 gimp_g_queue_get_memsize_foreach (GQueue *queue,
+ GimpMemsizeFunc func,
+ gint64 *gui_size);
+
+gint64 gimp_g_value_get_memsize (GValue *value);
+gint64 gimp_g_param_spec_get_memsize (GParamSpec *pspec);
+
+gint64 gimp_gegl_buffer_get_memsize (GeglBuffer *buffer);
+gint64 gimp_gegl_pyramid_get_memsize (GeglBuffer *buffer);
+
+gint64 gimp_string_get_memsize (const gchar *string);
+gint64 gimp_parasite_get_memsize (GimpParasite *parasite,
+ gint64 *gui_size);
+
+
+#endif /* __APP_GIMP_MEMSIZE_H__ */
diff --git a/app/core/gimp-modules.c b/app/core/gimp-modules.c
new file mode 100644
index 0000000..6a7b30d
--- /dev/null
+++ b/app/core/gimp-modules.c
@@ -0,0 +1,227 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpmodules.c
+ * (C) 1999 Austin Donnelly <austin@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gio/gio.h>
+
+#include "libgimpbase/gimpbase.h"
+#include "libgimpmodule/gimpmodule.h"
+#include "libgimpconfig/gimpconfig.h"
+
+#include "core-types.h"
+
+#include "config/gimpcoreconfig.h"
+
+#include "gimp.h"
+#include "gimp-modules.h"
+
+#include "gimp-intl.h"
+
+
+void
+gimp_modules_init (Gimp *gimp)
+{
+ g_return_if_fail (GIMP_IS_GIMP (gimp));
+
+ if (! gimp->no_interface)
+ {
+ gimp->module_db = gimp_module_db_new (gimp->be_verbose);
+ gimp->write_modulerc = FALSE;
+ }
+}
+
+void
+gimp_modules_exit (Gimp *gimp)
+{
+ g_return_if_fail (GIMP_IS_GIMP (gimp));
+
+ g_clear_object (&gimp->module_db);
+}
+
+void
+gimp_modules_load (Gimp *gimp)
+{
+ GFile *file;
+ GScanner *scanner;
+ gchar *module_load_inhibit = NULL;
+
+ g_return_if_fail (GIMP_IS_GIMP (gimp));
+
+ if (gimp->no_interface)
+ return;
+
+ /* FIXME, gimp->be_verbose is not yet initialized in init() */
+ gimp->module_db->verbose = gimp->be_verbose;
+
+ file = gimp_directory_file ("modulerc", NULL);
+
+ if (gimp->be_verbose)
+ g_print ("Parsing '%s'\n", gimp_file_get_utf8_name (file));
+
+ scanner = gimp_scanner_new_gfile (file, NULL);
+ g_object_unref (file);
+
+ if (scanner)
+ {
+ GTokenType token;
+ GError *error = NULL;
+
+#define MODULE_LOAD_INHIBIT 1
+
+ g_scanner_scope_add_symbol (scanner, 0, "module-load-inhibit",
+ GINT_TO_POINTER (MODULE_LOAD_INHIBIT));
+
+ token = G_TOKEN_LEFT_PAREN;
+
+ while (g_scanner_peek_next_token (scanner) == token)
+ {
+ token = g_scanner_get_next_token (scanner);
+
+ switch (token)
+ {
+ case G_TOKEN_LEFT_PAREN:
+ token = G_TOKEN_SYMBOL;
+ break;
+
+ case G_TOKEN_SYMBOL:
+ if (scanner->value.v_symbol == GINT_TO_POINTER (MODULE_LOAD_INHIBIT))
+ {
+ token = G_TOKEN_STRING;
+
+ if (! gimp_scanner_parse_string_no_validate (scanner,
+ &module_load_inhibit))
+ goto error;
+ }
+ token = G_TOKEN_RIGHT_PAREN;
+ break;
+
+ case G_TOKEN_RIGHT_PAREN:
+ token = G_TOKEN_LEFT_PAREN;
+ break;
+
+ default: /* do nothing */
+ break;
+ }
+ }
+
+#undef MODULE_LOAD_INHIBIT
+
+ if (token != G_TOKEN_LEFT_PAREN)
+ {
+ g_scanner_get_next_token (scanner);
+ g_scanner_unexp_token (scanner, token, NULL, NULL, NULL,
+ _("fatal parse error"), TRUE);
+ }
+
+ error:
+
+ if (error)
+ {
+ gimp_message_literal (gimp, NULL, GIMP_MESSAGE_ERROR, error->message);
+ g_clear_error (&error);
+ }
+
+ gimp_scanner_destroy (scanner);
+ }
+
+ if (module_load_inhibit)
+ {
+ gimp_module_db_set_load_inhibit (gimp->module_db, module_load_inhibit);
+ g_free (module_load_inhibit);
+ }
+
+ gimp_module_db_load (gimp->module_db, gimp->config->module_path);
+}
+
+static void
+add_to_inhibit_string (gpointer data,
+ gpointer user_data)
+{
+ GimpModule *module = data;
+ GString *str = user_data;
+
+ if (module->load_inhibit)
+ {
+ g_string_append_c (str, G_SEARCHPATH_SEPARATOR);
+ g_string_append (str, module->filename);
+ }
+}
+
+void
+gimp_modules_unload (Gimp *gimp)
+{
+ g_return_if_fail (GIMP_IS_GIMP (gimp));
+
+ if (! gimp->no_interface && gimp->write_modulerc)
+ {
+ GimpConfigWriter *writer;
+ GString *str;
+ const gchar *p;
+ GFile *file;
+ GError *error = NULL;
+
+ str = g_string_new (NULL);
+ g_list_foreach (gimp->module_db->modules, add_to_inhibit_string, str);
+ if (str->len > 0)
+ p = str->str + 1;
+ else
+ p = "";
+
+ file = gimp_directory_file ("modulerc", NULL);
+
+ if (gimp->be_verbose)
+ g_print ("Writing '%s'\n", gimp_file_get_utf8_name (file));
+
+ writer = gimp_config_writer_new_gfile (file, TRUE,
+ "GIMP modulerc", &error);
+ g_object_unref (file);
+
+ if (writer)
+ {
+ gimp_config_writer_open (writer, "module-load-inhibit");
+ gimp_config_writer_string (writer, p);
+ gimp_config_writer_close (writer);
+
+ gimp_config_writer_finish (writer, "end of modulerc", &error);
+
+ gimp->write_modulerc = FALSE;
+ }
+
+ g_string_free (str, TRUE);
+
+ if (error)
+ {
+ gimp_message_literal (gimp, NULL, GIMP_MESSAGE_ERROR, error->message);
+ g_clear_error (&error);
+ }
+ }
+}
+
+void
+gimp_modules_refresh (Gimp *gimp)
+{
+ g_return_if_fail (GIMP_IS_GIMP (gimp));
+
+ if (! gimp->no_interface)
+ {
+ gimp_module_db_refresh (gimp->module_db, gimp->config->module_path);
+ }
+}
diff --git a/app/core/gimp-modules.h b/app/core/gimp-modules.h
new file mode 100644
index 0000000..fc90d69
--- /dev/null
+++ b/app/core/gimp-modules.h
@@ -0,0 +1,34 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpmodules.h
+ * (C) 1999 Austin Donnelly <austin@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_MODULES_H__
+#define __GIMP_MODULES_H__
+
+
+void gimp_modules_init (Gimp *gimp);
+void gimp_modules_exit (Gimp *gimp);
+
+void gimp_modules_load (Gimp *gimp);
+void gimp_modules_unload (Gimp *gimp);
+
+void gimp_modules_refresh (Gimp *gimp);
+
+
+#endif /* __GIMP_MODULES_H__ */
diff --git a/app/core/gimp-palettes.c b/app/core/gimp-palettes.c
new file mode 100644
index 0000000..4f189a4
--- /dev/null
+++ b/app/core/gimp-palettes.c
@@ -0,0 +1,143 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-2002 Spencer Kimball, Peter Mattis, and others
+ *
+ * gimp-gradients.c
+ * Copyright (C) 2014 Michael Natterer <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpbase/gimpbase.h"
+
+#include "core-types.h"
+
+#include "gimp.h"
+#include "gimp-palettes.h"
+#include "gimpcontext.h"
+#include "gimpcontainer.h"
+#include "gimpdatafactory.h"
+#include "gimppalettemru.h"
+
+#include "gimp-intl.h"
+
+
+#define COLOR_HISTORY_KEY "gimp-palette-color-history"
+
+
+/* local function prototypes */
+
+static GimpPalette * gimp_palettes_add_palette (Gimp *gimp,
+ const gchar *name,
+ const gchar *id);
+
+
+/* public functions */
+
+void
+gimp_palettes_init (Gimp *gimp)
+{
+ GimpPalette *palette;
+
+ g_return_if_fail (GIMP_IS_GIMP (gimp));
+
+ palette = gimp_palettes_add_palette (gimp,
+ _("Color History"),
+ COLOR_HISTORY_KEY);
+ gimp_context_set_palette (gimp->user_context, palette);
+}
+
+void
+gimp_palettes_load (Gimp *gimp)
+{
+ GimpPalette *palette;
+ GFile *file;
+
+ g_return_if_fail (GIMP_IS_GIMP (gimp));
+
+ palette = gimp_palettes_get_color_history (gimp);
+
+ file = gimp_directory_file ("colorrc", NULL);
+
+ if (gimp->be_verbose)
+ g_print ("Parsing '%s'\n", gimp_file_get_utf8_name (file));
+
+ gimp_palette_mru_load (GIMP_PALETTE_MRU (palette), file);
+
+ g_object_unref (file);
+}
+
+void
+gimp_palettes_save (Gimp *gimp)
+{
+ GimpPalette *palette;
+ GFile *file;
+
+ g_return_if_fail (GIMP_IS_GIMP (gimp));
+
+ palette = gimp_palettes_get_color_history (gimp);
+
+ file = gimp_directory_file ("colorrc", NULL);
+
+ if (gimp->be_verbose)
+ g_print ("Writing '%s'\n", gimp_file_get_utf8_name (file));
+
+ gimp_palette_mru_save (GIMP_PALETTE_MRU (palette), file);
+
+ g_object_unref (file);
+}
+
+GimpPalette *
+gimp_palettes_get_color_history (Gimp *gimp)
+{
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
+
+ return g_object_get_data (G_OBJECT (gimp), COLOR_HISTORY_KEY);
+}
+
+void
+gimp_palettes_add_color_history (Gimp *gimp,
+ const GimpRGB *color)
+{
+ GimpPalette *history;
+
+ history = gimp_palettes_get_color_history (gimp);
+ gimp_palette_mru_add (GIMP_PALETTE_MRU (history), color);
+}
+
+/* private functions */
+
+static GimpPalette *
+gimp_palettes_add_palette (Gimp *gimp,
+ const gchar *name,
+ const gchar *id)
+{
+ GimpData *palette;
+
+ palette = gimp_palette_mru_new (name);
+
+ gimp_data_make_internal (palette, id);
+
+ gimp_container_add (gimp_data_factory_get_container (gimp->palette_factory),
+ GIMP_OBJECT (palette));
+ g_object_unref (palette);
+
+ g_object_set_data (G_OBJECT (gimp), id, palette);
+
+ return GIMP_PALETTE (palette);
+}
diff --git a/app/core/gimp-palettes.h b/app/core/gimp-palettes.h
new file mode 100644
index 0000000..7858c86
--- /dev/null
+++ b/app/core/gimp-palettes.h
@@ -0,0 +1,35 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-2002 Spencer Kimball, Peter Mattis, and others
+ *
+ * gimp-palettes.h
+ * Copyright (C) 2014 Michael Natterer <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_PALETTES__
+#define __GIMP_PALETTES__
+
+
+void gimp_palettes_init (Gimp *gimp);
+
+void gimp_palettes_load (Gimp *gimp);
+void gimp_palettes_save (Gimp *gimp);
+
+GimpPalette * gimp_palettes_get_color_history (Gimp *gimp);
+void gimp_palettes_add_color_history (Gimp *gimp,
+ const GimpRGB *color);
+
+
+#endif /* __GIMP_PALETTES__ */
diff --git a/app/core/gimp-parallel.cc b/app/core/gimp-parallel.cc
new file mode 100644
index 0000000..208bcfe
--- /dev/null
+++ b/app/core/gimp-parallel.cc
@@ -0,0 +1,553 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimp-parallel.c
+ * Copyright (C) 2018 Ell
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+
+#include "config.h"
+
+#include <gio/gio.h>
+#include <gegl.h>
+
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#ifdef G_OS_WIN32
+#include <windows.h>
+#endif
+
+extern "C"
+{
+
+#include "core-types.h"
+
+#include "config/gimpgeglconfig.h"
+
+#include "gimp.h"
+#include "gimp-parallel.h"
+#include "gimpasync.h"
+#include "gimpcancelable.h"
+
+
+#define GIMP_PARALLEL_MAX_THREADS 64
+#define GIMP_PARALLEL_RUN_ASYNC_MAX_THREADS 1
+
+
+typedef struct
+{
+ GimpAsync *async;
+ gint priority;
+ GimpRunAsyncFunc func;
+ gpointer user_data;
+ GDestroyNotify user_data_destroy_func;
+} GimpParallelRunAsyncTask;
+
+typedef struct
+{
+ GThread *thread;
+
+ gboolean quit;
+
+ GimpAsync *current_async;
+} GimpParallelRunAsyncThread;
+
+
+/* local function prototypes */
+
+static void gimp_parallel_notify_num_processors (GimpGeglConfig *config);
+
+static void gimp_parallel_set_n_threads (gint n_threads,
+ gboolean finish_tasks);
+
+static void gimp_parallel_run_async_set_n_threads (gint n_threads,
+ gboolean finish_tasks);
+static gpointer gimp_parallel_run_async_thread_func (GimpParallelRunAsyncThread *thread);
+static void gimp_parallel_run_async_enqueue_task (GimpParallelRunAsyncTask *task);
+static GimpParallelRunAsyncTask * gimp_parallel_run_async_dequeue_task (void);
+static gboolean gimp_parallel_run_async_execute_task (GimpParallelRunAsyncTask *task);
+static void gimp_parallel_run_async_abort_task (GimpParallelRunAsyncTask *task);
+static void gimp_parallel_run_async_cancel (GimpAsync *async);
+static void gimp_parallel_run_async_waiting (GimpAsync *async);
+
+
+/* local variables */
+
+static gint gimp_parallel_run_async_n_threads = 0;
+static GimpParallelRunAsyncThread gimp_parallel_run_async_threads[GIMP_PARALLEL_RUN_ASYNC_MAX_THREADS];
+
+static GMutex gimp_parallel_run_async_mutex;
+static GCond gimp_parallel_run_async_cond;
+static GQueue gimp_parallel_run_async_queue = G_QUEUE_INIT;
+
+
+/* public functions */
+
+
+void
+gimp_parallel_init (Gimp *gimp)
+{
+ GimpGeglConfig *config;
+
+ g_return_if_fail (GIMP_IS_GIMP (gimp));
+
+ config = GIMP_GEGL_CONFIG (gimp->config);
+
+ g_signal_connect (config, "notify::num-processors",
+ G_CALLBACK (gimp_parallel_notify_num_processors),
+ NULL);
+
+ gimp_parallel_notify_num_processors (config);
+}
+
+void
+gimp_parallel_exit (Gimp *gimp)
+{
+ g_return_if_fail (GIMP_IS_GIMP (gimp));
+
+ g_signal_handlers_disconnect_by_func (gimp->config,
+ (gpointer) gimp_parallel_notify_num_processors,
+ NULL);
+
+ /* stop all threads */
+ gimp_parallel_set_n_threads (0, /* finish_tasks = */ FALSE);
+}
+
+GimpAsync *
+gimp_parallel_run_async (GimpRunAsyncFunc func,
+ gpointer user_data)
+{
+ return gimp_parallel_run_async_full (0, func, user_data, NULL);
+}
+
+GimpAsync *
+gimp_parallel_run_async_full (gint priority,
+ GimpRunAsyncFunc func,
+ gpointer user_data,
+ GDestroyNotify user_data_destroy_func)
+{
+ GimpAsync *async;
+ GimpParallelRunAsyncTask *task;
+
+ g_return_val_if_fail (func != NULL, NULL);
+
+ async = gimp_async_new ();
+
+ task = g_slice_new (GimpParallelRunAsyncTask);
+
+ task->async = GIMP_ASYNC (g_object_ref (async));
+ task->priority = priority;
+ task->func = func;
+ task->user_data = user_data;
+ task->user_data_destroy_func = user_data_destroy_func;
+
+ if (gimp_parallel_run_async_n_threads > 0)
+ {
+ g_signal_connect_after (async, "cancel",
+ G_CALLBACK (gimp_parallel_run_async_cancel),
+ NULL);
+ g_signal_connect_after (async, "waiting",
+ G_CALLBACK (gimp_parallel_run_async_waiting),
+ NULL);
+
+ g_mutex_lock (&gimp_parallel_run_async_mutex);
+
+ gimp_parallel_run_async_enqueue_task (task);
+
+ g_cond_signal (&gimp_parallel_run_async_cond);
+
+ g_mutex_unlock (&gimp_parallel_run_async_mutex);
+ }
+ else
+ {
+ while (gimp_parallel_run_async_execute_task (task));
+ }
+
+ return async;
+}
+
+GimpAsync *
+gimp_parallel_run_async_independent (GimpRunAsyncFunc func,
+ gpointer user_data)
+{
+ return gimp_parallel_run_async_independent_full (0, func, user_data);
+}
+
+GimpAsync *
+gimp_parallel_run_async_independent_full (gint priority,
+ GimpRunAsyncFunc func,
+ gpointer user_data)
+{
+ GimpAsync *async;
+ GimpParallelRunAsyncTask *task;
+ GThread *thread;
+
+ g_return_val_if_fail (func != NULL, NULL);
+
+ async = gimp_async_new ();
+
+ task = g_slice_new0 (GimpParallelRunAsyncTask);
+
+ task->async = GIMP_ASYNC (g_object_ref (async));
+ task->priority = priority;
+ task->func = func;
+ task->user_data = user_data;
+
+ thread = g_thread_new (
+ "async-ind",
+ [] (gpointer data) -> gpointer
+ {
+ GimpParallelRunAsyncTask *task = (GimpParallelRunAsyncTask *) data;
+
+ /* adjust the thread's priority */
+#if defined (G_OS_WIN32)
+ if (task->priority < 0)
+ {
+ SetThreadPriority (GetCurrentThread (), THREAD_PRIORITY_ABOVE_NORMAL);
+ }
+ else if (task->priority > 0)
+ {
+ SetThreadPriority (GetCurrentThread (), THREAD_MODE_BACKGROUND_BEGIN);
+ }
+#elif defined (HAVE_UNISTD_H) && defined (__gnu_linux__)
+ if (task->priority)
+ {
+ (nice (task->priority) != -1);
+ /* ^-- avoid "unused result" warning */
+ }
+#endif
+
+ while (gimp_parallel_run_async_execute_task (task));
+
+ return NULL;
+ },
+ task);
+
+ gimp_async_add_callback (async,
+ [] (GimpAsync *async,
+ gpointer thread)
+ {
+ g_thread_join ((GThread *) thread);
+ },
+ thread);
+
+ return async;
+}
+
+
+/* private functions */
+
+
+static void
+gimp_parallel_notify_num_processors (GimpGeglConfig *config)
+{
+ gimp_parallel_set_n_threads (config->num_processors,
+ /* finish_tasks = */ TRUE);
+}
+
+static void
+gimp_parallel_set_n_threads (gint n_threads,
+ gboolean finish_tasks)
+{
+ gimp_parallel_run_async_set_n_threads (n_threads, finish_tasks);
+}
+
+static void
+gimp_parallel_run_async_set_n_threads (gint n_threads,
+ gboolean finish_tasks)
+{
+ gint i;
+
+ n_threads = CLAMP (n_threads, 0, GIMP_PARALLEL_RUN_ASYNC_MAX_THREADS);
+
+ if (n_threads > gimp_parallel_run_async_n_threads) /* need more threads */
+ {
+ for (i = gimp_parallel_run_async_n_threads; i < n_threads; i++)
+ {
+ GimpParallelRunAsyncThread *thread =
+ &gimp_parallel_run_async_threads[i];
+
+ thread->quit = FALSE;
+
+ thread->thread = g_thread_new (
+ "async",
+ (GThreadFunc) gimp_parallel_run_async_thread_func,
+ thread);
+ }
+ }
+ else if (n_threads < gimp_parallel_run_async_n_threads) /* need less threads */
+ {
+ g_mutex_lock (&gimp_parallel_run_async_mutex);
+
+ for (i = n_threads; i < gimp_parallel_run_async_n_threads; i++)
+ {
+ GimpParallelRunAsyncThread *thread =
+ &gimp_parallel_run_async_threads[i];
+
+ thread->quit = TRUE;
+
+ if (thread->current_async && ! finish_tasks)
+ gimp_cancelable_cancel (GIMP_CANCELABLE (thread->current_async));
+ }
+
+ g_cond_broadcast (&gimp_parallel_run_async_cond);
+
+ g_mutex_unlock (&gimp_parallel_run_async_mutex);
+
+ for (i = n_threads; i < gimp_parallel_run_async_n_threads; i++)
+ {
+ GimpParallelRunAsyncThread *thread =
+ &gimp_parallel_run_async_threads[i];
+
+ g_thread_join (thread->thread);
+ }
+ }
+
+ gimp_parallel_run_async_n_threads = n_threads;
+
+ if (n_threads == 0)
+ {
+ GimpParallelRunAsyncTask *task;
+
+ /* finish remaining tasks */
+ while ((task = gimp_parallel_run_async_dequeue_task ()))
+ {
+ if (finish_tasks)
+ while (gimp_parallel_run_async_execute_task (task));
+ else
+ gimp_parallel_run_async_abort_task (task);
+ }
+ }
+}
+
+static gpointer
+gimp_parallel_run_async_thread_func (GimpParallelRunAsyncThread *thread)
+{
+ g_mutex_lock (&gimp_parallel_run_async_mutex);
+
+ while (TRUE)
+ {
+ GimpParallelRunAsyncTask *task;
+
+ while (! thread->quit &&
+ (task = gimp_parallel_run_async_dequeue_task ()))
+ {
+ gboolean resume;
+
+ thread->current_async = GIMP_ASYNC (g_object_ref (task->async));
+
+ do
+ {
+ g_mutex_unlock (&gimp_parallel_run_async_mutex);
+
+ resume = gimp_parallel_run_async_execute_task (task);
+
+ g_mutex_lock (&gimp_parallel_run_async_mutex);
+ }
+ while (resume &&
+ (g_queue_is_empty (&gimp_parallel_run_async_queue) ||
+ task->priority <
+ ((GimpParallelRunAsyncTask *)
+ g_queue_peek_head (
+ &gimp_parallel_run_async_queue))->priority));
+
+ g_clear_object (&thread->current_async);
+
+ if (resume)
+ gimp_parallel_run_async_enqueue_task (task);
+ }
+
+ if (thread->quit)
+ break;
+
+ g_cond_wait (&gimp_parallel_run_async_cond,
+ &gimp_parallel_run_async_mutex);
+ }
+
+ g_mutex_unlock (&gimp_parallel_run_async_mutex);
+
+ return NULL;
+}
+
+static void
+gimp_parallel_run_async_enqueue_task (GimpParallelRunAsyncTask *task)
+{
+ GList *link;
+ GList *iter;
+
+ if (gimp_async_is_canceled (task->async))
+ {
+ gimp_parallel_run_async_abort_task (task);
+
+ return;
+ }
+
+ link = g_list_alloc ();
+ link->data = task;
+
+ g_object_set_data (G_OBJECT (task->async),
+ "gimp-parallel-run-async-link", link);
+
+ for (iter = g_queue_peek_tail_link (&gimp_parallel_run_async_queue);
+ iter;
+ iter = g_list_previous (iter))
+ {
+ GimpParallelRunAsyncTask *other_task =
+ (GimpParallelRunAsyncTask *) iter->data;
+
+ if (other_task->priority <= task->priority)
+ break;
+ }
+
+ if (iter)
+ {
+ link->prev = iter;
+ link->next = iter->next;
+
+ iter->next = link;
+
+ if (link->next)
+ link->next->prev = link;
+ else
+ gimp_parallel_run_async_queue.tail = link;
+
+ gimp_parallel_run_async_queue.length++;
+ }
+ else
+ {
+ g_queue_push_head_link (&gimp_parallel_run_async_queue, link);
+ }
+}
+
+static GimpParallelRunAsyncTask *
+gimp_parallel_run_async_dequeue_task (void)
+{
+ GimpParallelRunAsyncTask *task;
+
+ task = (GimpParallelRunAsyncTask *) g_queue_pop_head (
+ &gimp_parallel_run_async_queue);
+
+ if (task)
+ {
+ g_object_set_data (G_OBJECT (task->async),
+ "gimp-parallel-run-async-link", NULL);
+ }
+
+ return task;
+}
+
+static gboolean
+gimp_parallel_run_async_execute_task (GimpParallelRunAsyncTask *task)
+{
+ if (gimp_async_is_canceled (task->async))
+ {
+ gimp_parallel_run_async_abort_task (task);
+
+ return FALSE;
+ }
+
+ task->func (task->async, task->user_data);
+
+ if (gimp_async_is_stopped (task->async))
+ {
+ g_object_unref (task->async);
+
+ g_slice_free (GimpParallelRunAsyncTask, task);
+
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void
+gimp_parallel_run_async_abort_task (GimpParallelRunAsyncTask *task)
+{
+ if (task->user_data && task->user_data_destroy_func)
+ task->user_data_destroy_func (task->user_data);
+
+ gimp_async_abort (task->async);
+
+ g_object_unref (task->async);
+
+ g_slice_free (GimpParallelRunAsyncTask, task);
+}
+
+static void
+gimp_parallel_run_async_cancel (GimpAsync *async)
+{
+ GList *link;
+ GimpParallelRunAsyncTask *task = NULL;
+
+ link = (GList *) g_object_get_data (G_OBJECT (async),
+ "gimp-parallel-run-async-link");
+
+ if (! link)
+ return;
+
+ g_mutex_lock (&gimp_parallel_run_async_mutex);
+
+ link = (GList *) g_object_get_data (G_OBJECT (async),
+ "gimp-parallel-run-async-link");
+
+ if (link)
+ {
+ g_object_set_data (G_OBJECT (async),
+ "gimp-parallel-run-async-link", NULL);
+
+ task = (GimpParallelRunAsyncTask *) link->data;
+
+ g_queue_delete_link (&gimp_parallel_run_async_queue, link);
+ }
+
+ g_mutex_unlock (&gimp_parallel_run_async_mutex);
+
+ if (task)
+ gimp_parallel_run_async_abort_task (task);
+}
+
+static void
+gimp_parallel_run_async_waiting (GimpAsync *async)
+{
+ GList *link;
+
+ link = (GList *) g_object_get_data (G_OBJECT (async),
+ "gimp-parallel-run-async-link");
+
+ if (! link)
+ return;
+
+ g_mutex_lock (&gimp_parallel_run_async_mutex);
+
+ link = (GList *) g_object_get_data (G_OBJECT (async),
+ "gimp-parallel-run-async-link");
+
+ if (link)
+ {
+ GimpParallelRunAsyncTask *task = (GimpParallelRunAsyncTask *) link->data;
+
+ task->priority = G_MININT;
+
+ g_queue_unlink (&gimp_parallel_run_async_queue, link);
+ g_queue_push_head_link (&gimp_parallel_run_async_queue, link);
+ }
+
+ g_mutex_unlock (&gimp_parallel_run_async_mutex);
+}
+
+} /* extern "C" */
diff --git a/app/core/gimp-parallel.h b/app/core/gimp-parallel.h
new file mode 100644
index 0000000..76abc1d
--- /dev/null
+++ b/app/core/gimp-parallel.h
@@ -0,0 +1,157 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimp-parallel.h
+ * Copyright (C) 2018 Ell
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_PARALLEL_H__
+#define __GIMP_PARALLEL_H__
+
+
+void gimp_parallel_init (Gimp *gimp);
+void gimp_parallel_exit (Gimp *gimp);
+
+GimpAsync * gimp_parallel_run_async (GimpRunAsyncFunc func,
+ gpointer user_data);
+GimpAsync * gimp_parallel_run_async_full (gint priority,
+ GimpRunAsyncFunc func,
+ gpointer user_data,
+ GDestroyNotify user_data_destroy_func);
+GimpAsync * gimp_parallel_run_async_independent (GimpRunAsyncFunc func,
+ gpointer user_data);
+GimpAsync * gimp_parallel_run_async_independent_full (gint priority,
+ GimpRunAsyncFunc func,
+ gpointer user_data);
+
+
+#ifdef __cplusplus
+
+extern "C++"
+{
+
+#include <new>
+
+template <class RunAsyncFunc>
+inline GimpAsync *
+gimp_parallel_run_async (RunAsyncFunc func)
+{
+ RunAsyncFunc *func_copy = g_new (RunAsyncFunc, 1);
+
+ new (func_copy) RunAsyncFunc (func);
+
+ return gimp_parallel_run_async_full (0,
+ [] (GimpAsync *async,
+ gpointer user_data)
+ {
+ RunAsyncFunc *func_copy =
+ (RunAsyncFunc *) user_data;
+
+ (*func_copy) (async);
+
+ func_copy->~RunAsyncFunc ();
+ g_free (func_copy);
+ },
+ func_copy,
+ [] (gpointer user_data)
+ {
+ RunAsyncFunc *func_copy =
+ (RunAsyncFunc *) user_data;
+
+ func_copy->~RunAsyncFunc ();
+ g_free (func_copy);
+ });
+}
+
+template <class RunAsyncFunc,
+ class DestroyFunc>
+inline GimpAsync *
+gimp_parallel_run_async_full (gint priority,
+ RunAsyncFunc func,
+ DestroyFunc destroy_func)
+{
+ typedef struct
+ {
+ RunAsyncFunc func;
+ DestroyFunc destroy_func;
+ } Funcs;
+
+ Funcs *funcs_copy = g_new (Funcs, 1);
+
+ new (funcs_copy) Funcs {func, destroy_func};
+
+ return gimp_parallel_run_async_full (priority,
+ [] (GimpAsync *async,
+ gpointer user_data)
+ {
+ Funcs *funcs_copy =
+ (Funcs *) user_data;
+
+ funcs_copy->func (async);
+
+ funcs_copy->~Funcs ();
+ g_free (funcs_copy);
+ },
+ funcs_copy,
+ [] (gpointer user_data)
+ {
+ Funcs *funcs_copy =
+ (Funcs *) user_data;
+
+ funcs_copy->destroy_func ();
+
+ funcs_copy->~Funcs ();
+ g_free (funcs_copy);
+ });
+}
+
+template <class RunAsyncFunc>
+inline GimpAsync *
+gimp_parallel_run_async_independent_full (gint priority,
+ RunAsyncFunc func)
+{
+ RunAsyncFunc *func_copy = g_new (RunAsyncFunc, 1);
+
+ new (func_copy) RunAsyncFunc (func);
+
+ return gimp_parallel_run_async_independent_full (priority,
+ [] (GimpAsync *async,
+ gpointer user_data)
+ {
+ RunAsyncFunc *func_copy =
+ (RunAsyncFunc *) user_data;
+
+ (*func_copy) (async);
+
+ func_copy->~RunAsyncFunc ();
+ g_free (func_copy);
+ },
+ func_copy);
+}
+
+template <class RunAsyncFunc>
+inline GimpAsync *
+gimp_parallel_run_async_independent (RunAsyncFunc func)
+{
+ return gimp_parallel_run_async_independent_full (0, func);
+}
+
+}
+
+#endif /* __cplusplus */
+
+
+#endif /* __GIMP_PARALLEL_H__ */
diff --git a/app/core/gimp-parasites.c b/app/core/gimp-parasites.c
new file mode 100644
index 0000000..e781a54
--- /dev/null
+++ b/app/core/gimp-parasites.c
@@ -0,0 +1,170 @@
+/* gimpparasite.c: Copyright 1998 Jay Cox <jaycox@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gio/gio.h>
+
+#include "libgimpbase/gimpbase.h"
+#include "libgimpconfig/gimpconfig.h"
+
+#include "core-types.h"
+
+#include "gimp.h"
+#include "gimp-parasites.h"
+#include "gimpparasitelist.h"
+
+
+gboolean
+gimp_parasite_validate (Gimp *gimp,
+ const GimpParasite *parasite,
+ GError **error)
+{
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), FALSE);
+ g_return_val_if_fail (parasite != NULL, FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ return TRUE;
+}
+
+void
+gimp_parasite_attach (Gimp *gimp,
+ const GimpParasite *parasite)
+{
+ g_return_if_fail (GIMP_IS_GIMP (gimp));
+ g_return_if_fail (parasite != NULL);
+
+ gimp_parasite_list_add (gimp->parasites, parasite);
+}
+
+void
+gimp_parasite_detach (Gimp *gimp,
+ const gchar *name)
+{
+ g_return_if_fail (GIMP_IS_GIMP (gimp));
+ g_return_if_fail (name != NULL);
+
+ gimp_parasite_list_remove (gimp->parasites, name);
+}
+
+const GimpParasite *
+gimp_parasite_find (Gimp *gimp,
+ const gchar *name)
+{
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
+ g_return_val_if_fail (name != NULL, NULL);
+
+ return gimp_parasite_list_find (gimp->parasites, name);
+}
+
+static void
+list_func (const gchar *key,
+ GimpParasite *parasite,
+ gchar ***current)
+{
+ *(*current)++ = g_strdup (key);
+}
+
+gchar **
+gimp_parasite_list (Gimp *gimp,
+ gint *count)
+{
+ gchar **list;
+ gchar **current;
+
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
+ g_return_val_if_fail (count != NULL, NULL);
+
+ *count = gimp_parasite_list_length (gimp->parasites);
+
+ list = current = g_new (gchar *, *count);
+
+ gimp_parasite_list_foreach (gimp->parasites, (GHFunc) list_func, &current);
+
+ return list;
+}
+
+
+/* FIXME: this doesn't belong here */
+
+void
+gimp_parasite_shift_parent (GimpParasite *parasite)
+{
+ g_return_if_fail (parasite != NULL);
+
+ parasite->flags = (parasite->flags >> 8);
+}
+
+
+/* parasiterc functions */
+
+void
+gimp_parasiterc_load (Gimp *gimp)
+{
+ GFile *file;
+ GError *error = NULL;
+
+ g_return_if_fail (GIMP_IS_GIMP (gimp));
+
+ file = gimp_directory_file ("parasiterc", NULL);
+
+ if (gimp->be_verbose)
+ g_print ("Parsing '%s'\n", gimp_file_get_utf8_name (file));
+
+ if (! gimp_config_deserialize_gfile (GIMP_CONFIG (gimp->parasites),
+ file, NULL, &error))
+ {
+ if (error->code != GIMP_CONFIG_ERROR_OPEN_ENOENT)
+ gimp_message_literal (gimp, NULL, GIMP_MESSAGE_ERROR, error->message);
+
+ g_error_free (error);
+ }
+
+ g_object_unref (file);
+}
+
+void
+gimp_parasiterc_save (Gimp *gimp)
+{
+ const gchar *header =
+ "GIMP parasiterc\n"
+ "\n"
+ "This file will be entirely rewritten each time you exit.";
+ const gchar *footer =
+ "end of parasiterc";
+
+ GFile *file;
+ GError *error = NULL;
+
+ g_return_if_fail (GIMP_IS_GIMP (gimp));
+ g_return_if_fail (GIMP_IS_PARASITE_LIST (gimp->parasites));
+
+ file = gimp_directory_file ("parasiterc", NULL);
+
+ if (gimp->be_verbose)
+ g_print ("Writing '%s'\n", gimp_file_get_utf8_name (file));
+
+ if (! gimp_config_serialize_to_gfile (GIMP_CONFIG (gimp->parasites),
+ file,
+ header, footer, NULL,
+ &error))
+ {
+ gimp_message_literal (gimp, NULL, GIMP_MESSAGE_ERROR, error->message);
+ g_error_free (error);
+ }
+
+ g_object_unref (file);
+}
diff --git a/app/core/gimp-parasites.h b/app/core/gimp-parasites.h
new file mode 100644
index 0000000..06d38c8
--- /dev/null
+++ b/app/core/gimp-parasites.h
@@ -0,0 +1,41 @@
+/* gimpparasite.h: Copyright 1998 Jay Cox <jaycox@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_PARASITES_H__
+#define __GIMP_PARASITES_H__
+
+
+/* some wrappers to access gimp->parasites, mainly for the PDB */
+
+gboolean gimp_parasite_validate (Gimp *gimp,
+ const GimpParasite *parasite,
+ GError **error);
+void gimp_parasite_attach (Gimp *gimp,
+ const GimpParasite *parasite);
+void gimp_parasite_detach (Gimp *gimp,
+ const gchar *name);
+const GimpParasite * gimp_parasite_find (Gimp *gimp,
+ const gchar *name);
+gchar ** gimp_parasite_list (Gimp *gimp,
+ gint *count);
+
+void gimp_parasite_shift_parent (GimpParasite *parasite);
+
+void gimp_parasiterc_load (Gimp *gimp);
+void gimp_parasiterc_save (Gimp *gimp);
+
+
+#endif /* __GIMP_PARASITES_H__ */
diff --git a/app/core/gimp-spawn.c b/app/core/gimp-spawn.c
new file mode 100644
index 0000000..bfa08e8
--- /dev/null
+++ b/app/core/gimp-spawn.c
@@ -0,0 +1,250 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimp-spawn.c
+ * Copyright (C) 2018 Ell
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <glib-object.h>
+
+#ifdef HAVE_VFORK
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <errno.h>
+#endif
+
+#ifdef HAVE_FCNTL_H
+#include <fcntl.h>
+#endif
+
+#ifdef G_OS_WIN32
+#include <windows.h>
+#include <io.h>
+#endif
+
+#include "core-types.h"
+
+#include "gimp-spawn.h"
+
+#include "gimp-intl.h"
+
+
+#ifdef HAVE_VFORK
+
+/* copied from glib */
+static gint
+exec_err_to_g_error (gint en)
+{
+ switch (en)
+ {
+#ifdef EACCES
+ case EACCES:
+ return G_SPAWN_ERROR_ACCES;
+ break;
+#endif
+
+#ifdef EPERM
+ case EPERM:
+ return G_SPAWN_ERROR_PERM;
+ break;
+#endif
+
+#ifdef E2BIG
+ case E2BIG:
+ return G_SPAWN_ERROR_TOO_BIG;
+ break;
+#endif
+
+#ifdef ENOEXEC
+ case ENOEXEC:
+ return G_SPAWN_ERROR_NOEXEC;
+ break;
+#endif
+
+#ifdef ENAMETOOLONG
+ case ENAMETOOLONG:
+ return G_SPAWN_ERROR_NAMETOOLONG;
+ break;
+#endif
+
+#ifdef ENOENT
+ case ENOENT:
+ return G_SPAWN_ERROR_NOENT;
+ break;
+#endif
+
+#ifdef ENOMEM
+ case ENOMEM:
+ return G_SPAWN_ERROR_NOMEM;
+ break;
+#endif
+
+#ifdef ENOTDIR
+ case ENOTDIR:
+ return G_SPAWN_ERROR_NOTDIR;
+ break;
+#endif
+
+#ifdef ELOOP
+ case ELOOP:
+ return G_SPAWN_ERROR_LOOP;
+ break;
+#endif
+
+#ifdef ETXTBUSY
+ case ETXTBUSY:
+ return G_SPAWN_ERROR_TXTBUSY;
+ break;
+#endif
+
+#ifdef EIO
+ case EIO:
+ return G_SPAWN_ERROR_IO;
+ break;
+#endif
+
+#ifdef ENFILE
+ case ENFILE:
+ return G_SPAWN_ERROR_NFILE;
+ break;
+#endif
+
+#ifdef EMFILE
+ case EMFILE:
+ return G_SPAWN_ERROR_MFILE;
+ break;
+#endif
+
+#ifdef EINVAL
+ case EINVAL:
+ return G_SPAWN_ERROR_INVAL;
+ break;
+#endif
+
+#ifdef EISDIR
+ case EISDIR:
+ return G_SPAWN_ERROR_ISDIR;
+ break;
+#endif
+
+#ifdef ELIBBAD
+ case ELIBBAD:
+ return G_SPAWN_ERROR_LIBBAD;
+ break;
+#endif
+
+ default:
+ return G_SPAWN_ERROR_FAILED;
+ break;
+ }
+}
+
+#endif /* HAVE_VFORK */
+
+gboolean
+gimp_spawn_async (gchar **argv,
+ gchar **envp,
+ GSpawnFlags flags,
+ GPid *child_pid,
+ GError **error)
+{
+ g_return_val_if_fail (argv != NULL, FALSE);
+ g_return_val_if_fail (argv[0] != NULL, FALSE);
+
+#ifdef HAVE_VFORK
+ if (flags == (G_SPAWN_LEAVE_DESCRIPTORS_OPEN |
+ G_SPAWN_DO_NOT_REAP_CHILD |
+ G_SPAWN_CHILD_INHERITS_STDIN))
+ {
+ pid_t pid;
+
+ pid = vfork ();
+
+ if (pid < 0)
+ {
+ gint errsv = errno;
+
+ g_set_error (error,
+ G_SPAWN_ERROR,
+ G_SPAWN_ERROR_FORK,
+ _("Failed to fork (%s)"),
+ g_strerror (errsv));
+
+ return FALSE;
+ }
+ else if (pid == 0)
+ {
+ if (envp)
+ execve (argv[0], argv, envp);
+ else
+ execv (argv[0], argv);
+
+ _exit (errno);
+ }
+ else
+ {
+ int status = -1;
+ pid_t result;
+
+ result = waitpid (pid, &status, WNOHANG);
+
+ if (result)
+ {
+ if (result < 0)
+ {
+ g_warning ("waitpid() should not fail in "
+ "gimp_spawn_async()");
+ }
+
+ if (WIFEXITED (status))
+ status = WEXITSTATUS (status);
+ else
+ status = -1;
+
+ g_set_error (error,
+ G_SPAWN_ERROR,
+ exec_err_to_g_error (status),
+ _("Failed to execute child process “%s” (%s)"),
+ argv[0],
+ g_strerror (status));
+
+ return FALSE;
+ }
+
+ if (child_pid) *child_pid = pid;
+
+ return TRUE;
+ }
+ }
+#endif /* HAVE_VFORK */
+
+ return g_spawn_async (NULL, argv, envp, flags, NULL, NULL, child_pid, error);
+}
+
+void
+gimp_spawn_set_cloexec (gint fd)
+{
+#if defined (G_OS_WIN32)
+ SetHandleInformation ((HANDLE) _get_osfhandle (fd), HANDLE_FLAG_INHERIT, 0);
+#elif defined (HAVE_FCNTL_H)
+ fcntl (fd, F_SETFD, fcntl (fd, F_GETFD, 0) | FD_CLOEXEC);
+#elif defined (__GNUC__)
+#warning gimp_spawn_set_cloexec() is not implemented for the target platform
+#endif
+}
diff --git a/app/core/gimp-spawn.h b/app/core/gimp-spawn.h
new file mode 100644
index 0000000..f81cf7d
--- /dev/null
+++ b/app/core/gimp-spawn.h
@@ -0,0 +1,34 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimp-spawn.h
+ * Copyright (C) 2018 Ell
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_SPAWN_H__
+#define __GIMP_SPAWN_H__
+
+
+gboolean gimp_spawn_async (gchar **argv,
+ gchar **envp,
+ GSpawnFlags flags,
+ GPid *child_pid,
+ GError **error);
+
+void gimp_spawn_set_cloexec (gint fd);
+
+
+#endif /* __GIMP_SPAWN_H__ */
diff --git a/app/core/gimp-tags.c b/app/core/gimp-tags.c
new file mode 100644
index 0000000..5efedb9
--- /dev/null
+++ b/app/core/gimp-tags.c
@@ -0,0 +1,271 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimp-tags.c
+ * Copyright (C) 2009 Aurimas Juška <aurisj@svn.gnome.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gio/gio.h>
+#include <gegl.h>
+
+#include "libgimpbase/gimpbase.h"
+#include "libgimpmath/gimpmath.h"
+
+#include "core-types.h"
+
+#include "config/gimpxmlparser.h"
+
+#include "gimp-utils.h"
+#include "gimp-tags.h"
+
+#include "gimp-intl.h"
+
+
+#define GIMP_TAGS_FILE "tags.xml"
+
+typedef struct
+{
+ const gchar *locale;
+ GString *buf;
+ gboolean locale_matches;
+} GimpTagsInstaller;
+
+
+static void gimp_tags_installer_load_start_element (GMarkupParseContext *context,
+ const gchar *element_name,
+ const gchar **attribute_names,
+ const gchar **attribute_values,
+ gpointer user_data,
+ GError **error);
+static void gimp_tags_installer_load_end_element (GMarkupParseContext *context,
+ const gchar *element_name,
+ gpointer user_data,
+ GError **error);
+static void gimp_tags_installer_load_text (GMarkupParseContext *context,
+ const gchar *text,
+ gsize text_len,
+ gpointer user_data,
+ GError **error);
+static const gchar* attribute_name_to_value (const gchar **attribute_names,
+ const gchar **attribute_values,
+ const gchar *name);
+
+
+gboolean
+gimp_tags_user_install (void)
+{
+ GFile *file;
+ GOutputStream *output;
+ GMarkupParser markup_parser;
+ GimpXmlParser *xml_parser;
+ const char *tags_locale;
+ GimpTagsInstaller tags_installer = { 0, };
+ GError *error = NULL;
+ gboolean result = TRUE;
+
+ /* This is a special string to specify the language identifier to
+ * look for in the gimp-tags-default.xml file. Please translate the
+ * C in it according to the name of the po file used for
+ * gimp-tags-default.xml. E.g. lithuanian for the translation,
+ * that would be "tags-locale:lt".
+ */
+ tags_locale = _("tags-locale:C");
+
+ if (g_str_has_prefix (tags_locale, "tags-locale:"))
+ {
+ tags_locale += strlen ("tags-locale:");
+
+ if (*tags_locale && *tags_locale != 'C')
+ tags_installer.locale = tags_locale;
+ }
+ else
+ {
+ g_warning ("Wrong translation for 'tags-locale:', fix the translation!");
+ }
+
+ tags_installer.buf = g_string_new (NULL);
+
+ g_string_append (tags_installer.buf, "<?xml version='1.0' encoding='UTF-8'?>\n");
+ g_string_append (tags_installer.buf, "<tags>\n");
+
+ markup_parser.start_element = gimp_tags_installer_load_start_element;
+ markup_parser.end_element = gimp_tags_installer_load_end_element;
+ markup_parser.text = gimp_tags_installer_load_text;
+ markup_parser.passthrough = NULL;
+ markup_parser.error = NULL;
+
+ xml_parser = gimp_xml_parser_new (&markup_parser, &tags_installer);
+
+ file = gimp_data_directory_file ("tags", "gimp-tags-default.xml", NULL);
+ result = gimp_xml_parser_parse_gfile (xml_parser, file, &error);
+ g_object_unref (file);
+
+ gimp_xml_parser_free (xml_parser);
+
+ if (! result)
+ {
+ g_string_free (tags_installer.buf, TRUE);
+ return FALSE;
+ }
+
+ g_string_append (tags_installer.buf, "\n</tags>\n");
+
+ file = gimp_directory_file (GIMP_TAGS_FILE, NULL);
+
+ output = G_OUTPUT_STREAM (g_file_replace (file,
+ NULL, FALSE, G_FILE_CREATE_NONE,
+ NULL, &error));
+ if (! output)
+ {
+ g_printerr ("%s\n", error->message);
+ result = FALSE;
+ }
+ else if (! g_output_stream_write_all (output,
+ tags_installer.buf->str,
+ tags_installer.buf->len,
+ NULL, NULL, &error))
+ {
+ GCancellable *cancellable = g_cancellable_new ();
+
+ g_printerr (_("Error writing '%s': %s"),
+ gimp_file_get_utf8_name (file), error->message);
+ result = FALSE;
+
+ /* Cancel the overwrite initiated by g_file_replace(). */
+ g_cancellable_cancel (cancellable);
+ g_output_stream_close (output, cancellable, NULL);
+ g_object_unref (cancellable);
+ }
+ else if (! g_output_stream_close (output, NULL, &error))
+ {
+ g_printerr (_("Error closing '%s': %s"),
+ gimp_file_get_utf8_name (file), error->message);
+ result = FALSE;
+ }
+
+ if (output)
+ g_object_unref (output);
+
+ g_clear_error (&error);
+ g_object_unref (file);
+ g_string_free (tags_installer.buf, TRUE);
+
+ return result;
+}
+
+static void
+gimp_tags_installer_load_start_element (GMarkupParseContext *context,
+ const gchar *element_name,
+ const gchar **attribute_names,
+ const gchar **attribute_values,
+ gpointer user_data,
+ GError **error)
+{
+ GimpTagsInstaller *tags_installer = user_data;
+
+ if (! strcmp (element_name, "resource"))
+ {
+ g_string_append_printf (tags_installer->buf, "\n <resource");
+
+ while (*attribute_names)
+ {
+ g_string_append_printf (tags_installer->buf, " %s=\"%s\"",
+ *attribute_names, *attribute_values);
+
+ attribute_names++;
+ attribute_values++;
+ }
+
+ g_string_append_printf (tags_installer->buf, ">\n");
+ }
+ else if (! strcmp (element_name, "thetag"))
+ {
+ const char *current_locale;
+
+ current_locale = attribute_name_to_value (attribute_names, attribute_values,
+ "xml:lang");
+
+ if (current_locale && tags_installer->locale)
+ {
+ tags_installer->locale_matches = ! strcmp (current_locale,
+ tags_installer->locale);
+ }
+ else
+ {
+ tags_installer->locale_matches = (current_locale ==
+ tags_installer->locale);
+ }
+ }
+}
+
+static void
+gimp_tags_installer_load_end_element (GMarkupParseContext *context,
+ const gchar *element_name,
+ gpointer user_data,
+ GError **error)
+{
+ GimpTagsInstaller *tags_installer = user_data;
+
+ if (strcmp (element_name, "resource") == 0)
+ {
+ g_string_append (tags_installer->buf, " </resource>\n");
+ }
+}
+
+static void
+gimp_tags_installer_load_text (GMarkupParseContext *context,
+ const gchar *text,
+ gsize text_len,
+ gpointer user_data,
+ GError **error)
+{
+ GimpTagsInstaller *tags_installer = user_data;
+ const gchar *current_element;
+ gchar *tag_string;
+
+ current_element = g_markup_parse_context_get_element (context);
+
+ if (tags_installer->locale_matches &&
+ current_element &&
+ strcmp (current_element, "thetag") == 0)
+ {
+ tag_string = g_markup_escape_text (text, text_len);
+ g_string_append_printf (tags_installer->buf, " <tag>%s</tag>\n",
+ tag_string);
+ g_free (tag_string);
+ }
+}
+
+static const gchar *
+attribute_name_to_value (const gchar **attribute_names,
+ const gchar **attribute_values,
+ const gchar *name)
+{
+ while (*attribute_names)
+ {
+ if (! strcmp (*attribute_names, name))
+ {
+ return *attribute_values;
+ }
+
+ attribute_names++;
+ attribute_values++;
+ }
+
+ return NULL;
+}
diff --git a/app/core/gimp-tags.h b/app/core/gimp-tags.h
new file mode 100644
index 0000000..8f52dce
--- /dev/null
+++ b/app/core/gimp-tags.h
@@ -0,0 +1,25 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-1997 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_TAGS_H__
+#define __GIMP_TAGS_H__
+
+
+gboolean gimp_tags_user_install (void);
+
+
+#endif /* __GIMP_TAGS_H__ */
diff --git a/app/core/gimp-templates.c b/app/core/gimp-templates.c
new file mode 100644
index 0000000..ba0800d
--- /dev/null
+++ b/app/core/gimp-templates.c
@@ -0,0 +1,213 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-1997 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpbase/gimpbase.h"
+#include "libgimpconfig/gimpconfig.h"
+
+#include "core-types.h"
+
+#include "gimp.h"
+#include "gimp-templates.h"
+#include "gimplist.h"
+#include "gimptemplate.h"
+
+
+/* functions to load and save the gimp templates files */
+
+void
+gimp_templates_load (Gimp *gimp)
+{
+ GFile *file;
+ GError *error = NULL;
+
+ g_return_if_fail (GIMP_IS_GIMP (gimp));
+ g_return_if_fail (GIMP_IS_LIST (gimp->templates));
+
+ file = gimp_directory_file ("templaterc", NULL);
+
+ if (gimp->be_verbose)
+ g_print ("Parsing '%s'\n", gimp_file_get_utf8_name (file));
+
+ if (! gimp_config_deserialize_gfile (GIMP_CONFIG (gimp->templates),
+ file, NULL, &error))
+ {
+ if (error->code == GIMP_CONFIG_ERROR_OPEN_ENOENT)
+ {
+ g_clear_error (&error);
+ g_object_unref (file);
+
+ file = gimp_sysconf_directory_file ("templaterc", NULL);
+
+ if (! gimp_config_deserialize_gfile (GIMP_CONFIG (gimp->templates),
+ file, NULL, &error))
+ {
+ gimp_message_literal (gimp, NULL, GIMP_MESSAGE_ERROR,
+ error->message);
+ }
+ }
+ else
+ {
+ gimp_message_literal (gimp, NULL, GIMP_MESSAGE_ERROR, error->message);
+ }
+
+ g_clear_error (&error);
+ }
+
+ gimp_list_reverse (GIMP_LIST (gimp->templates));
+
+ g_object_unref (file);
+}
+
+void
+gimp_templates_save (Gimp *gimp)
+{
+ const gchar *header =
+ "GIMP templaterc\n"
+ "\n"
+ "This file will be entirely rewritten each time you exit.";
+ const gchar *footer =
+ "end of templaterc";
+
+ GFile *file;
+ GError *error = NULL;
+
+ g_return_if_fail (GIMP_IS_GIMP (gimp));
+ g_return_if_fail (GIMP_IS_LIST (gimp->templates));
+
+ file = gimp_directory_file ("templaterc", NULL);
+
+ if (gimp->be_verbose)
+ g_print ("Writing '%s'\n", gimp_file_get_utf8_name (file));
+
+ if (! gimp_config_serialize_to_gfile (GIMP_CONFIG (gimp->templates),
+ file,
+ header, footer, NULL,
+ &error))
+ {
+ gimp_message_literal (gimp, NULL, GIMP_MESSAGE_ERROR, error->message);
+ g_error_free (error);
+ }
+
+ g_object_unref (file);
+}
+
+
+/* just like gimp_list_get_child_by_name() but matches case-insensitive
+ * and dpi/ppi-insensitive
+ */
+static GimpObject *
+gimp_templates_migrate_get_child_by_name (GimpContainer *container,
+ const gchar *name)
+{
+ GimpList *list = GIMP_LIST (container);
+ GimpObject *retval = NULL;
+ GList *glist;
+
+ for (glist = list->queue->head; glist; glist = g_list_next (glist))
+ {
+ GimpObject *object = glist->data;
+ gchar *str1 = g_ascii_strdown (gimp_object_get_name (object), -1);
+ gchar *str2 = g_ascii_strdown (name, -1);
+
+ if (! strcmp (str1, str2))
+ {
+ retval = object;
+ }
+ else
+ {
+ gchar *dpi = strstr (str1, "dpi");
+
+ if (dpi)
+ {
+ memcpy (dpi, "ppi", 3);
+
+ g_print ("replaced: %s\n", str1);
+
+ if (! strcmp (str1, str2))
+ retval = object;
+ }
+ }
+
+ g_free (str1);
+ g_free (str2);
+ }
+
+ return retval;
+}
+
+/**
+ * gimp_templates_migrate:
+ * @olddir: the old user directory
+ *
+ * Migrating the templaterc from GIMP 2.0 to GIMP 2.2 needs this special
+ * hack since we changed the way that units are handled. This function
+ * merges the user's templaterc with the systemwide templaterc. The goal
+ * is to replace the unit for a couple of default templates with "pixels".
+ **/
+void
+gimp_templates_migrate (const gchar *olddir)
+{
+ GimpContainer *templates = gimp_list_new (GIMP_TYPE_TEMPLATE, TRUE);
+ GFile *file = gimp_directory_file ("templaterc", NULL);
+
+ if (gimp_config_deserialize_gfile (GIMP_CONFIG (templates), file,
+ NULL, NULL))
+ {
+ GFile *sysconf_file;
+
+ sysconf_file = gimp_sysconf_directory_file ("templaterc", NULL);
+
+ if (olddir && (strstr (olddir, "2.0") || strstr (olddir, "2.2")))
+ {
+ /* We changed the spelling of a couple of template names:
+ *
+ * - from upper to lower case between 2.0 and 2.2
+ * - from "dpi" to "ppi" between 2.2 and 2.4
+ */
+ GimpContainerClass *class = GIMP_CONTAINER_GET_CLASS (templates);
+ gpointer func = class->get_child_by_name;
+
+ class->get_child_by_name = gimp_templates_migrate_get_child_by_name;
+
+ gimp_config_deserialize_gfile (GIMP_CONFIG (templates),
+ sysconf_file, NULL, NULL);
+
+ class->get_child_by_name = func;
+ }
+ else
+ {
+ gimp_config_deserialize_gfile (GIMP_CONFIG (templates),
+ sysconf_file, NULL, NULL);
+ }
+
+ g_object_unref (sysconf_file);
+
+ gimp_list_reverse (GIMP_LIST (templates));
+
+ gimp_config_serialize_to_gfile (GIMP_CONFIG (templates), file,
+ NULL, NULL, NULL, NULL);
+ }
+
+ g_object_unref (file);
+}
diff --git a/app/core/gimp-templates.h b/app/core/gimp-templates.h
new file mode 100644
index 0000000..3dc0fde
--- /dev/null
+++ b/app/core/gimp-templates.h
@@ -0,0 +1,28 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-1997 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_TEMPLATES_H__
+#define __GIMP_TEMPLATES_H__
+
+
+void gimp_templates_load (Gimp *gimp);
+void gimp_templates_save (Gimp *gimp);
+
+void gimp_templates_migrate (const gchar *olddir);
+
+
+#endif /* __GIMP_TEMPLATES_H__ */
diff --git a/app/core/gimp-transform-3d-utils.c b/app/core/gimp-transform-3d-utils.c
new file mode 100644
index 0000000..5ea9f81
--- /dev/null
+++ b/app/core/gimp-transform-3d-utils.c
@@ -0,0 +1,359 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimp-3d-transform-utils.c
+ * Copyright (C) 2019 Ell
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <glib-object.h>
+
+#include "libgimpmath/gimpmath.h"
+
+#include "core-types.h"
+
+#include "gimp-transform-3d-utils.h"
+
+
+#define MIN_FOCAL_LENGTH 0.01
+
+
+gdouble
+gimp_transform_3d_angle_of_view_to_focal_length (gdouble angle_of_view,
+ gdouble width,
+ gdouble height)
+{
+ return MAX (width, height) / (2.0 * tan (angle_of_view / 2.0));
+}
+
+gdouble
+gimp_transform_3d_focal_length_to_angle_of_view (gdouble focal_length,
+ gdouble width,
+ gdouble height)
+{
+ return 2.0 * atan (MAX (width, height) / (2.0 * focal_length));
+}
+
+gint
+gimp_transform_3d_permutation_to_rotation_order (const gint permutation[3])
+{
+ if (permutation[1] == (permutation[0] + 1) % 3)
+ return permutation[0] << 1;
+ else
+ return (permutation[2] << 1) + 1;
+}
+
+void
+gimp_transform_3d_rotation_order_to_permutation (gint rotation_order,
+ gint permutation[3])
+{
+ gboolean reverse = rotation_order & 1;
+ gint shift = rotation_order >> 1;
+ gint i;
+
+ for (i = 0; i < 3; i++)
+ permutation[reverse ? 2 - i : i] = (i + shift) % 3;
+}
+
+gint
+gimp_transform_3d_rotation_order_reverse (gint rotation_order)
+{
+ return rotation_order ^ 1;
+}
+
+void
+gimp_transform_3d_vector3_rotate (GimpVector3 *vector,
+ const GimpVector3 *axis)
+{
+ GimpVector3 normal;
+ GimpVector3 proj;
+ GimpVector3 u, v;
+ gdouble angle;
+
+ angle = gimp_vector3_length (axis);
+
+ if (angle == 0.0)
+ return;
+
+ normal = gimp_vector3_mul_val (*axis, 1.0 / angle);
+
+ proj = gimp_vector3_mul_val (normal,
+ gimp_vector3_inner_product_val (*vector,
+ normal));
+
+ u = gimp_vector3_sub_val (*vector, proj);
+ v = gimp_vector3_cross_product_val (u, normal);
+
+ gimp_vector3_mul (&u, cos (angle));
+ gimp_vector3_mul (&v, sin (angle));
+
+ *vector = proj;
+
+ gimp_vector3_add (vector, vector, &u);
+ gimp_vector3_add (vector, vector, &v);
+}
+
+GimpVector3
+gimp_transform_3d_vector3_rotate_val (GimpVector3 vector,
+ GimpVector3 axis)
+{
+ gimp_transform_3d_vector3_rotate (&vector, &axis);
+
+ return vector;
+}
+
+void
+gimp_transform_3d_matrix3_to_matrix4 (const GimpMatrix3 *matrix3,
+ GimpMatrix4 *matrix4,
+ gint axis)
+{
+ gint i, j;
+ gint k, l;
+
+ for (i = 0; i < 4; i++)
+ {
+ if (i == axis)
+ {
+ matrix4->coeff[i][i] = 1.0;
+ }
+ else
+ {
+ matrix4->coeff[axis][i] = 0.0;
+ matrix4->coeff[i][axis] = 0.0;
+ }
+ }
+
+ for (i = 0; i < 3; i++)
+ {
+ k = i + (i >= axis);
+
+ for (j = 0; j < 3; j++)
+ {
+ l = j + (j >= axis);
+
+ matrix4->coeff[k][l] = matrix3->coeff[i][j];
+ }
+ }
+}
+
+void
+gimp_transform_3d_matrix4_to_matrix3 (const GimpMatrix4 *matrix4,
+ GimpMatrix3 *matrix3,
+ gint axis)
+{
+ gint i, j;
+ gint k, l;
+
+ for (i = 0; i < 3; i++)
+ {
+ k = i + (i >= axis);
+
+ for (j = 0; j < 3; j++)
+ {
+ l = j + (j >= axis);
+
+ matrix3->coeff[i][j] = matrix4->coeff[k][l];
+ }
+ }
+}
+
+void
+gimp_transform_3d_matrix4_translate (GimpMatrix4 *matrix,
+ gdouble x,
+ gdouble y,
+ gdouble z)
+{
+ gint i;
+
+ for (i = 0; i < 4; i++)
+ matrix->coeff[0][i] += x * matrix->coeff[3][i];
+
+ for (i = 0; i < 4; i++)
+ matrix->coeff[1][i] += y * matrix->coeff[3][i];
+
+ for (i = 0; i < 4; i++)
+ matrix->coeff[2][i] += z * matrix->coeff[3][i];
+}
+
+void
+gimp_transform_3d_matrix4_rotate (GimpMatrix4 *matrix,
+ const GimpVector3 *axis)
+{
+ GimpMatrix4 rotation;
+ GimpVector3 v;
+
+ v = gimp_transform_3d_vector3_rotate_val ((GimpVector3) {1.0, 0.0, 0.0},
+ *axis);
+
+ rotation.coeff[0][0] = v.x;
+ rotation.coeff[1][0] = v.y;
+ rotation.coeff[2][0] = v.z;
+ rotation.coeff[3][0] = 0.0;
+
+ v = gimp_transform_3d_vector3_rotate_val ((GimpVector3) {0.0, 1.0, 0.0},
+ *axis);
+
+ rotation.coeff[0][1] = v.x;
+ rotation.coeff[1][1] = v.y;
+ rotation.coeff[2][1] = v.z;
+ rotation.coeff[3][1] = 0.0;
+
+ v = gimp_transform_3d_vector3_rotate_val ((GimpVector3) {0.0, 0.0, 1.0},
+ *axis);
+
+ rotation.coeff[0][2] = v.x;
+ rotation.coeff[1][2] = v.y;
+ rotation.coeff[2][2] = v.z;
+ rotation.coeff[3][2] = 0.0;
+
+ rotation.coeff[0][3] = 0.0;
+ rotation.coeff[1][3] = 0.0;
+ rotation.coeff[2][3] = 0.0;
+ rotation.coeff[3][3] = 1.0;
+
+ gimp_matrix4_mult (&rotation, matrix);
+}
+
+void
+gimp_transform_3d_matrix4_rotate_standard (GimpMatrix4 *matrix,
+ gint axis,
+ gdouble angle)
+{
+ gdouble v[3] = {};
+
+ v[axis] = angle;
+
+ gimp_transform_3d_matrix4_rotate (matrix, &(GimpVector3) {v[0], v[1], v[2]});
+}
+
+void
+gimp_transform_3d_matrix4_rotate_euler (GimpMatrix4 *matrix,
+ gint rotation_order,
+ gdouble angle_x,
+ gdouble angle_y,
+ gdouble angle_z,
+ gdouble pivot_x,
+ gdouble pivot_y,
+ gdouble pivot_z)
+{
+ const gdouble angles[3] = {angle_x, angle_y, angle_z};
+ gint permutation[3];
+ gint i;
+
+ gimp_transform_3d_rotation_order_to_permutation (rotation_order, permutation);
+
+ gimp_transform_3d_matrix4_translate (matrix, -pivot_x, -pivot_y, -pivot_z);
+
+ for (i = 0; i < 3; i++)
+ {
+ gimp_transform_3d_matrix4_rotate_standard (matrix,
+ permutation[i],
+ angles[permutation[i]]);
+ }
+
+ gimp_transform_3d_matrix4_translate (matrix, +pivot_x, +pivot_y, +pivot_z);
+}
+
+void
+gimp_transform_3d_matrix4_rotate_euler_decompose (GimpMatrix4 *matrix,
+ gint rotation_order,
+ gdouble *angle_x,
+ gdouble *angle_y,
+ gdouble *angle_z)
+{
+ GimpMatrix4 m = *matrix;
+ gdouble * const angles[3] = {angle_x, angle_y, angle_z};
+ gint permutation[3];
+ gboolean forward;
+
+ gimp_transform_3d_rotation_order_to_permutation (rotation_order, permutation);
+
+ forward = permutation[1] == (permutation[0] + 1) % 3;
+
+ *angles[permutation[2]] = atan2 (m.coeff[permutation[1]][permutation[0]],
+ m.coeff[permutation[0]][permutation[0]]);
+
+ if (forward)
+ *angles[permutation[2]] *= -1.0;
+
+ gimp_transform_3d_matrix4_rotate_standard (&m,
+ permutation[2],
+ -*angles[permutation[2]]);
+
+ *angles[permutation[1]] = atan2 (m.coeff[permutation[2]][permutation[0]],
+ m.coeff[permutation[0]][permutation[0]]);
+
+ if (! forward)
+ *angles[permutation[1]] *= -1.0;
+
+ gimp_transform_3d_matrix4_rotate_standard (&m,
+ permutation[1],
+ -*angles[permutation[1]]);
+
+ *angles[permutation[0]] = atan2 (m.coeff[permutation[2]][permutation[1]],
+ m.coeff[permutation[1]][permutation[1]]);
+
+ if (forward)
+ *angles[permutation[0]] *= -1.0;
+}
+
+void
+gimp_transform_3d_matrix4_perspective (GimpMatrix4 *matrix,
+ gdouble camera_x,
+ gdouble camera_y,
+ gdouble camera_z)
+{
+ gint i;
+
+ camera_z = MIN (camera_z, -MIN_FOCAL_LENGTH);
+
+ gimp_transform_3d_matrix4_translate (matrix, -camera_x, -camera_y, 0.0);
+
+ for (i = 0; i < 4; i++)
+ matrix->coeff[3][i] += matrix->coeff[2][i] / -camera_z;
+
+ gimp_transform_3d_matrix4_translate (matrix, +camera_x, +camera_y, 0.0);
+}
+
+void
+gimp_transform_3d_matrix (GimpMatrix3 *matrix,
+ gdouble camera_x,
+ gdouble camera_y,
+ gdouble camera_z,
+ gdouble offset_x,
+ gdouble offset_y,
+ gdouble offset_z,
+ gint rotation_order,
+ gdouble angle_x,
+ gdouble angle_y,
+ gdouble angle_z,
+ gdouble pivot_x,
+ gdouble pivot_y,
+ gdouble pivot_z)
+{
+ GimpMatrix4 m;
+
+ gimp_matrix4_identity (&m);
+ gimp_transform_3d_matrix4_rotate_euler (&m,
+ rotation_order,
+ angle_x, angle_y, angle_z,
+ pivot_x, pivot_y, pivot_z);
+ gimp_transform_3d_matrix4_translate (&m, offset_x, offset_y, offset_z);
+ gimp_transform_3d_matrix4_perspective (&m, camera_x, camera_y, camera_z);
+
+ gimp_transform_3d_matrix4_to_matrix3 (&m, matrix, 2);
+}
diff --git a/app/core/gimp-transform-3d-utils.h b/app/core/gimp-transform-3d-utils.h
new file mode 100644
index 0000000..92f6795
--- /dev/null
+++ b/app/core/gimp-transform-3d-utils.h
@@ -0,0 +1,95 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimp-3d-transform-utils.h
+ * Copyright (C) 2019 Ell
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_TRANSFORM_3D_UTILS_H__
+#define __GIMP_TRANSFORM_3D_UTILS_H__
+
+
+gdouble gimp_transform_3d_angle_of_view_to_focal_length (gdouble angle_of_view,
+ gdouble width,
+ gdouble height);
+gdouble gimp_transform_3d_focal_length_to_angle_of_view (gdouble focal_length,
+ gdouble width,
+ gdouble height);
+
+gint gimp_transform_3d_permutation_to_rotation_order (const gint permutation[3]);
+void gimp_transform_3d_rotation_order_to_permutation (gint rotation_order,
+ gint permutation[3]);
+gint gimp_transform_3d_rotation_order_reverse (gint rotation_order);
+
+void gimp_transform_3d_vector3_rotate (GimpVector3 *vector,
+ const GimpVector3 *axis);
+GimpVector3 gimp_transform_3d_vector3_rotate_val (GimpVector3 vector,
+ GimpVector3 axis);
+
+void gimp_transform_3d_matrix3_to_matrix4 (const GimpMatrix3 *matrix3,
+ GimpMatrix4 *matrix4,
+ gint axis);
+void gimp_transform_3d_matrix4_to_matrix3 (const GimpMatrix4 *matrix4,
+ GimpMatrix3 *matrix3,
+ gint axis);
+
+void gimp_transform_3d_matrix4_translate (GimpMatrix4 *matrix,
+ gdouble x,
+ gdouble y,
+ gdouble z);
+
+void gimp_transform_3d_matrix4_rotate (GimpMatrix4 *matrix,
+ const GimpVector3 *axis);
+void gimp_transform_3d_matrix4_rotate_standard (GimpMatrix4 *matrix,
+ gint axis,
+ gdouble angle);
+
+void gimp_transform_3d_matrix4_rotate_euler (GimpMatrix4 *matrix,
+ gint rotation_order,
+ gdouble angle_x,
+ gdouble angle_y,
+ gdouble angle_z,
+ gdouble pivot_x,
+ gdouble pivot_y,
+ gdouble pivot_z);
+void gimp_transform_3d_matrix4_rotate_euler_decompose (GimpMatrix4 *matrix,
+ gint rotation_order,
+ gdouble *angle_x,
+ gdouble *angle_y,
+ gdouble *angle_z);
+
+void gimp_transform_3d_matrix4_perspective (GimpMatrix4 *matrix,
+ gdouble camera_x,
+ gdouble camera_y,
+ gdouble camera_z);
+
+void gimp_transform_3d_matrix (GimpMatrix3 *matrix,
+ gdouble camera_x,
+ gdouble camera_y,
+ gdouble camera_z,
+ gdouble offset_x,
+ gdouble offset_y,
+ gdouble offset_z,
+ gint rotation_order,
+ gdouble angle_x,
+ gdouble angle_y,
+ gdouble angle_z,
+ gdouble pivot_x,
+ gdouble pivot_y,
+ gdouble pivot_z);
+
+
+#endif /* __GIMP_TRANSFORM_3D_UTILS_H__ */
diff --git a/app/core/gimp-transform-resize.c b/app/core/gimp-transform-resize.c
new file mode 100644
index 0000000..7aabd67
--- /dev/null
+++ b/app/core/gimp-transform-resize.c
@@ -0,0 +1,841 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-2001 Spencer Kimball, Peter Mattis, and others
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gio/gio.h>
+#include <gegl.h>
+
+#include "libgimpmath/gimpmath.h"
+
+#include "core-types.h"
+
+#include "gimp-transform-resize.h"
+#include "gimp-transform-utils.h"
+#include "gimp-utils.h"
+
+
+#if defined (HAVE_ISFINITE)
+#define FINITE(x) isfinite(x)
+#elif defined (HAVE_FINITE)
+#define FINITE(x) finite(x)
+#elif defined (G_OS_WIN32)
+#define FINITE(x) _finite(x)
+#else
+#error "no FINITE() implementation available?!"
+#endif
+
+#define EPSILON 0.00000001
+
+
+typedef struct
+{
+ GimpVector2 a, b, c, d;
+ gdouble area;
+ gdouble aspect;
+} Rectangle;
+
+
+static void gimp_transform_resize_adjust (const GimpVector2 *points,
+ gint n_points,
+ gint *x1,
+ gint *y1,
+ gint *x2,
+ gint *y2);
+static void gimp_transform_resize_crop (const GimpVector2 *points,
+ gint n_points,
+ gdouble aspect,
+ gint *x1,
+ gint *y1,
+ gint *x2,
+ gint *y2);
+
+static void add_rectangle (const GimpVector2 *points,
+ gint n_points,
+ Rectangle *r,
+ GimpVector2 a,
+ GimpVector2 b,
+ GimpVector2 c,
+ GimpVector2 d);
+static gboolean intersect (GimpVector2 a,
+ GimpVector2 b,
+ GimpVector2 c,
+ GimpVector2 d,
+ GimpVector2 *i);
+static gboolean intersect_x (GimpVector2 a,
+ GimpVector2 b,
+ GimpVector2 c,
+ GimpVector2 *i);
+static gboolean intersect_y (GimpVector2 a,
+ GimpVector2 b,
+ GimpVector2 c,
+ GimpVector2 *i);
+static gboolean in_poly (const GimpVector2 *points,
+ gint n_points,
+ GimpVector2 p);
+static gboolean point_on_border (const GimpVector2 *points,
+ gint n_points,
+ GimpVector2 p);
+
+static void find_two_point_rectangle (Rectangle *r,
+ const GimpVector2 *points,
+ gint n_points,
+ gint p);
+static void find_three_point_rectangle_corner (Rectangle *r,
+ const GimpVector2 *points,
+ gint n_points,
+ gint p);
+static void find_three_point_rectangle (Rectangle *r,
+ const GimpVector2 *points,
+ gint n_points,
+ gint p);
+static void find_three_point_rectangle_triangle (Rectangle *r,
+ const GimpVector2 *points,
+ gint n_points,
+ gint p);
+static void find_maximum_aspect_rectangle (Rectangle *r,
+ const GimpVector2 *points,
+ gint n_points,
+ gint p);
+
+
+/*
+ * This function wants to be passed the inverse transformation matrix!!
+ */
+gboolean
+gimp_transform_resize_boundary (const GimpMatrix3 *inv,
+ GimpTransformResize resize,
+ gdouble u1,
+ gdouble v1,
+ gdouble u2,
+ gdouble v2,
+ gint *x1,
+ gint *y1,
+ gint *x2,
+ gint *y2)
+{
+ GimpVector2 bounds[4];
+ GimpVector2 points[5];
+ gint n_points;
+ gboolean valid;
+ gint i;
+
+ g_return_val_if_fail (inv != NULL, FALSE);
+
+ /* initialize with the original boundary */
+ *x1 = floor (u1);
+ *y1 = floor (v1);
+ *x2 = ceil (u2);
+ *y2 = ceil (v2);
+
+ /* if clipping then just return the original rectangle */
+ if (resize == GIMP_TRANSFORM_RESIZE_CLIP)
+ return TRUE;
+
+ bounds[0] = (GimpVector2) { u1, v1 };
+ bounds[1] = (GimpVector2) { u2, v1 };
+ bounds[2] = (GimpVector2) { u2, v2 };
+ bounds[3] = (GimpVector2) { u1, v2 };
+
+ gimp_transform_polygon (inv, bounds, 4, TRUE,
+ points, &n_points);
+
+ valid = (n_points >= 2);
+
+ /* check if the transformation matrix is valid at all */
+ for (i = 0; i < n_points && valid; i++)
+ valid = (FINITE (points[i].x) && FINITE (points[i].y));
+
+ if (! valid)
+ {
+ /* since there is no sensible way to deal with this, just do the same as
+ * with GIMP_TRANSFORM_RESIZE_CLIP: return
+ */
+ return FALSE;
+ }
+
+ switch (resize)
+ {
+ case GIMP_TRANSFORM_RESIZE_ADJUST:
+ /* return smallest rectangle (with sides parallel to x- and y-axis)
+ * that surrounds the new points */
+ gimp_transform_resize_adjust (points, n_points,
+ x1, y1, x2, y2);
+ break;
+
+ case GIMP_TRANSFORM_RESIZE_CROP:
+ gimp_transform_resize_crop (points, n_points,
+ 0.0,
+ x1, y1, x2, y2);
+ break;
+
+ case GIMP_TRANSFORM_RESIZE_CROP_WITH_ASPECT:
+ gimp_transform_resize_crop (points, n_points,
+ (u2 - u1) / (v2 - v1),
+ x1, y1, x2, y2);
+ break;
+
+ case GIMP_TRANSFORM_RESIZE_CLIP:
+ /* Remove warning about not handling all enum values. We handle
+ * this case in the beginning of the function
+ */
+ break;
+ }
+
+ /* ensure that resulting rectangle has at least area 1 */
+ if (*x1 == *x2)
+ (*x2)++;
+
+ if (*y1 == *y2)
+ (*y2)++;
+
+ return TRUE;
+}
+
+/* this calculates the smallest rectangle (with sides parallel to x- and
+ * y-axis) that contains the points d1 to d4
+ */
+static void
+gimp_transform_resize_adjust (const GimpVector2 *points,
+ gint n_points,
+ gint *x1,
+ gint *y1,
+ gint *x2,
+ gint *y2)
+{
+ GimpVector2 top_left;
+ GimpVector2 bottom_right;
+ gint i;
+
+ top_left = bottom_right = points[0];
+
+ for (i = 1; i < n_points; i++)
+ {
+ top_left.x = MIN (top_left.x, points[i].x);
+ top_left.y = MIN (top_left.y, points[i].y);
+
+ bottom_right.x = MAX (bottom_right.x, points[i].x);
+ bottom_right.y = MAX (bottom_right.y, points[i].y);
+ }
+
+ *x1 = (gint) floor (top_left.x + EPSILON);
+ *y1 = (gint) floor (top_left.y + EPSILON);
+
+ *x2 = (gint) ceil (bottom_right.x - EPSILON);
+ *y2 = (gint) ceil (bottom_right.y - EPSILON);
+}
+
+static void
+gimp_transform_resize_crop (const GimpVector2 *orig_points,
+ gint n_points,
+ gdouble aspect,
+ gint *x1,
+ gint *y1,
+ gint *x2,
+ gint *y2)
+{
+ GimpVector2 points[5];
+ Rectangle r;
+ GimpVector2 t,a;
+ gint i, j;
+ gint min;
+
+ memcpy (points, orig_points, sizeof (GimpVector2) * n_points);
+
+ /* find lowest, rightmost corner of surrounding rectangle */
+ a.x = 0;
+ a.y = 0;
+ for (i = 0; i < 4; i++)
+ {
+ if (points[i].x < a.x)
+ a.x = points[i].x;
+
+ if (points[i].y < a.y)
+ a.y = points[i].y;
+ }
+
+ /* and translate all the points to the first quadrant */
+ for (i = 0; i < n_points; i++)
+ {
+ points[i].x += (-a.x) * 2;
+ points[i].y += (-a.y) * 2;
+ }
+
+ /* find the convex hull using Jarvis's March as the points are passed
+ * in different orders due to gimp_matrix3_transform_point()
+ */
+ min = 0;
+ for (i = 0; i < n_points; i++)
+ {
+ if (points[i].y < points[min].y)
+ min = i;
+ }
+
+ t = points[0];
+ points[0] = points[min];
+ points[min] = t;
+
+ for (i = 1; i < n_points - 1; i++)
+ {
+ gdouble min_theta;
+ gdouble min_mag;
+ int next;
+
+ next = n_points - 1;
+ min_theta = 2.0 * G_PI;
+ min_mag = DBL_MAX;
+
+ for (j = i; j < n_points; j++)
+ {
+ gdouble theta;
+ gdouble sy;
+ gdouble sx;
+ gdouble mag;
+
+ sy = points[j].y - points[i - 1].y;
+ sx = points[j].x - points[i - 1].x;
+
+ if ((sx == 0.0) && (sy == 0.0))
+ {
+ next = j;
+ break;
+ }
+
+ theta = atan2 (-sy, -sx);
+ mag = (sx * sx) + (sy * sy);
+
+ if ((theta < min_theta) ||
+ ((theta == min_theta) && (mag < min_mag)))
+ {
+ min_theta = theta;
+ min_mag = mag;
+ next = j;
+ }
+ }
+
+ t = points[i];
+ points[i] = points[next];
+ points[next] = t;
+ }
+
+ /* reverse the order of points */
+ for (i = 0; i < n_points / 2; i++)
+ {
+ t = points[i];
+ points[i] = points[n_points - i - 1];
+ points[n_points - i - 1] = t;
+ }
+
+ r.a.x = r.a.y = r.b.x = r.b.y = r.c.x = r.c.y = r.d.x = r.d.y = r.area = 0;
+ r.aspect = aspect;
+
+ if (aspect != 0)
+ {
+ for (i = 0; i < n_points; i++)
+ find_maximum_aspect_rectangle (&r, points, n_points, i);
+ }
+ else
+ {
+ for (i = 0; i < n_points; i++)
+ {
+ find_three_point_rectangle (&r, points, n_points, i);
+ find_three_point_rectangle_corner (&r, points, n_points, i);
+ find_two_point_rectangle (&r, points, n_points, i);
+ find_three_point_rectangle_triangle (&r, points, n_points, i);
+ }
+ }
+
+ if (r.area == 0)
+ {
+ /* saveguard if something went wrong, adjust and give warning */
+ gimp_transform_resize_adjust (orig_points, n_points,
+ x1, y1, x2, y2);
+ g_printerr ("no rectangle found by algorithm, no cropping done\n");
+ return;
+ }
+ else
+ {
+ /* round and translate the calculated points back */
+ *x1 = floor (r.a.x + 0.5);
+ *y1 = floor (r.a.y + 0.5);
+ *x2 = ceil (r.c.x - 0.5);
+ *y2 = ceil (r.c.y - 0.5);
+
+ *x1 = *x1 - ((-a.x) * 2);
+ *y1 = *y1 - ((-a.y) * 2);
+ *x2 = *x2 - ((-a.x) * 2);
+ *y2 = *y2 - ((-a.y) * 2);
+ return;
+ }
+}
+
+static void
+find_three_point_rectangle (Rectangle *r,
+ const GimpVector2 *points,
+ gint n_points,
+ gint p)
+{
+ GimpVector2 a = points[p % n_points]; /* 0 1 2 3 */
+ GimpVector2 b = points[(p + 1) % n_points]; /* 1 2 3 0 */
+ GimpVector2 c = points[(p + 2) % n_points]; /* 2 3 0 1 */
+ GimpVector2 d = points[(p + 3) % n_points]; /* 3 0 1 2 */
+ GimpVector2 i1; /* intersection point */
+ GimpVector2 i2; /* intersection point */
+ GimpVector2 i3; /* intersection point */
+
+ if (intersect_x (b, c, a, &i1) &&
+ intersect_y (c, d, i1, &i2) &&
+ intersect_x (d, a, i2, &i3))
+ add_rectangle (points, n_points, r, i3, i3, i1, i1);
+
+ if (intersect_y (b, c, a, &i1) &&
+ intersect_x (c, d, i1, &i2) &&
+ intersect_y (d, a, i2, &i3))
+ add_rectangle (points, n_points, r, i3, i3, i1, i1);
+
+ if (intersect_x (d, c, a, &i1) &&
+ intersect_y (c, b, i1, &i2) &&
+ intersect_x (b, a, i2, &i3))
+ add_rectangle (points, n_points, r, i3, i3, i1, i1);
+
+ if (intersect_y (d, c, a, &i1) &&
+ intersect_x (c, b, i1, &i2) &&
+ intersect_y (b, a, i2, &i3))
+ add_rectangle (points, n_points, r, i3, i3, i1, i1);
+}
+
+static void
+find_three_point_rectangle_corner (Rectangle *r,
+ const GimpVector2 *points,
+ gint n_points,
+ gint p)
+{
+ GimpVector2 a = points[p % n_points]; /* 0 1 2 3 */
+ GimpVector2 b = points[(p + 1) % n_points]; /* 1 2 3 0 */
+ GimpVector2 c = points[(p + 2) % n_points]; /* 2 3 0 2 */
+ GimpVector2 d = points[(p + 3) % n_points]; /* 3 0 2 1 */
+ GimpVector2 i1; /* intersection point */
+ GimpVector2 i2; /* intersection point */
+
+ if (intersect_x (b, c, a , &i1) &&
+ intersect_y (c, d, i1, &i2))
+ add_rectangle (points, n_points, r, a, a, i1, i2);
+
+ if (intersect_y (b, c, a , &i1) &&
+ intersect_x (c, d, i1, &i2))
+ add_rectangle (points, n_points, r, a, a, i1, i2);
+
+ if (intersect_x (c, d, a , &i1) &&
+ intersect_y (b, c, i1, &i2))
+ add_rectangle (points, n_points, r, a, a, i1, i2);
+
+ if (intersect_y (c, d, a , &i1) &&
+ intersect_x (b, c, i1, &i2))
+ add_rectangle (points, n_points, r, a, a, i1, i2);
+}
+
+static void
+find_two_point_rectangle (Rectangle *r,
+ const GimpVector2 *points,
+ gint n_points,
+ gint p)
+{
+ GimpVector2 a = points[ p % n_points]; /* 0 1 2 3 */
+ GimpVector2 b = points[(p + 1) % n_points]; /* 1 2 3 0 */
+ GimpVector2 c = points[(p + 2) % n_points]; /* 2 3 0 1 */
+ GimpVector2 d = points[(p + 3) % n_points]; /* 3 0 1 2 */
+ GimpVector2 i1; /* intersection point */
+ GimpVector2 i2; /* intersection point */
+ GimpVector2 mid; /* Mid point */
+
+ add_rectangle (points, n_points, r, a, a, c, c);
+ add_rectangle (points, n_points, r, b, b, d, d);
+
+ if (intersect_x (c, b, a, &i1) &&
+ intersect_y (c, b, a, &i2))
+ {
+ mid.x = ( i1.x + i2.x ) / 2.0;
+ mid.y = ( i1.y + i2.y ) / 2.0;
+
+ add_rectangle (points, n_points, r, a, a, mid, mid);
+ }
+}
+
+static void
+find_three_point_rectangle_triangle (Rectangle *r,
+ const GimpVector2 *points,
+ gint n_points,
+ gint p)
+{
+ GimpVector2 a = points[p % n_points]; /* 0 1 2 3 */
+ GimpVector2 b = points[(p + 1) % n_points]; /* 1 2 3 0 */
+ GimpVector2 c = points[(p + 2) % n_points]; /* 2 3 0 1 */
+ GimpVector2 d = points[(p + 3) % n_points]; /* 3 0 1 2 */
+ GimpVector2 i1; /* intersection point */
+ GimpVector2 i2; /* intersection point */
+ GimpVector2 mid;
+
+ mid.x = (a.x + b.x) / 2.0;
+ mid.y = (a.y + b.y) / 2.0;
+
+ if (intersect_x (b, c, mid, &i1) &&
+ intersect_y (a, d, mid, &i2))
+ add_rectangle (points, n_points, r, mid, mid, i1, i2);
+
+ if (intersect_y (b, c, mid, &i1) &&
+ intersect_x (a, d, mid, &i2))
+ add_rectangle (points, n_points, r, mid, mid, i1, i2);
+
+ if (intersect_x (a, d, mid, &i1) &&
+ intersect_y (b, c, mid, &i2))
+ add_rectangle (points, n_points, r, mid, mid, i1, i2);
+
+ if (intersect_y (a, d, mid, &i1) &&
+ intersect_x (b, c, mid, &i2))
+ add_rectangle (points, n_points, r, mid, mid, i1, i2);
+}
+
+static void
+find_maximum_aspect_rectangle (Rectangle *r,
+ const GimpVector2 *points,
+ gint n_points,
+ gint p)
+{
+ GimpVector2 a = points[ p % n_points]; /* 0 1 2 3 */
+ GimpVector2 b = points[(p + 1) % n_points]; /* 1 2 3 0 */
+ GimpVector2 c = points[(p + 2) % n_points]; /* 2 3 0 1 */
+ GimpVector2 d = points[(p + 3) % n_points]; /* 3 0 1 2 */
+ GimpVector2 i1; /* intersection point */
+ GimpVector2 i2; /* intersection point */
+ GimpVector2 i3; /* intersection point */
+
+ if (intersect_x (b, c, a, &i1))
+ {
+ i2.x = i1.x + 1.0 * r->aspect;
+ i2.y = i1.y + 1.0;
+
+ if (intersect (d, a, i1, i2, &i3))
+ add_rectangle (points, n_points, r, i1, i3, i1, i3);
+
+ if (intersect (a, b, i1, i2, &i3))
+ add_rectangle (points, n_points, r, i1, i3, i1, i3);
+
+ if (intersect (c, d, i1, i2, &i3))
+ add_rectangle (points, n_points, r, i1, i3, i1, i3);
+
+ i2.x = i1.x - 1.0 * r->aspect;
+ i2.y = i1.y + 1.0;
+
+ if (intersect (d, a, i1, i2, &i3))
+ add_rectangle (points, n_points, r, i1, i3, i1, i3);
+
+ if (intersect (a, b, i1, i2, &i3))
+ add_rectangle (points, n_points, r, i1, i3, i1, i3);
+
+ if (intersect (c, d, i1, i2, &i3))
+ add_rectangle (points, n_points, r, i1, i3, i1, i3);
+ }
+
+ if (intersect_y (b, c, a, &i1))
+ {
+ i2.x = i1.x + 1.0 * r->aspect;
+ i2.y = i1.y + 1.0;
+
+ if (intersect (d, a, i1, i2, &i3))
+ add_rectangle (points, n_points, r, i1, i3, i1, i3);
+
+ if (intersect (a, b, i1, i2, &i3))
+ add_rectangle (points, n_points, r, i1, i3, i1, i3);
+
+ if (intersect (c, d, i1, i2, &i3))
+ add_rectangle (points, n_points, r, i1, i3, i1, i3);
+
+ i2.x = i1.x - 1.0 * r->aspect;
+ i2.y = i1.y + 1.0;
+
+ if (intersect (d, a, i1, i2, &i3))
+ add_rectangle (points, n_points, r, i1, i3, i1, i3);
+
+ if (intersect (a, b, i1, i2, &i3))
+ add_rectangle (points, n_points, r, i1, i3, i1, i3);
+
+ if (intersect (c, d, i1, i2, &i3))
+ add_rectangle (points, n_points, r, i1, i3, i1, i3);
+ }
+
+ if (intersect_x (c, d, a, &i1))
+ {
+ i2.x = i1.x + 1.0 * r->aspect;
+ i2.y = i1.y + 1.0;
+
+ if (intersect (d, a, i1, i2, &i3))
+ add_rectangle (points, n_points, r, i1, i3, i1, i3);
+
+ if (intersect (a, b, i1, i2, &i3))
+ add_rectangle (points, n_points, r, i1, i3, i1, i3);
+
+ if (intersect (b, c, i1, i2, &i3))
+ add_rectangle (points, n_points, r, i1, i3, i1, i3);
+
+ i2.x = i1.x - 1.0 * r->aspect;
+ i2.y = i1.y + 1.0;
+
+ if (intersect (d, a, i1, i2, &i3))
+ add_rectangle (points, n_points, r, i1, i3, i1, i3);
+
+ if (intersect (a, b, i1, i2, &i3))
+ add_rectangle (points, n_points, r, i1, i3, i1, i3);
+
+ if (intersect (b, c, i1, i2, &i3))
+ add_rectangle (points, n_points, r, i1, i3, i1, i3);
+ }
+
+ if (intersect_y (c, d, a, &i1))
+ {
+ i2.x = i1.x + 1.0 * r->aspect;
+ i2.y = i1.y + 1.0;
+
+ if (intersect (d, a, i1, i2, &i3))
+ add_rectangle (points, n_points, r, i1, i3, i1, i3);
+
+ if (intersect (a, b, i1, i2, &i3))
+ add_rectangle (points, n_points, r, i1, i3, i1, i3);
+
+ if (intersect (b, c, i1, i2, &i3))
+ add_rectangle (points, n_points, r, i1, i3, i1, i3);
+
+ i2.x = i1.x - 1.0 * r->aspect;
+ i2.y = i1.y + 1.0;
+
+ if (intersect (d, a, i1, i2, &i3))
+ add_rectangle (points, n_points, r, i1, i3, i1, i3);
+
+ if (intersect (a, b, i1, i2, &i3))
+ add_rectangle (points, n_points, r, i1, i3, i1, i3);
+
+ if (intersect (b, c, i1, i2, &i3))
+ add_rectangle (points, n_points, r, i1, i3, i1, i3);
+ }
+}
+
+/* check if point is inside the polygon "points", if point is on border
+ * its still inside.
+ */
+static gboolean
+in_poly (const GimpVector2 *points,
+ gint n_points,
+ GimpVector2 p)
+{
+ GimpVector2 p1, p2;
+ gint counter = 0;
+ gint i;
+
+ p1 = points[0];
+
+ for (i = 1; i <= n_points; i++)
+ {
+ p2 = points[i % n_points];
+
+ if (p.y > MIN (p1.y, p2.y))
+ {
+ if (p.y <= MAX (p1.y, p2.y))
+ {
+ if (p.x <= MAX (p1.x, p2.x))
+ {
+ if (p1.y != p2.y)
+ {
+ gdouble xinters = ((p.y - p1.y) * (p2.x - p1.x) /
+ (p2.y - p1.y) + p1.x);
+
+ if (p1.x == p2.x || p.x <= xinters)
+ counter++;
+ }
+ }
+ }
+ }
+
+ p1 = p2;
+ }
+
+ /* border check */
+ if (point_on_border (points, n_points, p))
+ return TRUE;
+
+ return (counter % 2 != 0);
+}
+
+/* check if the point p lies on the polygon "points"
+ */
+static gboolean
+point_on_border (const GimpVector2 *points,
+ gint n_points,
+ GimpVector2 p)
+{
+ gint i;
+
+ for (i = 0; i <= n_points; i++)
+ {
+ GimpVector2 a = points[i % n_points];
+ GimpVector2 b = points[(i + 1) % n_points];
+ gdouble a1 = (b.y - a.y);
+ gdouble b1 = (a.x - b.x);
+ gdouble c1 = a1 * a.x + b1 * a.y;
+ gdouble c2 = a1 * p.x + b1 * p.y;
+
+ if (ABS (c1 - c2) < EPSILON &&
+ MIN (a.x, b.x) <= p.x &&
+ MAX (a.x, b.x) >= p.x &&
+ MIN (a.y, b.y) <= p.y &&
+ MAX (a.y, b.y) >= p.y)
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/* calculate the intersection point of the line a-b with the line c-d
+ * and write it to i, if existing.
+ */
+static gboolean
+intersect (GimpVector2 a,
+ GimpVector2 b,
+ GimpVector2 c,
+ GimpVector2 d,
+ GimpVector2 *i)
+{
+ gdouble a1 = (b.y - a.y);
+ gdouble b1 = (a.x - b.x);
+ gdouble c1 = a1 * a.x + b1 * a.y;
+
+ gdouble a2 = (d.y - c.y);
+ gdouble b2 = (c.x - d.x);
+ gdouble c2 = a2 * c.x + b2 * c.y;
+ gdouble det = a1 * b2 - a2 * b1;
+
+ if (det == 0)
+ return FALSE;
+
+ i->x = (b2 * c1 - b1 * c2) / det;
+ i->y = (a1 * c2 - a2 * c1) / det;
+
+ return TRUE;
+}
+
+/* calculate the intersection point of the line a-b with the vertical line
+ * through c and write it to i, if existing.
+ */
+static gboolean
+intersect_x (GimpVector2 a,
+ GimpVector2 b,
+ GimpVector2 c,
+ GimpVector2 *i)
+{
+ GimpVector2 d = c;
+ d.y += 1;
+
+ return intersect(a,b,c,d,i);
+}
+
+/* calculate the intersection point of the line a-b with the horizontal line
+ * through c and write it to i, if existing.
+ */
+static gboolean
+intersect_y (GimpVector2 a,
+ GimpVector2 b,
+ GimpVector2 c,
+ GimpVector2 *i)
+{
+ GimpVector2 d = c;
+ d.x += 1;
+
+ return intersect(a,b,c,d,i);
+}
+
+/* this takes the smallest ortho-aligned (the sides of the rectangle are
+ * parallel to the x- and y-axis) rectangle fitting around the points a to d,
+ * checks if the whole rectangle is inside the polygon described by points and
+ * writes it to r if the area is bigger than the rectangle already stored in r.
+ */
+static void
+add_rectangle (const GimpVector2 *points,
+ gint n_points,
+ Rectangle *r,
+ GimpVector2 a,
+ GimpVector2 b,
+ GimpVector2 c,
+ GimpVector2 d)
+{
+ gdouble width;
+ gdouble height;
+ gdouble minx, maxx;
+ gdouble miny, maxy;
+
+ /* get the orthoaligned (the sides of the rectangle are parallel to the x-
+ * and y-axis) rectangle surrounding the points a to d.
+ */
+ minx = MIN4 (a.x, b.x, c.x, d.x);
+ maxx = MAX4 (a.x, b.x, c.x, d.x);
+ miny = MIN4 (a.y, b.y, c.y, d.y);
+ maxy = MAX4 (a.y, b.y, c.y, d.y);
+
+ a.x = minx;
+ a.y = miny;
+
+ b.x = maxx;
+ b.y = miny;
+
+ c.x = maxx;
+ c.y = maxy;
+
+ d.x = minx;
+ d.y = maxy;
+
+ width = maxx - minx;
+ height = maxy - miny;
+
+ /* check if this rectangle is inside the polygon "points" */
+ if (in_poly (points, n_points, a) &&
+ in_poly (points, n_points, b) &&
+ in_poly (points, n_points, c) &&
+ in_poly (points, n_points, d))
+ {
+ gdouble area = width * height;
+
+ /* check if the new rectangle is larger (in terms of area)
+ * than the currently stored rectangle in r, if yes store
+ * new rectangle to r
+ */
+ if (r->area <= area)
+ {
+ r->a.x = a.x;
+ r->a.y = a.y;
+
+ r->b.x = b.x;
+ r->b.y = b.y;
+
+ r->c.x = c.x;
+ r->c.y = c.y;
+
+ r->d.x = d.x;
+ r->d.y = d.y;
+
+ r->area = area;
+ }
+ }
+}
diff --git a/app/core/gimp-transform-resize.h b/app/core/gimp-transform-resize.h
new file mode 100644
index 0000000..4ebb53f
--- /dev/null
+++ b/app/core/gimp-transform-resize.h
@@ -0,0 +1,34 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-2001 Spencer Kimball, Peter Mattis, and others
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_TRANSFORM_RESIZE_H__
+#define __GIMP_TRANSFORM_RESIZE_H__
+
+
+gboolean gimp_transform_resize_boundary (const GimpMatrix3 *inv,
+ GimpTransformResize resize,
+ gdouble u1,
+ gdouble v1,
+ gdouble u2,
+ gdouble v2,
+ gint *x1,
+ gint *y1,
+ gint *x2,
+ gint *y2);
+
+
+#endif /* __GIMP_TRANSFORM_RESIZE_H__ */
diff --git a/app/core/gimp-transform-utils.c b/app/core/gimp-transform-utils.c
new file mode 100644
index 0000000..555ff09
--- /dev/null
+++ b/app/core/gimp-transform-utils.c
@@ -0,0 +1,1211 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-2001 Spencer Kimball, Peter Mattis, and others
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <glib-object.h>
+
+#include "libgimpmath/gimpmath.h"
+
+#include "core-types.h"
+
+#include "gimp-transform-utils.h"
+#include "gimpcoords.h"
+#include "gimpcoords-interpolate.h"
+
+
+#define EPSILON 1e-6
+
+
+void
+gimp_transform_get_rotate_center (gint x,
+ gint y,
+ gint width,
+ gint height,
+ gboolean auto_center,
+ gdouble *center_x,
+ gdouble *center_y)
+{
+ g_return_if_fail (center_x != NULL);
+ g_return_if_fail (center_y != NULL);
+
+ if (auto_center)
+ {
+ *center_x = (gdouble) x + (gdouble) width / 2.0;
+ *center_y = (gdouble) y + (gdouble) height / 2.0;
+ }
+}
+
+void
+gimp_transform_get_flip_axis (gint x,
+ gint y,
+ gint width,
+ gint height,
+ GimpOrientationType flip_type,
+ gboolean auto_center,
+ gdouble *axis)
+{
+ g_return_if_fail (axis != NULL);
+
+ if (auto_center)
+ {
+ switch (flip_type)
+ {
+ case GIMP_ORIENTATION_HORIZONTAL:
+ *axis = ((gdouble) x + (gdouble) width / 2.0);
+ break;
+
+ case GIMP_ORIENTATION_VERTICAL:
+ *axis = ((gdouble) y + (gdouble) height / 2.0);
+ break;
+
+ default:
+ g_return_if_reached ();
+ break;
+ }
+ }
+}
+
+void
+gimp_transform_matrix_flip (GimpMatrix3 *matrix,
+ GimpOrientationType flip_type,
+ gdouble axis)
+{
+ g_return_if_fail (matrix != NULL);
+
+ switch (flip_type)
+ {
+ case GIMP_ORIENTATION_HORIZONTAL:
+ gimp_matrix3_translate (matrix, - axis, 0.0);
+ gimp_matrix3_scale (matrix, -1.0, 1.0);
+ gimp_matrix3_translate (matrix, axis, 0.0);
+ break;
+
+ case GIMP_ORIENTATION_VERTICAL:
+ gimp_matrix3_translate (matrix, 0.0, - axis);
+ gimp_matrix3_scale (matrix, 1.0, -1.0);
+ gimp_matrix3_translate (matrix, 0.0, axis);
+ break;
+
+ case GIMP_ORIENTATION_UNKNOWN:
+ break;
+ }
+}
+
+void
+gimp_transform_matrix_flip_free (GimpMatrix3 *matrix,
+ gdouble x1,
+ gdouble y1,
+ gdouble x2,
+ gdouble y2)
+{
+ gdouble angle;
+
+ g_return_if_fail (matrix != NULL);
+
+ angle = atan2 (y2 - y1, x2 - x1);
+
+ gimp_matrix3_identity (matrix);
+ gimp_matrix3_translate (matrix, -x1, -y1);
+ gimp_matrix3_rotate (matrix, -angle);
+ gimp_matrix3_scale (matrix, 1.0, -1.0);
+ gimp_matrix3_rotate (matrix, angle);
+ gimp_matrix3_translate (matrix, x1, y1);
+}
+
+void
+gimp_transform_matrix_rotate (GimpMatrix3 *matrix,
+ GimpRotationType rotate_type,
+ gdouble center_x,
+ gdouble center_y)
+{
+ gdouble angle = 0;
+
+ switch (rotate_type)
+ {
+ case GIMP_ROTATE_90:
+ angle = G_PI_2;
+ break;
+ case GIMP_ROTATE_180:
+ angle = G_PI;
+ break;
+ case GIMP_ROTATE_270:
+ angle = - G_PI_2;
+ break;
+ }
+
+ gimp_transform_matrix_rotate_center (matrix, center_x, center_y, angle);
+}
+
+void
+gimp_transform_matrix_rotate_rect (GimpMatrix3 *matrix,
+ gint x,
+ gint y,
+ gint width,
+ gint height,
+ gdouble angle)
+{
+ gdouble center_x;
+ gdouble center_y;
+
+ g_return_if_fail (matrix != NULL);
+
+ center_x = (gdouble) x + (gdouble) width / 2.0;
+ center_y = (gdouble) y + (gdouble) height / 2.0;
+
+ gimp_matrix3_translate (matrix, -center_x, -center_y);
+ gimp_matrix3_rotate (matrix, angle);
+ gimp_matrix3_translate (matrix, +center_x, +center_y);
+}
+
+void
+gimp_transform_matrix_rotate_center (GimpMatrix3 *matrix,
+ gdouble center_x,
+ gdouble center_y,
+ gdouble angle)
+{
+ g_return_if_fail (matrix != NULL);
+
+ gimp_matrix3_translate (matrix, -center_x, -center_y);
+ gimp_matrix3_rotate (matrix, angle);
+ gimp_matrix3_translate (matrix, +center_x, +center_y);
+}
+
+void
+gimp_transform_matrix_scale (GimpMatrix3 *matrix,
+ gint x,
+ gint y,
+ gint width,
+ gint height,
+ gdouble t_x,
+ gdouble t_y,
+ gdouble t_width,
+ gdouble t_height)
+{
+ gdouble scale_x = 1.0;
+ gdouble scale_y = 1.0;
+
+ g_return_if_fail (matrix != NULL);
+
+ if (width > 0)
+ scale_x = t_width / (gdouble) width;
+
+ if (height > 0)
+ scale_y = t_height / (gdouble) height;
+
+ gimp_matrix3_identity (matrix);
+ gimp_matrix3_translate (matrix, -x, -y);
+ gimp_matrix3_scale (matrix, scale_x, scale_y);
+ gimp_matrix3_translate (matrix, t_x, t_y);
+}
+
+void
+gimp_transform_matrix_shear (GimpMatrix3 *matrix,
+ gint x,
+ gint y,
+ gint width,
+ gint height,
+ GimpOrientationType orientation,
+ gdouble amount)
+{
+ gdouble center_x;
+ gdouble center_y;
+
+ g_return_if_fail (matrix != NULL);
+
+ if (width == 0)
+ width = 1;
+
+ if (height == 0)
+ height = 1;
+
+ center_x = (gdouble) x + (gdouble) width / 2.0;
+ center_y = (gdouble) y + (gdouble) height / 2.0;
+
+ gimp_matrix3_identity (matrix);
+ gimp_matrix3_translate (matrix, -center_x, -center_y);
+
+ if (orientation == GIMP_ORIENTATION_HORIZONTAL)
+ gimp_matrix3_xshear (matrix, amount / height);
+ else
+ gimp_matrix3_yshear (matrix, amount / width);
+
+ gimp_matrix3_translate (matrix, +center_x, +center_y);
+}
+
+void
+gimp_transform_matrix_perspective (GimpMatrix3 *matrix,
+ gint x,
+ gint y,
+ gint width,
+ gint height,
+ gdouble t_x1,
+ gdouble t_y1,
+ gdouble t_x2,
+ gdouble t_y2,
+ gdouble t_x3,
+ gdouble t_y3,
+ gdouble t_x4,
+ gdouble t_y4)
+{
+ GimpMatrix3 trafo;
+ gdouble scalex;
+ gdouble scaley;
+
+ g_return_if_fail (matrix != NULL);
+
+ scalex = scaley = 1.0;
+
+ if (width > 0)
+ scalex = 1.0 / (gdouble) width;
+
+ if (height > 0)
+ scaley = 1.0 / (gdouble) height;
+
+ gimp_matrix3_translate (matrix, -x, -y);
+ gimp_matrix3_scale (matrix, scalex, scaley);
+
+ /* Determine the perspective transform that maps from
+ * the unit cube to the transformed coordinates
+ */
+ {
+ gdouble dx1, dx2, dx3, dy1, dy2, dy3;
+
+ dx1 = t_x2 - t_x4;
+ dx2 = t_x3 - t_x4;
+ dx3 = t_x1 - t_x2 + t_x4 - t_x3;
+
+ dy1 = t_y2 - t_y4;
+ dy2 = t_y3 - t_y4;
+ dy3 = t_y1 - t_y2 + t_y4 - t_y3;
+
+ /* Is the mapping affine? */
+ if ((dx3 == 0.0) && (dy3 == 0.0))
+ {
+ trafo.coeff[0][0] = t_x2 - t_x1;
+ trafo.coeff[0][1] = t_x4 - t_x2;
+ trafo.coeff[0][2] = t_x1;
+ trafo.coeff[1][0] = t_y2 - t_y1;
+ trafo.coeff[1][1] = t_y4 - t_y2;
+ trafo.coeff[1][2] = t_y1;
+ trafo.coeff[2][0] = 0.0;
+ trafo.coeff[2][1] = 0.0;
+ }
+ else
+ {
+ gdouble det1, det2;
+
+ det1 = dx3 * dy2 - dy3 * dx2;
+ det2 = dx1 * dy2 - dy1 * dx2;
+
+ trafo.coeff[2][0] = (det2 == 0.0) ? 1.0 : det1 / det2;
+
+ det1 = dx1 * dy3 - dy1 * dx3;
+
+ trafo.coeff[2][1] = (det2 == 0.0) ? 1.0 : det1 / det2;
+
+ trafo.coeff[0][0] = t_x2 - t_x1 + trafo.coeff[2][0] * t_x2;
+ trafo.coeff[0][1] = t_x3 - t_x1 + trafo.coeff[2][1] * t_x3;
+ trafo.coeff[0][2] = t_x1;
+
+ trafo.coeff[1][0] = t_y2 - t_y1 + trafo.coeff[2][0] * t_y2;
+ trafo.coeff[1][1] = t_y3 - t_y1 + trafo.coeff[2][1] * t_y3;
+ trafo.coeff[1][2] = t_y1;
+ }
+
+ trafo.coeff[2][2] = 1.0;
+ }
+
+ gimp_matrix3_mult (&trafo, matrix);
+}
+
+/* modified gaussian algorithm
+ * solves a system of linear equations
+ *
+ * Example:
+ * 1x + 2y + 4z = 25
+ * 2x + 1y = 4
+ * 3x + 5y + 2z = 23
+ * Solution: x=1, y=2, z=5
+ *
+ * Input:
+ * matrix = { 1,2,4,25,2,1,0,4,3,5,2,23 }
+ * s = 3 (Number of variables)
+ * Output:
+ * return value == TRUE (TRUE, if there is a single unique solution)
+ * solution == { 1,2,5 } (if the return value is FALSE, the content
+ * of solution is of no use)
+ */
+static gboolean
+mod_gauss (gdouble matrix[],
+ gdouble solution[],
+ gint s)
+{
+ gint p[s]; /* row permutation */
+ gint i, j, r, temp;
+ gdouble q;
+ gint t = s + 1;
+
+ for (i = 0; i < s; i++)
+ {
+ p[i] = i;
+ }
+
+ for (r = 0; r < s; r++)
+ {
+ /* make sure that (r,r) is not 0 */
+ if (fabs (matrix[p[r] * t + r]) <= EPSILON)
+ {
+ /* we need to permutate rows */
+ for (i = r + 1; i <= s; i++)
+ {
+ if (i == s)
+ {
+ /* if this happens, the linear system has zero or
+ * more than one solutions.
+ */
+ return FALSE;
+ }
+
+ if (fabs (matrix[p[i] * t + r]) > EPSILON)
+ break;
+ }
+
+ temp = p[r];
+ p[r] = p[i];
+ p[i] = temp;
+ }
+
+ /* make (r,r) == 1 */
+ q = 1.0 / matrix[p[r] * t + r];
+ matrix[p[r] * t + r] = 1.0;
+
+ for (j = r + 1; j < t; j++)
+ {
+ matrix[p[r] * t + j] *= q;
+ }
+
+ /* make that all entries in column r are 0 (except (r,r)) */
+ for (i = 0; i < s; i++)
+ {
+ if (i == r)
+ continue;
+
+ for (j = r + 1; j < t ; j++)
+ {
+ matrix[p[i] * t + j] -= matrix[p[r] * t + j] * matrix[p[i] * t + r];
+ }
+
+ /* we don't need to execute the following line
+ * since we won't access this element again:
+ *
+ * matrix[p[i] * t + r] = 0.0;
+ */
+ }
+ }
+
+ for (i = 0; i < s; i++)
+ {
+ solution[i] = matrix[p[i] * t + s];
+ }
+
+ return TRUE;
+}
+
+/* multiplies 'matrix' by the matrix that transforms a set of 4 'input_points'
+ * to corresponding 'output_points', if such matrix exists, and is valid (i.e.,
+ * keeps the output points in front of the camera).
+ *
+ * returns TRUE if successful.
+ */
+gboolean
+gimp_transform_matrix_generic (GimpMatrix3 *matrix,
+ const GimpVector2 input_points[4],
+ const GimpVector2 output_points[4])
+{
+ GimpMatrix3 trafo;
+ gdouble coeff[8 * 9];
+ gboolean negative = -1;
+ gint i;
+ gboolean result = TRUE;
+
+ g_return_val_if_fail (matrix != NULL, FALSE);
+ g_return_val_if_fail (input_points != NULL, FALSE);
+ g_return_val_if_fail (output_points != NULL, FALSE);
+
+ /* find the matrix that transforms 'input_points' to 'output_points', whose
+ * (3, 3) coeffcient is 1, by solving a system of linear equations whose
+ * solution is the remaining 8 coefficients.
+ */
+ for (i = 0; i < 4; i++)
+ {
+ coeff[i * 9 + 0] = input_points[i].x;
+ coeff[i * 9 + 1] = input_points[i].y;
+ coeff[i * 9 + 2] = 1.0;
+ coeff[i * 9 + 3] = 0.0;
+ coeff[i * 9 + 4] = 0.0;
+ coeff[i * 9 + 5] = 0.0;
+ coeff[i * 9 + 6] = -input_points[i].x * output_points[i].x;
+ coeff[i * 9 + 7] = -input_points[i].y * output_points[i].x;
+ coeff[i * 9 + 8] = output_points[i].x;
+
+ coeff[(i + 4) * 9 + 0] = 0.0;
+ coeff[(i + 4) * 9 + 1] = 0.0;
+ coeff[(i + 4) * 9 + 2] = 0.0;
+ coeff[(i + 4) * 9 + 3] = input_points[i].x;
+ coeff[(i + 4) * 9 + 4] = input_points[i].y;
+ coeff[(i + 4) * 9 + 5] = 1.0;
+ coeff[(i + 4) * 9 + 6] = -input_points[i].x * output_points[i].y;
+ coeff[(i + 4) * 9 + 7] = -input_points[i].y * output_points[i].y;
+ coeff[(i + 4) * 9 + 8] = output_points[i].y;
+ }
+
+ /* if there is no solution, bail */
+ if (! mod_gauss (coeff, (gdouble *) trafo.coeff, 8))
+ return FALSE;
+
+ trafo.coeff[2][2] = 1.0;
+
+ /* make sure that none of the input points maps to a point at infinity, and
+ * that all output points are on the same side of the camera.
+ */
+ for (i = 0; i < 4; i++)
+ {
+ gdouble w;
+ gboolean neg;
+
+ w = trafo.coeff[2][0] * input_points[i].x +
+ trafo.coeff[2][1] * input_points[i].y +
+ trafo.coeff[2][2];
+
+ if (fabs (w) <= EPSILON)
+ result = FALSE;
+
+ neg = (w < 0.0);
+
+ if (negative < 0)
+ {
+ negative = neg;
+ }
+ else if (neg != negative)
+ {
+ result = FALSE;
+ break;
+ }
+ }
+
+ /* if the output points are all behind the camera, negate the matrix, which
+ * would map the input points to the corresponding points in front of the
+ * camera.
+ */
+ if (negative > 0)
+ {
+ gint r;
+ gint c;
+
+ for (r = 0; r < 3; r++)
+ {
+ for (c = 0; c < 3; c++)
+ {
+ trafo.coeff[r][c] = -trafo.coeff[r][c];
+ }
+ }
+ }
+
+ /* append the transformation to 'matrix' */
+ gimp_matrix3_mult (&trafo, matrix);
+
+ return result;
+}
+
+gboolean
+gimp_transform_polygon_is_convex (gdouble x1,
+ gdouble y1,
+ gdouble x2,
+ gdouble y2,
+ gdouble x3,
+ gdouble y3,
+ gdouble x4,
+ gdouble y4)
+{
+ gdouble z1, z2, z3, z4;
+
+ /* We test if the transformed polygon is convex. if z1 and z2 have
+ * the same sign as well as z3 and z4 the polygon is convex.
+ */
+ z1 = ((x2 - x1) * (y4 - y1) -
+ (x4 - x1) * (y2 - y1));
+ z2 = ((x4 - x1) * (y3 - y1) -
+ (x3 - x1) * (y4 - y1));
+ z3 = ((x4 - x2) * (y3 - y2) -
+ (x3 - x2) * (y4 - y2));
+ z4 = ((x3 - x2) * (y1 - y2) -
+ (x1 - x2) * (y3 - y2));
+
+ return (z1 * z2 > 0) && (z3 * z4 > 0);
+}
+
+/* transforms the polygon or polyline, whose vertices are given by 'vertices',
+ * by 'matrix', performing clipping by the near plane. 'closed' indicates
+ * whether the vertices represent a polygon ('closed == TRUE') or a polyline
+ * ('closed == FALSE').
+ *
+ * returns the transformed vertices in 't_vertices', and their count in
+ * 'n_t_vertices'. the minimal possible number of transformed vertices is 0,
+ * which happens when the entire input is clipped. in general, the maximal
+ * possible number of transformed vertices is '3 * n_vertices / 2' (rounded
+ * down), however, for convex polygons the number is 'n_vertices + 1', and for
+ * a single line segment ('n_vertices == 2' and 'closed == FALSE') the number
+ * is 2.
+ *
+ * 't_vertices' may not alias 'vertices', except when transforming a single
+ * line segment.
+ */
+void
+gimp_transform_polygon (const GimpMatrix3 *matrix,
+ const GimpVector2 *vertices,
+ gint n_vertices,
+ gboolean closed,
+ GimpVector2 *t_vertices,
+ gint *n_t_vertices)
+{
+ GimpVector3 curr;
+ gboolean curr_visible;
+ gint i;
+
+ g_return_if_fail (matrix != NULL);
+ g_return_if_fail (vertices != NULL);
+ g_return_if_fail (n_vertices >= 0);
+ g_return_if_fail (t_vertices != NULL);
+ g_return_if_fail (n_t_vertices != NULL);
+
+ *n_t_vertices = 0;
+
+ if (n_vertices == 0)
+ return;
+
+ curr.x = matrix->coeff[0][0] * vertices[0].x +
+ matrix->coeff[0][1] * vertices[0].y +
+ matrix->coeff[0][2];
+ curr.y = matrix->coeff[1][0] * vertices[0].x +
+ matrix->coeff[1][1] * vertices[0].y +
+ matrix->coeff[1][2];
+ curr.z = matrix->coeff[2][0] * vertices[0].x +
+ matrix->coeff[2][1] * vertices[0].y +
+ matrix->coeff[2][2];
+
+ curr_visible = (curr.z >= GIMP_TRANSFORM_NEAR_Z);
+
+ for (i = 0; i < n_vertices; i++)
+ {
+ if (curr_visible)
+ {
+ t_vertices[(*n_t_vertices)++] = (GimpVector2) { curr.x / curr.z,
+ curr.y / curr.z };
+ }
+
+ if (i < n_vertices - 1 || closed)
+ {
+ GimpVector3 next;
+ gboolean next_visible;
+ gint j = (i + 1) % n_vertices;
+
+ next.x = matrix->coeff[0][0] * vertices[j].x +
+ matrix->coeff[0][1] * vertices[j].y +
+ matrix->coeff[0][2];
+ next.y = matrix->coeff[1][0] * vertices[j].x +
+ matrix->coeff[1][1] * vertices[j].y +
+ matrix->coeff[1][2];
+ next.z = matrix->coeff[2][0] * vertices[j].x +
+ matrix->coeff[2][1] * vertices[j].y +
+ matrix->coeff[2][2];
+
+ next_visible = (next.z >= GIMP_TRANSFORM_NEAR_Z);
+
+ if (next_visible != curr_visible)
+ {
+ gdouble ratio = (curr.z - GIMP_TRANSFORM_NEAR_Z) / (curr.z - next.z);
+
+ t_vertices[(*n_t_vertices)++] =
+ (GimpVector2) { (curr.x + (next.x - curr.x) * ratio) / GIMP_TRANSFORM_NEAR_Z,
+ (curr.y + (next.y - curr.y) * ratio) / GIMP_TRANSFORM_NEAR_Z };
+ }
+
+ curr = next;
+ curr_visible = next_visible;
+ }
+ }
+}
+
+/* same as gimp_transform_polygon(), but using GimpCoords as the vertex type,
+ * instead of GimpVector2.
+ */
+void
+gimp_transform_polygon_coords (const GimpMatrix3 *matrix,
+ const GimpCoords *vertices,
+ gint n_vertices,
+ gboolean closed,
+ GimpCoords *t_vertices,
+ gint *n_t_vertices)
+{
+ GimpVector3 curr;
+ gboolean curr_visible;
+ gint i;
+
+ g_return_if_fail (matrix != NULL);
+ g_return_if_fail (vertices != NULL);
+ g_return_if_fail (n_vertices >= 0);
+ g_return_if_fail (t_vertices != NULL);
+ g_return_if_fail (n_t_vertices != NULL);
+
+ *n_t_vertices = 0;
+
+ if (n_vertices == 0)
+ return;
+
+ curr.x = matrix->coeff[0][0] * vertices[0].x +
+ matrix->coeff[0][1] * vertices[0].y +
+ matrix->coeff[0][2];
+ curr.y = matrix->coeff[1][0] * vertices[0].x +
+ matrix->coeff[1][1] * vertices[0].y +
+ matrix->coeff[1][2];
+ curr.z = matrix->coeff[2][0] * vertices[0].x +
+ matrix->coeff[2][1] * vertices[0].y +
+ matrix->coeff[2][2];
+
+ curr_visible = (curr.z >= GIMP_TRANSFORM_NEAR_Z);
+
+ for (i = 0; i < n_vertices; i++)
+ {
+ if (curr_visible)
+ {
+ t_vertices[*n_t_vertices] = vertices[i];
+ t_vertices[*n_t_vertices].x = curr.x / curr.z;
+ t_vertices[*n_t_vertices].y = curr.y / curr.z;
+
+ (*n_t_vertices)++;
+ }
+
+ if (i < n_vertices - 1 || closed)
+ {
+ GimpVector3 next;
+ gboolean next_visible;
+ gint j = (i + 1) % n_vertices;
+
+ next.x = matrix->coeff[0][0] * vertices[j].x +
+ matrix->coeff[0][1] * vertices[j].y +
+ matrix->coeff[0][2];
+ next.y = matrix->coeff[1][0] * vertices[j].x +
+ matrix->coeff[1][1] * vertices[j].y +
+ matrix->coeff[1][2];
+ next.z = matrix->coeff[2][0] * vertices[j].x +
+ matrix->coeff[2][1] * vertices[j].y +
+ matrix->coeff[2][2];
+
+ next_visible = (next.z >= GIMP_TRANSFORM_NEAR_Z);
+
+ if (next_visible != curr_visible)
+ {
+ gdouble ratio = (curr.z - GIMP_TRANSFORM_NEAR_Z) / (curr.z - next.z);
+
+ gimp_coords_mix (1.0 - ratio, &vertices[i],
+ ratio, &vertices[j],
+ &t_vertices[*n_t_vertices]);
+
+ t_vertices[*n_t_vertices].x = (curr.x + (next.x - curr.x) * ratio) /
+ GIMP_TRANSFORM_NEAR_Z;
+ t_vertices[*n_t_vertices].y = (curr.y + (next.y - curr.y) * ratio) /
+ GIMP_TRANSFORM_NEAR_Z;
+
+ (*n_t_vertices)++;
+ }
+
+ curr = next;
+ curr_visible = next_visible;
+ }
+ }
+}
+
+/* returns the value of the polynomial 'poly', of degree 'degree', at 'x'. the
+ * coefficients of 'poly' should be specified in descending-degree order.
+ */
+static gdouble
+polynomial_eval (const gdouble *poly,
+ gint degree,
+ gdouble x)
+{
+ gdouble y = poly[0];
+ gint i;
+
+ for (i = 1; i <= degree; i++)
+ y = y * x + poly[i];
+
+ return y;
+}
+
+/* derives the polynomial 'poly', of degree 'degree'.
+ *
+ * returns the derivative in 'result'.
+ */
+static void
+polynomial_derive (const gdouble *poly,
+ gint degree,
+ gdouble *result)
+{
+ while (degree)
+ *result++ = *poly++ * degree--;
+}
+
+/* finds the real odd-multiplicity root of the polynomial 'poly', of degree
+ * 'degree', inside the range '(x1, x2)'.
+ *
+ * returns TRUE if such a root exists, and stores its value in '*root'.
+ *
+ * 'poly' shall be monotonic in the range '(x1, x2)'.
+ */
+static gboolean
+polynomial_odd_root (const gdouble *poly,
+ gint degree,
+ gdouble x1,
+ gdouble x2,
+ gdouble *root)
+{
+ gdouble y1;
+ gdouble y2;
+ gint i;
+
+ y1 = polynomial_eval (poly, degree, x1);
+ y2 = polynomial_eval (poly, degree, x2);
+
+ if (y1 * y2 > -EPSILON)
+ {
+ /* the two endpoints have the same sign, or one of them is zero. there's
+ * no root inside the range.
+ */
+ return FALSE;
+ }
+ else if (y1 > 0.0)
+ {
+ gdouble t;
+
+ /* if the first endpoint is positive, swap the endpoints, so that the
+ * first endpoint is always negative, and the second endpoint is always
+ * positive.
+ */
+
+ t = x1;
+ x1 = x2;
+ x2 = t;
+ }
+
+ /* approximate the root using binary search */
+ for (i = 0; i < 53; i++)
+ {
+ gdouble x = (x1 + x2) / 2.0;
+ gdouble y = polynomial_eval (poly, degree, x);
+
+ if (y > 0.0)
+ x2 = x;
+ else
+ x1 = x;
+ }
+
+ *root = (x1 + x2) / 2.0;
+
+ return TRUE;
+}
+
+/* finds the real odd-multiplicity roots of the polynomial 'poly', of degree
+ * 'degree', inside the range '(x1, x2)'.
+ *
+ * returns the roots in 'roots', in ascending order, and their count in
+ * 'n_roots'.
+ */
+static void
+polynomial_odd_roots (const gdouble *poly,
+ gint degree,
+ gdouble x1,
+ gdouble x2,
+ gdouble *roots,
+ gint *n_roots)
+{
+ *n_roots = 0;
+
+ /* find the real degree of the polynomial (skip any leading coefficients that
+ * are 0)
+ */
+ for (; degree && fabs (*poly) < EPSILON; poly++, degree--);
+
+ #define ADD_ROOT(root) \
+ do \
+ { \
+ gdouble r = (root); \
+ \
+ if (r > x1 && r < x2) \
+ roots[(*n_roots)++] = r; \
+ } \
+ while (FALSE)
+
+ switch (degree)
+ {
+ /* constant case */
+ case 0:
+ break;
+
+ /* linear case */
+ case 1:
+ ADD_ROOT (-poly[1] / poly[0]);
+ break;
+
+ /* quadratic case */
+ case 2:
+ {
+ gdouble s = SQR (poly[1]) - 4 * poly[0] * poly[2];
+
+ if (s > EPSILON)
+ {
+ s = sqrt (s);
+
+ if (poly[0] < 0.0)
+ s = -s;
+
+ ADD_ROOT ((-poly[1] - s) / (2.0 * poly[0]));
+ ADD_ROOT ((-poly[1] + s) / (2.0 * poly[0]));
+ }
+
+ break;
+ }
+
+ /* general case */
+ default:
+ {
+ gdouble deriv[degree];
+ gdouble deriv_roots[degree - 1];
+ gint n_deriv_roots;
+ gdouble a;
+ gdouble b;
+ gint i;
+
+ /* find the odd roots of the derivative, i.e., the local extrema of the
+ * polynomial
+ */
+ polynomial_derive (poly, degree, deriv);
+ polynomial_odd_roots (deriv, degree - 1, x1, x2,
+ deriv_roots, &n_deriv_roots);
+
+ /* search for roots between each consecutive pair of extrema, including
+ * the endpoints
+ */
+ a = x1;
+
+ for (i = 0; i <= n_deriv_roots; i++)
+ {
+ if (i < n_deriv_roots)
+ b = deriv_roots[i];
+ else
+ b = x2;
+
+ *n_roots += polynomial_odd_root (poly, degree, a, b,
+ &roots[*n_roots]);
+
+ a = b;
+ }
+
+ break;
+ }
+ }
+
+ #undef ADD_ROOT
+}
+
+/* clips the cubic bezier segment, defined by the four control points 'bezier',
+ * to the halfplane 'ax + by + c >= 0'.
+ *
+ * returns the clipped set of bezier segments in 'c_bezier', and their count in
+ * 'n_c_bezier'. the minimal possible number of clipped segments is 0, which
+ * happens when the entire segment is clipped. the maximal possible number of
+ * clipped segments is 2.
+ *
+ * if the first clipped segment is an initial segment of 'bezier', sets
+ * '*start_in' to TRUE, otherwise to FALSE. if the last clipped segment is a
+ * final segment of 'bezier', sets '*end_in' to TRUE, otherwise to FALSE.
+ *
+ * 'c_bezier' may not alias 'bezier'.
+ */
+static void
+clip_bezier (const GimpCoords bezier[4],
+ gdouble a,
+ gdouble b,
+ gdouble c,
+ GimpCoords c_bezier[2][4],
+ gint *n_c_bezier,
+ gboolean *start_in,
+ gboolean *end_in)
+{
+ gdouble dot[4];
+ gdouble poly[4];
+ gdouble roots[5];
+ gint n_roots;
+ gint n_positive;
+ gint i;
+
+ n_positive = 0;
+
+ for (i = 0; i < 4; i++)
+ {
+ dot[i] = a * bezier[i].x + b * bezier[i].y + c;
+
+ n_positive += (dot[i] >= 0.0);
+ }
+
+ if (n_positive == 0)
+ {
+ /* all points are out -- the entire segment is out */
+
+ *n_c_bezier = 0;
+ *start_in = FALSE;
+ *end_in = FALSE;
+
+ return;
+ }
+ else if (n_positive == 4)
+ {
+ /* all points are in -- the entire segment is in */
+
+ memcpy (c_bezier[0], bezier, sizeof (GimpCoords[4]));
+
+ *n_c_bezier = 1;
+ *start_in = TRUE;
+ *end_in = TRUE;
+
+ return;
+ }
+
+ /* find the points of intersection of the segment with the 'ax + by + c = 0'
+ * line
+ */
+ poly[0] = dot[3] - 3.0 * dot[2] + 3.0 * dot[1] - dot[0];
+ poly[1] = 3.0 * (dot[2] - 2.0 * dot[1] + dot[0]);
+ poly[2] = 3.0 * (dot[1] - dot[0]);
+ poly[3] = dot[0];
+
+ roots[0] = 0.0;
+ polynomial_odd_roots (poly, 3, 0.0, 1.0, roots + 1, &n_roots);
+ roots[++n_roots] = 1.0;
+
+ /* construct the list of segments that are inside the halfplane */
+ *n_c_bezier = 0;
+ *start_in = (polynomial_eval (poly, 3, roots[1] / 2.0) > 0.0);
+ *end_in = (*start_in + n_roots + 1) % 2;
+
+ for (i = ! *start_in; i < n_roots; i += 2)
+ {
+ gdouble t0 = roots[i];
+ gdouble t1 = roots[i + 1];
+
+ gimp_coords_interpolate_bezier_at (bezier, t0,
+ &c_bezier[*n_c_bezier][0],
+ &c_bezier[*n_c_bezier][1]);
+ gimp_coords_interpolate_bezier_at (bezier, t1,
+ &c_bezier[*n_c_bezier][3],
+ &c_bezier[*n_c_bezier][2]);
+
+ gimp_coords_mix (1.0, &c_bezier[*n_c_bezier][0],
+ (t1 - t0) / 3.0, &c_bezier[*n_c_bezier][1],
+ &c_bezier[*n_c_bezier][1]);
+ gimp_coords_mix (1.0, &c_bezier[*n_c_bezier][3],
+ (t0 - t1) / 3.0, &c_bezier[*n_c_bezier][2],
+ &c_bezier[*n_c_bezier][2]);
+
+ (*n_c_bezier)++;
+ }
+}
+
+/* transforms the cubic bezier segment, defined by the four control points
+ * 'bezier', by 'matrix', subdividing it as necessary to avoid diverging too
+ * much from the real transformed curve. at most 'depth' subdivisions are
+ * performed.
+ *
+ * appends the transformed sequence of bezier segments to 't_beziers'.
+ *
+ * 'bezier' shall be fully clipped to the near plane.
+ */
+static void
+transform_bezier_coords (const GimpMatrix3 *matrix,
+ const GimpCoords bezier[4],
+ GQueue *t_beziers,
+ gint depth)
+{
+ GimpCoords *t_bezier;
+ gint n;
+
+ /* check if we need to split the segment */
+ if (depth > 0)
+ {
+ GimpVector2 v[4];
+ GimpVector2 c[2];
+ GimpVector2 b;
+ gint i;
+
+ for (i = 0; i < 4; i++)
+ v[i] = (GimpVector2) { bezier[i].x, bezier[i].y };
+
+ gimp_vector2_sub (&c[0], &v[1], &v[0]);
+ gimp_vector2_sub (&c[1], &v[2], &v[3]);
+
+ gimp_vector2_sub (&b, &v[3], &v[0]);
+ gimp_vector2_mul (&b, 1.0 / gimp_vector2_inner_product (&b, &b));
+
+ for (i = 0; i < 2; i++)
+ {
+ /* split the segment if one of the control points is too far from the
+ * line connecting the anchors
+ */
+ if (fabs (gimp_vector2_cross_product (&c[i], &b).x) > 0.5)
+ {
+ GimpCoords mid_position;
+ GimpCoords mid_velocity;
+ GimpCoords sub[4];
+
+ gimp_coords_interpolate_bezier_at (bezier, 0.5,
+ &mid_position, &mid_velocity);
+
+ /* first half */
+ sub[0] = bezier[0];
+ sub[3] = mid_position;
+
+ gimp_coords_mix (0.5, &sub[0],
+ 0.5, &bezier[1],
+ &sub[1]);
+ gimp_coords_mix (1.0, &sub[3],
+ -1.0 / 6.0, &mid_velocity,
+ &sub[2]);
+
+ transform_bezier_coords (matrix, sub, t_beziers, depth - 1);
+
+ /* second half */
+ sub[0] = mid_position;
+ sub[3] = bezier[3];
+
+ gimp_coords_mix (1.0, &sub[0],
+ +1.0 / 6.0, &mid_velocity,
+ &sub[1]);
+ gimp_coords_mix (0.5, &sub[3],
+ 0.5, &bezier[2],
+ &sub[2]);
+
+ transform_bezier_coords (matrix, sub, t_beziers, depth - 1);
+
+ return;
+ }
+ }
+ }
+
+ /* transform the segment by transforming each of the individual points. note
+ * that, for non-affine transforms, this is only an approximation of the real
+ * transformed curve, but due to subdivision it should be good enough.
+ */
+ t_bezier = g_new (GimpCoords, 4);
+
+ /* note that while the segments themselves are clipped to the near plane,
+ * their control points may still get transformed behind the camera. we
+ * therefore clip the control points to the near plane as well, which is not
+ * too meaningful, but avoids erroneously transforming them behind the
+ * camera.
+ */
+ gimp_transform_polygon_coords (matrix, bezier, 2, FALSE,
+ t_bezier, &n);
+ gimp_transform_polygon_coords (matrix, bezier + 2, 2, FALSE,
+ t_bezier + 2, &n);
+
+ g_queue_push_tail (t_beziers, t_bezier);
+}
+
+/* transforms the cubic bezier segment, defined by the four control points
+ * 'bezier', by 'matrix', performing clipping by the near plane and subdividing
+ * as necessary.
+ *
+ * returns the transformed set of bezier-segment sequences in 't_beziers', as
+ * GQueues of GimpCoords[4] bezier-segments, and the number of sequences in
+ * 'n_t_beziers'. the minimal possible number of transformed sequences is 0,
+ * which happens when the entire segment is clipped. the maximal possible
+ * number of transformed sequences is 2. each sequence has at least one
+ * segment.
+ *
+ * if the first transformed segment is an initial segment of 'bezier', sets
+ * '*start_in' to TRUE, otherwise to FALSE. if the last transformed segment is
+ * a final segment of 'bezier', sets '*end_in' to TRUE, otherwise to FALSE.
+ */
+void
+gimp_transform_bezier_coords (const GimpMatrix3 *matrix,
+ const GimpCoords bezier[4],
+ GQueue *t_beziers[2],
+ gint *n_t_beziers,
+ gboolean *start_in,
+ gboolean *end_in)
+{
+ GimpCoords c_bezier[2][4];
+ gint i;
+
+ g_return_if_fail (matrix != NULL);
+ g_return_if_fail (bezier != NULL);
+ g_return_if_fail (t_beziers != NULL);
+ g_return_if_fail (n_t_beziers != NULL);
+ g_return_if_fail (start_in != NULL);
+ g_return_if_fail (end_in != NULL);
+
+ /* if the matrix is affine, transform the easy way */
+ if (gimp_matrix3_is_affine (matrix))
+ {
+ GimpCoords *t_bezier;
+
+ t_beziers[0] = g_queue_new ();
+ *n_t_beziers = 1;
+
+ t_bezier = g_new (GimpCoords, 1);
+ g_queue_push_tail (t_beziers[0], t_bezier);
+
+ for (i = 0; i < 4; i++)
+ {
+ t_bezier[i] = bezier[i];
+
+ gimp_matrix3_transform_point (matrix,
+ bezier[i].x, bezier[i].y,
+ &t_bezier[i].x, &t_bezier[i].y);
+ }
+
+ return;
+ }
+
+ /* clip the segment to the near plane */
+ clip_bezier (bezier,
+ matrix->coeff[2][0],
+ matrix->coeff[2][1],
+ matrix->coeff[2][2] - GIMP_TRANSFORM_NEAR_Z,
+ c_bezier, n_t_beziers,
+ start_in, end_in);
+
+ /* transform each of the resulting segments */
+ for (i = 0; i < *n_t_beziers; i++)
+ {
+ t_beziers[i] = g_queue_new ();
+
+ transform_bezier_coords (matrix, c_bezier[i], t_beziers[i], 3);
+ }
+}
diff --git a/app/core/gimp-transform-utils.h b/app/core/gimp-transform-utils.h
new file mode 100644
index 0000000..c2c1252
--- /dev/null
+++ b/app/core/gimp-transform-utils.h
@@ -0,0 +1,125 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-2001 Spencer Kimball, Peter Mattis, and others
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_TRANSFORM_UTILS_H__
+#define __GIMP_TRANSFORM_UTILS_H__
+
+
+#define GIMP_TRANSFORM_NEAR_Z 0.02
+
+
+void gimp_transform_get_rotate_center (gint x,
+ gint y,
+ gint width,
+ gint height,
+ gboolean auto_center,
+ gdouble *center_x,
+ gdouble *center_y);
+void gimp_transform_get_flip_axis (gint x,
+ gint y,
+ gint width,
+ gint height,
+ GimpOrientationType flip_type,
+ gboolean auto_center,
+ gdouble *axis);
+
+void gimp_transform_matrix_flip (GimpMatrix3 *matrix,
+ GimpOrientationType flip_type,
+ gdouble axis);
+void gimp_transform_matrix_flip_free (GimpMatrix3 *matrix,
+ gdouble x1,
+ gdouble y1,
+ gdouble x2,
+ gdouble y2);
+void gimp_transform_matrix_rotate (GimpMatrix3 *matrix,
+ GimpRotationType rotate_type,
+ gdouble center_x,
+ gdouble center_y);
+void gimp_transform_matrix_rotate_rect (GimpMatrix3 *matrix,
+ gint x,
+ gint y,
+ gint width,
+ gint height,
+ gdouble angle);
+void gimp_transform_matrix_rotate_center (GimpMatrix3 *matrix,
+ gdouble center_x,
+ gdouble center_y,
+ gdouble angle);
+void gimp_transform_matrix_scale (GimpMatrix3 *matrix,
+ gint x,
+ gint y,
+ gint width,
+ gint height,
+ gdouble t_x,
+ gdouble t_y,
+ gdouble t_width,
+ gdouble t_height);
+void gimp_transform_matrix_shear (GimpMatrix3 *matrix,
+ gint x,
+ gint y,
+ gint width,
+ gint height,
+ GimpOrientationType orientation,
+ gdouble amount);
+void gimp_transform_matrix_perspective (GimpMatrix3 *matrix,
+ gint x,
+ gint y,
+ gint width,
+ gint height,
+ gdouble t_x1,
+ gdouble t_y1,
+ gdouble t_x2,
+ gdouble t_y2,
+ gdouble t_x3,
+ gdouble t_y3,
+ gdouble t_x4,
+ gdouble t_y4);
+gboolean gimp_transform_matrix_generic (GimpMatrix3 *matrix,
+ const GimpVector2 input_points[4],
+ const GimpVector2 output_points[4]);
+
+gboolean gimp_transform_polygon_is_convex (gdouble x1,
+ gdouble y1,
+ gdouble x2,
+ gdouble y2,
+ gdouble x3,
+ gdouble y3,
+ gdouble x4,
+ gdouble y4);
+
+void gimp_transform_polygon (const GimpMatrix3 *matrix,
+ const GimpVector2 *vertices,
+ gint n_vertices,
+ gboolean closed,
+ GimpVector2 *t_vertices,
+ gint *n_t_vertices);
+void gimp_transform_polygon_coords (const GimpMatrix3 *matrix,
+ const GimpCoords *vertices,
+ gint n_vertices,
+ gboolean closed,
+ GimpCoords *t_vertices,
+ gint *n_t_vertices);
+
+void gimp_transform_bezier_coords (const GimpMatrix3 *matrix,
+ const GimpCoords bezier[4],
+ GQueue *t_beziers[2],
+ gint *n_t_beziers,
+ gboolean *start_in,
+ gboolean *end_in);
+
+
+#endif /* __GIMP_TRANSFORM_UTILS_H__ */
diff --git a/app/core/gimp-units.c b/app/core/gimp-units.c
new file mode 100644
index 0000000..22caca1
--- /dev/null
+++ b/app/core/gimp-units.c
@@ -0,0 +1,488 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-1999 Spencer Kimball and Peter Mattis
+ *
+ * gimpunit.c
+ * Copyright (C) 1999-2000 Michael Natterer <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+/* This file contains functions to load & save the file containing the
+ * user-defined size units, when the application starts/finished.
+ */
+
+#include "config.h"
+
+#include <gio/gio.h>
+
+#include "libgimpbase/gimpbase.h"
+#include "libgimpbase/gimpbase-private.h"
+#include "libgimpconfig/gimpconfig.h"
+
+#include "core-types.h"
+
+#include "gimp.h"
+#include "gimp-units.h"
+#include "gimpunit.h"
+
+#include "config/gimpconfig-file.h"
+
+#include "gimp-intl.h"
+
+
+/*
+ * All deserialize functions return G_TOKEN_LEFT_PAREN on success,
+ * or the GTokenType they would have expected but didn't get.
+ */
+
+static GTokenType gimp_unitrc_unit_info_deserialize (GScanner *scanner,
+ Gimp *gimp);
+
+
+static Gimp *the_unit_gimp = NULL;
+
+
+static gint
+gimp_units_get_number_of_units (void)
+{
+ return _gimp_unit_get_number_of_units (the_unit_gimp);
+}
+
+static gint
+gimp_units_get_number_of_built_in_units (void)
+{
+ return GIMP_UNIT_END;
+}
+
+static GimpUnit
+gimp_units_unit_new (gchar *identifier,
+ gdouble factor,
+ gint digits,
+ gchar *symbol,
+ gchar *abbreviation,
+ gchar *singular,
+ gchar *plural)
+{
+ return _gimp_unit_new (the_unit_gimp,
+ identifier,
+ factor,
+ digits,
+ symbol,
+ abbreviation,
+ singular,
+ plural);
+}
+
+static gboolean
+gimp_units_unit_get_deletion_flag (GimpUnit unit)
+{
+ return _gimp_unit_get_deletion_flag (the_unit_gimp, unit);
+}
+
+static void
+gimp_units_unit_set_deletion_flag (GimpUnit unit,
+ gboolean deletion_flag)
+{
+ _gimp_unit_set_deletion_flag (the_unit_gimp, unit, deletion_flag);
+}
+
+static gdouble
+gimp_units_unit_get_factor (GimpUnit unit)
+{
+ return _gimp_unit_get_factor (the_unit_gimp, unit);
+}
+
+static gint
+gimp_units_unit_get_digits (GimpUnit unit)
+{
+ return _gimp_unit_get_digits (the_unit_gimp, unit);
+}
+
+static const gchar *
+gimp_units_unit_get_identifier (GimpUnit unit)
+{
+ return _gimp_unit_get_identifier (the_unit_gimp, unit);
+}
+
+static const gchar *
+gimp_units_unit_get_symbol (GimpUnit unit)
+{
+ return _gimp_unit_get_symbol (the_unit_gimp, unit);
+}
+
+static const gchar *
+gimp_units_unit_get_abbreviation (GimpUnit unit)
+{
+ return _gimp_unit_get_abbreviation (the_unit_gimp, unit);
+}
+
+static const gchar *
+gimp_units_unit_get_singular (GimpUnit unit)
+{
+ return _gimp_unit_get_singular (the_unit_gimp, unit);
+}
+
+static const gchar *
+gimp_units_unit_get_plural (GimpUnit unit)
+{
+ return _gimp_unit_get_plural (the_unit_gimp, unit);
+}
+
+void
+gimp_units_init (Gimp *gimp)
+{
+ GimpUnitVtable vtable;
+
+ g_return_if_fail (GIMP_IS_GIMP (gimp));
+ g_return_if_fail (the_unit_gimp == NULL);
+
+ the_unit_gimp = gimp;
+
+ vtable.unit_get_number_of_units = gimp_units_get_number_of_units;
+ vtable.unit_get_number_of_built_in_units = gimp_units_get_number_of_built_in_units;
+ vtable.unit_new = gimp_units_unit_new;
+ vtable.unit_get_deletion_flag = gimp_units_unit_get_deletion_flag;
+ vtable.unit_set_deletion_flag = gimp_units_unit_set_deletion_flag;
+ vtable.unit_get_factor = gimp_units_unit_get_factor;
+ vtable.unit_get_digits = gimp_units_unit_get_digits;
+ vtable.unit_get_identifier = gimp_units_unit_get_identifier;
+ vtable.unit_get_symbol = gimp_units_unit_get_symbol;
+ vtable.unit_get_abbreviation = gimp_units_unit_get_abbreviation;
+ vtable.unit_get_singular = gimp_units_unit_get_singular;
+ vtable.unit_get_plural = gimp_units_unit_get_plural;
+
+ gimp_base_init (&vtable);
+
+ gimp->user_units = NULL;
+ gimp->n_user_units = 0;
+}
+
+void
+gimp_units_exit (Gimp *gimp)
+{
+ g_return_if_fail (GIMP_IS_GIMP (gimp));
+
+ gimp_user_units_free (gimp);
+}
+
+
+/* unitrc functions **********/
+
+enum
+{
+ UNIT_INFO = 1,
+ UNIT_FACTOR,
+ UNIT_DIGITS,
+ UNIT_SYMBOL,
+ UNIT_ABBREV,
+ UNIT_SINGULAR,
+ UNIT_PLURAL
+};
+
+void
+gimp_unitrc_load (Gimp *gimp)
+{
+ GFile *file;
+ GScanner *scanner;
+ GTokenType token;
+ GError *error = NULL;
+
+ g_return_if_fail (GIMP_IS_GIMP (gimp));
+
+ file = gimp_directory_file ("unitrc", NULL);
+
+ if (gimp->be_verbose)
+ g_print ("Parsing '%s'\n", gimp_file_get_utf8_name (file));
+
+ scanner = gimp_scanner_new_gfile (file, &error);
+
+ if (! scanner && error->code == GIMP_CONFIG_ERROR_OPEN_ENOENT)
+ {
+ g_clear_error (&error);
+ g_object_unref (file);
+
+ file = gimp_sysconf_directory_file ("unitrc", NULL);
+
+ scanner = gimp_scanner_new_gfile (file, NULL);
+ }
+
+ if (! scanner)
+ {
+ g_clear_error (&error);
+ g_object_unref (file);
+ return;
+ }
+
+ g_scanner_scope_add_symbol (scanner, 0,
+ "unit-info", GINT_TO_POINTER (UNIT_INFO));
+ g_scanner_scope_add_symbol (scanner, UNIT_INFO,
+ "factor", GINT_TO_POINTER (UNIT_FACTOR));
+ g_scanner_scope_add_symbol (scanner, UNIT_INFO,
+ "digits", GINT_TO_POINTER (UNIT_DIGITS));
+ g_scanner_scope_add_symbol (scanner, UNIT_INFO,
+ "symbol", GINT_TO_POINTER (UNIT_SYMBOL));
+ g_scanner_scope_add_symbol (scanner, UNIT_INFO,
+ "abbreviation", GINT_TO_POINTER (UNIT_ABBREV));
+ g_scanner_scope_add_symbol (scanner, UNIT_INFO,
+ "singular", GINT_TO_POINTER (UNIT_SINGULAR));
+ g_scanner_scope_add_symbol (scanner, UNIT_INFO,
+ "plural", GINT_TO_POINTER (UNIT_PLURAL));
+
+ token = G_TOKEN_LEFT_PAREN;
+
+ while (g_scanner_peek_next_token (scanner) == token)
+ {
+ token = g_scanner_get_next_token (scanner);
+
+ switch (token)
+ {
+ case G_TOKEN_LEFT_PAREN:
+ token = G_TOKEN_SYMBOL;
+ break;
+
+ case G_TOKEN_SYMBOL:
+ if (scanner->value.v_symbol == GINT_TO_POINTER (UNIT_INFO))
+ {
+ g_scanner_set_scope (scanner, UNIT_INFO);
+ token = gimp_unitrc_unit_info_deserialize (scanner, gimp);
+
+ if (token == G_TOKEN_RIGHT_PAREN)
+ g_scanner_set_scope (scanner, 0);
+ }
+ break;
+
+ case G_TOKEN_RIGHT_PAREN:
+ token = G_TOKEN_LEFT_PAREN;
+ break;
+
+ default: /* do nothing */
+ break;
+ }
+ }
+
+ if (token != G_TOKEN_LEFT_PAREN)
+ {
+ g_scanner_get_next_token (scanner);
+ g_scanner_unexp_token (scanner, token, NULL, NULL, NULL,
+ _("fatal parse error"), TRUE);
+
+ gimp_message_literal (gimp, NULL, GIMP_MESSAGE_ERROR, error->message);
+ g_clear_error (&error);
+
+ gimp_config_file_backup_on_error (file, "unitrc", NULL);
+ }
+
+ gimp_scanner_destroy (scanner);
+ g_object_unref (file);
+}
+
+void
+gimp_unitrc_save (Gimp *gimp)
+{
+ GimpConfigWriter *writer;
+ GFile *file;
+ gint i;
+ GError *error = NULL;
+
+ g_return_if_fail (GIMP_IS_GIMP (gimp));
+
+ file = gimp_directory_file ("unitrc", NULL);
+
+ if (gimp->be_verbose)
+ g_print ("Writing '%s'\n", gimp_file_get_utf8_name (file));
+
+ writer =
+ gimp_config_writer_new_gfile (file,
+ TRUE,
+ "GIMP units\n\n"
+ "This file contains the user unit database. "
+ "You can edit this list with the unit "
+ "editor. You are not supposed to edit it "
+ "manually, but of course you can do.\n"
+ "This file will be entirely rewritten each "
+ "time you exit.",
+ NULL);
+
+ g_object_unref (file);
+
+ if (!writer)
+ return;
+
+ /* save user defined units */
+ for (i = _gimp_unit_get_number_of_built_in_units (gimp);
+ i < _gimp_unit_get_number_of_units (gimp);
+ i++)
+ {
+ if (_gimp_unit_get_deletion_flag (gimp, i) == FALSE)
+ {
+ gchar buf[G_ASCII_DTOSTR_BUF_SIZE];
+
+ gimp_config_writer_open (writer, "unit-info");
+ gimp_config_writer_string (writer,
+ _gimp_unit_get_identifier (gimp, i));
+
+ gimp_config_writer_open (writer, "factor");
+ gimp_config_writer_print (writer,
+ g_ascii_dtostr (buf, sizeof (buf),
+ _gimp_unit_get_factor (gimp, i)),
+ -1);
+ gimp_config_writer_close (writer);
+
+ gimp_config_writer_open (writer, "digits");
+ gimp_config_writer_printf (writer,
+ "%d", _gimp_unit_get_digits (gimp, i));
+ gimp_config_writer_close (writer);
+
+ gimp_config_writer_open (writer, "symbol");
+ gimp_config_writer_string (writer,
+ _gimp_unit_get_symbol (gimp, i));
+ gimp_config_writer_close (writer);
+
+ gimp_config_writer_open (writer, "abbreviation");
+ gimp_config_writer_string (writer,
+ _gimp_unit_get_abbreviation (gimp, i));
+ gimp_config_writer_close (writer);
+
+ gimp_config_writer_open (writer, "singular");
+ gimp_config_writer_string (writer,
+ _gimp_unit_get_singular (gimp, i));
+ gimp_config_writer_close (writer);
+
+ gimp_config_writer_open (writer, "plural");
+ gimp_config_writer_string (writer,
+ _gimp_unit_get_plural (gimp, i));
+ gimp_config_writer_close (writer);
+
+ gimp_config_writer_close (writer);
+ }
+ }
+
+ if (! gimp_config_writer_finish (writer, "end of units", &error))
+ {
+ gimp_message_literal (gimp, NULL, GIMP_MESSAGE_ERROR, error->message);
+ g_clear_error (&error);
+ }
+}
+
+
+/* private functions */
+
+static GTokenType
+gimp_unitrc_unit_info_deserialize (GScanner *scanner,
+ Gimp *gimp)
+{
+ gchar *identifier = NULL;
+ gdouble factor = 1.0;
+ gint digits = 2.0;
+ gchar *symbol = NULL;
+ gchar *abbreviation = NULL;
+ gchar *singular = NULL;
+ gchar *plural = NULL;
+ GTokenType token;
+
+ if (! gimp_scanner_parse_string (scanner, &identifier))
+ return G_TOKEN_STRING;
+
+ token = G_TOKEN_LEFT_PAREN;
+
+ while (g_scanner_peek_next_token (scanner) == token)
+ {
+ token = g_scanner_get_next_token (scanner);
+
+ switch (token)
+ {
+ case G_TOKEN_LEFT_PAREN:
+ token = G_TOKEN_SYMBOL;
+ break;
+
+ case G_TOKEN_SYMBOL:
+ switch (GPOINTER_TO_INT (scanner->value.v_symbol))
+ {
+ case UNIT_FACTOR:
+ token = G_TOKEN_FLOAT;
+ if (! gimp_scanner_parse_float (scanner, &factor))
+ goto cleanup;
+ break;
+
+ case UNIT_DIGITS:
+ token = G_TOKEN_INT;
+ if (! gimp_scanner_parse_int (scanner, &digits))
+ goto cleanup;
+ break;
+
+ case UNIT_SYMBOL:
+ token = G_TOKEN_STRING;
+ if (! gimp_scanner_parse_string (scanner, &symbol))
+ goto cleanup;
+ break;
+
+ case UNIT_ABBREV:
+ token = G_TOKEN_STRING;
+ if (! gimp_scanner_parse_string (scanner, &abbreviation))
+ goto cleanup;
+ break;
+
+ case UNIT_SINGULAR:
+ token = G_TOKEN_STRING;
+ if (! gimp_scanner_parse_string (scanner, &singular))
+ goto cleanup;
+ break;
+
+ case UNIT_PLURAL:
+ token = G_TOKEN_STRING;
+ if (! gimp_scanner_parse_string (scanner, &plural))
+ goto cleanup;
+ break;
+
+ default:
+ break;
+ }
+ token = G_TOKEN_RIGHT_PAREN;
+ break;
+
+ case G_TOKEN_RIGHT_PAREN:
+ token = G_TOKEN_LEFT_PAREN;
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ if (token == G_TOKEN_LEFT_PAREN)
+ {
+ token = G_TOKEN_RIGHT_PAREN;
+
+ if (g_scanner_peek_next_token (scanner) == token)
+ {
+ GimpUnit unit = _gimp_unit_new (gimp,
+ identifier, factor, digits,
+ symbol, abbreviation,
+ singular, plural);
+
+ /* make the unit definition persistent */
+ _gimp_unit_set_deletion_flag (gimp, unit, FALSE);
+ }
+ }
+
+ cleanup:
+
+ g_free (identifier);
+ g_free (symbol);
+ g_free (abbreviation);
+ g_free (singular);
+ g_free (plural);
+
+ return token;
+}
diff --git a/app/core/gimp-units.h b/app/core/gimp-units.h
new file mode 100644
index 0000000..64f819a
--- /dev/null
+++ b/app/core/gimp-units.h
@@ -0,0 +1,29 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_UNITS_H__
+#define __GIMP_UNITS_H__
+
+
+void gimp_units_init (Gimp *gimp);
+void gimp_units_exit (Gimp *gimp);
+
+void gimp_unitrc_load (Gimp *gimp);
+void gimp_unitrc_save (Gimp *gimp);
+
+
+#endif /* __GIMP_UNITS_H__ */
diff --git a/app/core/gimp-user-install.c b/app/core/gimp-user-install.c
new file mode 100644
index 0000000..2b1c910
--- /dev/null
+++ b/app/core/gimp-user-install.c
@@ -0,0 +1,977 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimp-user-install.c
+ * Copyright (C) 2000-2008 Michael Natterer and Sven Neumann
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+/* This file contains functions to help migrate the settings from a
+ * previous GIMP version to be used with the current (newer) version.
+ */
+
+#include "config.h"
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <sys/types.h>
+
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#ifdef PLATFORM_OSX
+#include <AppKit/AppKit.h>
+#endif
+
+#include <gio/gio.h>
+#include <glib/gstdio.h>
+
+#ifdef G_OS_WIN32
+#include <libgimpbase/gimpwin32-io.h>
+#endif
+
+#include "libgimpbase/gimpbase.h"
+
+#include "core-types.h"
+
+#include "config/gimpconfig-file.h"
+#include "config/gimprc.h"
+
+#include "gimp-templates.h"
+#include "gimp-tags.h"
+#include "gimp-user-install.h"
+
+#include "gimp-intl.h"
+
+
+struct _GimpUserInstall
+{
+ GObject *gimp;
+
+ gboolean verbose;
+
+ gchar *old_dir;
+ gint old_major;
+ gint old_minor;
+
+ const gchar *migrate;
+
+ GimpUserInstallLogFunc log;
+ gpointer log_data;
+};
+
+typedef enum
+{
+ USER_INSTALL_MKDIR, /* Create the directory */
+ USER_INSTALL_COPY /* Copy from sysconf directory */
+} GimpUserInstallAction;
+
+static const struct
+{
+ const gchar *name;
+ GimpUserInstallAction action;
+}
+gimp_user_install_items[] =
+{
+ { "menurc", USER_INSTALL_COPY },
+ { "brushes", USER_INSTALL_MKDIR },
+ { "dynamics", USER_INSTALL_MKDIR },
+ { "fonts", USER_INSTALL_MKDIR },
+ { "gradients", USER_INSTALL_MKDIR },
+ { "palettes", USER_INSTALL_MKDIR },
+ { "patterns", USER_INSTALL_MKDIR },
+ { "tool-presets", USER_INSTALL_MKDIR },
+ { "plug-ins", USER_INSTALL_MKDIR },
+ { "modules", USER_INSTALL_MKDIR },
+ { "interpreters", USER_INSTALL_MKDIR },
+ { "environ", USER_INSTALL_MKDIR },
+ { "scripts", USER_INSTALL_MKDIR },
+ { "templates", USER_INSTALL_MKDIR },
+ { "themes", USER_INSTALL_MKDIR },
+ { "icons", USER_INSTALL_MKDIR },
+ { "tmp", USER_INSTALL_MKDIR },
+ { "curves", USER_INSTALL_MKDIR },
+ { "levels", USER_INSTALL_MKDIR },
+ { "filters", USER_INSTALL_MKDIR },
+ { "fractalexplorer", USER_INSTALL_MKDIR },
+ { "gfig", USER_INSTALL_MKDIR },
+ { "gflare", USER_INSTALL_MKDIR },
+ { "gimpressionist", USER_INSTALL_MKDIR }
+};
+
+
+static gboolean user_install_detect_old (GimpUserInstall *install,
+ const gchar *gimp_dir);
+static gchar * user_install_old_style_gimpdir (void);
+
+static void user_install_log (GimpUserInstall *install,
+ const gchar *format,
+ ...) G_GNUC_PRINTF (2, 3);
+static void user_install_log_newline (GimpUserInstall *install);
+static void user_install_log_error (GimpUserInstall *install,
+ GError **error);
+
+static gboolean user_install_mkdir (GimpUserInstall *install,
+ const gchar *dirname);
+static gboolean user_install_mkdir_with_parents (GimpUserInstall *install,
+ const gchar *dirname);
+static gboolean user_install_file_copy (GimpUserInstall *install,
+ const gchar *source,
+ const gchar *dest,
+ const gchar *old_options_regexp,
+ GRegexEvalCallback update_callback);
+static gboolean user_install_dir_copy (GimpUserInstall *install,
+ gint level,
+ const gchar *source,
+ const gchar *base,
+ const gchar *update_pattern,
+ GRegexEvalCallback update_callback);
+
+static gboolean user_install_create_files (GimpUserInstall *install);
+static gboolean user_install_migrate_files (GimpUserInstall *install);
+
+
+/* public functions */
+
+GimpUserInstall *
+gimp_user_install_new (GObject *gimp,
+ gboolean verbose)
+{
+ GimpUserInstall *install = g_slice_new0 (GimpUserInstall);
+
+ install->gimp = gimp;
+ install->verbose = verbose;
+
+ user_install_detect_old (install, gimp_directory ());
+
+#ifdef PLATFORM_OSX
+ /* The config path on OSX has for a very short time frame (2.8.2 only)
+ been "~/Library/GIMP". It changed to "~/Library/Application Support"
+ in 2.8.4 and was in the home folder (as was other UNIX) before. */
+
+ if (! install->old_dir)
+ {
+ gchar *dir;
+ NSAutoreleasePool *pool;
+ NSArray *path;
+ NSString *library_dir;
+
+ pool = [[NSAutoreleasePool alloc] init];
+
+ path = NSSearchPathForDirectoriesInDomains (NSLibraryDirectory,
+ NSUserDomainMask, YES);
+ library_dir = [path objectAtIndex:0];
+
+ dir = g_build_filename ([library_dir UTF8String],
+ GIMPDIR, GIMP_USER_VERSION, NULL);
+
+ [pool drain];
+
+ user_install_detect_old (install, dir);
+ g_free (dir);
+ }
+
+#endif
+
+ if (! install->old_dir)
+ {
+ /* if the default XDG-style config directory was not found, try
+ * the "old-style" path in the home folder.
+ */
+ gchar *dir = user_install_old_style_gimpdir ();
+ user_install_detect_old (install, dir);
+ g_free (dir);
+ }
+
+ return install;
+}
+
+gboolean
+gimp_user_install_run (GimpUserInstall *install)
+{
+ gchar *dirname;
+
+ g_return_val_if_fail (install != NULL, FALSE);
+
+ dirname = g_filename_display_name (gimp_directory ());
+
+ if (install->migrate)
+ user_install_log (install,
+ _("It seems you have used GIMP %s before. "
+ "GIMP will now migrate your user settings to '%s'."),
+ install->migrate, dirname);
+ else
+ user_install_log (install,
+ _("It appears that you are using GIMP for the "
+ "first time. GIMP will now create a folder "
+ "named '%s' and copy some files to it."),
+ dirname);
+
+ g_free (dirname);
+
+ user_install_log_newline (install);
+
+ if (! user_install_mkdir_with_parents (install, gimp_directory ()))
+ return FALSE;
+
+ if (install->migrate)
+ if (! user_install_migrate_files (install))
+ return FALSE;
+
+ return user_install_create_files (install);
+}
+
+void
+gimp_user_install_free (GimpUserInstall *install)
+{
+ g_return_if_fail (install != NULL);
+
+ g_free (install->old_dir);
+
+ g_slice_free (GimpUserInstall, install);
+}
+
+void
+gimp_user_install_set_log_handler (GimpUserInstall *install,
+ GimpUserInstallLogFunc log,
+ gpointer user_data)
+{
+ g_return_if_fail (install != NULL);
+
+ install->log = log;
+ install->log_data = user_data;
+}
+
+
+/* Local functions */
+
+static gboolean
+user_install_detect_old (GimpUserInstall *install,
+ const gchar *gimp_dir)
+{
+ gchar *dir = g_strdup (gimp_dir);
+ gchar *version;
+ gboolean migrate = FALSE;
+
+ version = strstr (dir, GIMP_APP_VERSION);
+
+ if (version)
+ {
+ gint i;
+
+ for (i = (GIMP_MINOR_VERSION & ~1); i >= 0; i -= 2)
+ {
+ /* we assume that GIMP_APP_VERSION is in the form '2.x' */
+ g_snprintf (version + 2, 2, "%d", i);
+
+ migrate = g_file_test (dir, G_FILE_TEST_IS_DIR);
+
+ if (migrate)
+ {
+#ifdef GIMP_UNSTABLE
+ g_printerr ("gimp-user-install: migrating from %s\n", dir);
+#endif
+ install->old_major = 2;
+ install->old_minor = i;
+
+ break;
+ }
+ }
+ }
+
+ if (migrate)
+ {
+ install->old_dir = dir;
+ install->migrate = (const gchar *) version;
+ }
+ else
+ {
+ g_free (dir);
+ }
+
+ return migrate;
+}
+
+static gchar *
+user_install_old_style_gimpdir (void)
+{
+ const gchar *home_dir = g_get_home_dir ();
+ gchar *gimp_dir = NULL;
+
+ if (home_dir)
+ {
+ gimp_dir = g_build_filename (home_dir, ".gimp-" GIMP_APP_VERSION, NULL);
+ }
+ else
+ {
+ gchar *user_name = g_strdup (g_get_user_name ());
+ gchar *subdir_name;
+
+#ifdef G_OS_WIN32
+ gchar *p = user_name;
+
+ while (*p)
+ {
+ /* Replace funny characters in the user name with an
+ * underscore. The code below also replaces some
+ * characters that in fact are legal in file names, but
+ * who cares, as long as the definitely illegal ones are
+ * caught.
+ */
+ if (!g_ascii_isalnum (*p) && !strchr ("-.,@=", *p))
+ *p = '_';
+ p++;
+ }
+#endif
+
+#ifndef G_OS_WIN32
+ g_message ("warning: no home directory.");
+#endif
+ subdir_name = g_strconcat (".gimp-" GIMP_APP_VERSION ".", user_name, NULL);
+ gimp_dir = g_build_filename (gimp_data_directory (),
+ subdir_name,
+ NULL);
+ g_free (user_name);
+ g_free (subdir_name);
+ }
+
+ return gimp_dir;
+}
+
+static void
+user_install_log (GimpUserInstall *install,
+ const gchar *format,
+ ...)
+{
+ va_list args;
+
+ va_start (args, format);
+
+ if (format)
+ {
+ gchar *message = g_strdup_vprintf (format, args);
+
+ if (install->verbose)
+ g_print ("%s\n", message);
+
+ if (install->log)
+ install->log (message, FALSE, install->log_data);
+
+ g_free (message);
+ }
+
+ va_end (args);
+}
+
+static void
+user_install_log_newline (GimpUserInstall *install)
+{
+ if (install->verbose)
+ g_print ("\n");
+
+ if (install->log)
+ install->log (NULL, FALSE, install->log_data);
+}
+
+static void
+user_install_log_error (GimpUserInstall *install,
+ GError **error)
+{
+ if (error && *error)
+ {
+ const gchar *message = ((*error)->message ?
+ (*error)->message : "(unknown error)");
+
+ if (install->log)
+ install->log (message, TRUE, install->log_data);
+ else
+ g_print ("error: %s\n", message);
+
+ g_clear_error (error);
+ }
+}
+
+static gboolean
+user_install_file_copy (GimpUserInstall *install,
+ const gchar *source,
+ const gchar *dest,
+ const gchar *old_options_regexp,
+ GRegexEvalCallback update_callback)
+{
+ GError *error = NULL;
+ gboolean success;
+
+ user_install_log (install, _("Copying file '%s' from '%s'..."),
+ gimp_filename_to_utf8 (dest),
+ gimp_filename_to_utf8 (source));
+
+ success = gimp_config_file_copy (source, dest, old_options_regexp, update_callback, &error);
+
+ user_install_log_error (install, &error);
+
+ return success;
+}
+
+static gboolean
+user_install_mkdir (GimpUserInstall *install,
+ const gchar *dirname)
+{
+ user_install_log (install, _("Creating folder '%s'..."),
+ gimp_filename_to_utf8 (dirname));
+
+ if (g_mkdir (dirname,
+ S_IRUSR | S_IWUSR | S_IXUSR |
+ S_IRGRP | S_IXGRP |
+ S_IROTH | S_IXOTH) == -1)
+ {
+ GError *error = NULL;
+
+ g_set_error (&error, G_FILE_ERROR, g_file_error_from_errno (errno),
+ _("Cannot create folder '%s': %s"),
+ gimp_filename_to_utf8 (dirname), g_strerror (errno));
+
+ user_install_log_error (install, &error);
+
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+user_install_mkdir_with_parents (GimpUserInstall *install,
+ const gchar *dirname)
+{
+ user_install_log (install, _("Creating folder '%s'..."),
+ gimp_filename_to_utf8 (dirname));
+
+ if (g_mkdir_with_parents (dirname,
+ S_IRUSR | S_IWUSR | S_IXUSR |
+ S_IRGRP | S_IXGRP |
+ S_IROTH | S_IXOTH) == -1)
+ {
+ GError *error = NULL;
+
+ g_set_error (&error, G_FILE_ERROR, g_file_error_from_errno (errno),
+ _("Cannot create folder '%s': %s"),
+ gimp_filename_to_utf8 (dirname), g_strerror (errno));
+
+ user_install_log_error (install, &error);
+
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/* The regexp pattern of all options changed from menurc of GIMP 2.8.
+ * Add any pattern that we want to recognize for replacement in the menurc of
+ * the next release
+ */
+#define MENURC_OVER20_UPDATE_PATTERN \
+ "\"<Actions>/buffers/buffers-paste-as-new\"" "|" \
+ "\"<Actions>/edit/edit-paste-as-new\"" "|" \
+ "\"<Actions>/file/file-export\"" "|" \
+ "\"<Actions>/file/file-export-to\"" "|" \
+ "\"<Actions>/layers/layers-text-tool\"" "|" \
+ "\"<Actions>/plug-in/plug-in-gauss\"" "|" \
+ "\"<Actions>/tools/tools-value-[1-4]-.*\"" "|" \
+ "\"<Actions>/vectors/vectors-path-tool\"" "|" \
+ "\"<Actions>/tools/tools-blend\""
+
+/**
+ * callback to use for updating a menurc from GIMP over 2.0.
+ * data is unused (always NULL).
+ * The updated value will be matched line by line.
+ */
+static gboolean
+user_update_menurc_over20 (const GMatchInfo *matched_value,
+ GString *new_value,
+ gpointer data)
+{
+ gchar *match = g_match_info_fetch (matched_value, 0);
+
+ /* "*-paste-as-new" renamed to "*-paste-as-new-image"
+ */
+ if (g_strcmp0 (match, "\"<Actions>/buffers/buffers-paste-as-new\"") == 0)
+ {
+ g_string_append (new_value, "\"<Actions>/buffers/buffers-paste-as-new-image\"");
+ }
+ else if (g_strcmp0 (match, "\"<Actions>/edit/edit-paste-as-new\"") == 0)
+ {
+ g_string_append (new_value, "\"<Actions>/edit/edit-paste-as-new-image\"");
+ }
+ /* file-export-* changes to follow file-save-* patterns. Actions
+ * available since GIMP 2.8, changed for 2.10 in commit 4b14ed2.
+ */
+ else if (g_strcmp0 (match, "\"<Actions>/file/file-export\"") == 0)
+ {
+ g_string_append (new_value, "\"<Actions>/file/file-export-as\"");
+ }
+ else if (g_strcmp0 (match, "\"<Actions>/file/file-export-to\"") == 0)
+ {
+ g_string_append (new_value, "\"<Actions>/file/file-export\"");
+ }
+ else if (g_strcmp0 (match, "\"<Actions>/layers/layers-text-tool\"") == 0)
+ {
+ g_string_append (new_value, "\"<Actions>/layers/layers-edit\"");
+ }
+ /* plug-in-gauss doesn't exist anymore since commit ff59aebbe88.
+ * The expected replacement would be filters-gaussian-blur which is
+ * gegl:gaussian-blur operation. See also bug 775931.
+ */
+ else if (g_strcmp0 (match, "\"<Actions>/plug-in/plug-in-gauss\"") == 0)
+ {
+ g_string_append (new_value, "\"<Actions>/filters/filters-gaussian-blur\"");
+ }
+ /* Tools settings renamed more user-friendly. Actions available
+ * since GIMP 2.4, changed for 2.10 in commit 0bdb747.
+ */
+ else if (g_str_has_prefix (match, "\"<Actions>/tools/tools-value-1-"))
+ {
+ g_string_append (new_value, "\"<Actions>/tools/tools-opacity-");
+ g_string_append (new_value, match + 31);
+ }
+ else if (g_str_has_prefix (match, "\"<Actions>/tools/tools-value-2-"))
+ {
+ g_string_append (new_value, "\"<Actions>/tools/tools-size-");
+ g_string_append (new_value, match + 31);
+ }
+ else if (g_str_has_prefix (match, "\"<Actions>/tools/tools-value-3-"))
+ {
+ g_string_append (new_value, "\"<Actions>/tools/tools-aspect-");
+ g_string_append (new_value, match + 31);
+ }
+ else if (g_str_has_prefix (match, "\"<Actions>/tools/tools-value-4-"))
+ {
+ g_string_append (new_value, "\"<Actions>/tools/tools-angle-");
+ g_string_append (new_value, match + 31);
+ }
+ else if (g_strcmp0 (match, "\"<Actions>/vectors/vectors-path-tool\"") == 0)
+ {
+ g_string_append (new_value, "\"<Actions>/vectors/vectors-edit\"");
+ }
+ else if (g_strcmp0 (match, "\"<Actions>/tools/tools-blend\"") == 0)
+ {
+ g_string_append (new_value, "\"<Actions>/tools/tools-gradient\"");
+ }
+ /* Should not happen. Just in case we match something unexpected by
+ * mistake.
+ */
+ else
+ {
+ g_message ("(WARNING) %s: invalid match \"%s\"", G_STRFUNC, match);
+ g_string_append (new_value, match);
+ }
+
+ g_free (match);
+ return FALSE;
+}
+
+#define CONTROLLERRC_UPDATE_PATTERN \
+ "\\(map \"(scroll|cursor)-[^\"]*\\bcontrol\\b[^\"]*\""
+
+static gboolean
+user_update_controllerrc (const GMatchInfo *matched_value,
+ GString *new_value,
+ gpointer data)
+{
+ gchar *original;
+ gchar *replacement;
+ GRegex *regexp = NULL;
+
+ /* No need of a complicated pattern here.
+ * CONTROLLERRC_UPDATE_PATTERN took care of it first.
+ */
+ regexp = g_regex_new ("\\bcontrol\\b", 0, 0, NULL);
+ original = g_match_info_fetch (matched_value, 0);
+
+ replacement = g_regex_replace (regexp, original, -1, 0,
+ "primary", 0, NULL);
+ g_string_append (new_value, replacement);
+
+ g_free (original);
+ g_free (replacement);
+ g_regex_unref (regexp);
+
+ return FALSE;
+}
+
+#define GIMPRC_UPDATE_PATTERN \
+ "\\(theme [^)]*\\)" "|" \
+ "\\(.*-path [^)]*\\)"
+
+static gboolean
+user_update_gimprc (const GMatchInfo *matched_value,
+ GString *new_value,
+ gpointer data)
+{
+ /* Do not migrate paths and themes from GIMP < 2.10. */
+ return FALSE;
+}
+
+#define GIMPRESSIONIST_UPDATE_PATTERN \
+ "selectedbrush=Brushes/paintbrush.pgm"
+
+static gboolean
+user_update_gimpressionist (const GMatchInfo *matched_value,
+ GString *new_value,
+ gpointer data)
+{
+ gchar *match = g_match_info_fetch (matched_value, 0);
+
+ /* See bug 791934: both brushes are identical. */
+ if (g_strcmp0 (match, "selectedbrush=Brushes/paintbrush.pgm") == 0)
+ {
+ g_string_append (new_value, "selectedbrush=Brushes/paintbrush01.pgm");
+ }
+ else
+ {
+ g_message ("(WARNING) %s: invalid match \"%s\"", G_STRFUNC, match);
+ g_string_append (new_value, match);
+ }
+
+ g_free (match);
+ return FALSE;
+}
+
+#define TOOL_PRESETS_UPDATE_PATTERN \
+ "GimpImageMapOptions" "|" \
+ "GimpBlendOptions" "|" \
+ "gimp-blend-tool" "|" \
+ "gimp-tool-blend"
+
+static gboolean
+user_update_tool_presets (const GMatchInfo *matched_value,
+ GString *new_value,
+ gpointer data)
+{
+ gchar *match = g_match_info_fetch (matched_value, 0);
+
+ if (g_strcmp0 (match, "GimpImageMapOptions") == 0)
+ {
+ g_string_append (new_value, "GimpFilterOptions");
+ }
+ else if (g_strcmp0 (match, "GimpBlendOptions") == 0)
+ {
+ g_string_append (new_value, "GimpGradientOptions");
+ }
+ else if (g_strcmp0 (match, "gimp-blend-tool") == 0)
+ {
+ g_string_append (new_value, "gimp-gradient-tool");
+ }
+ else if (g_strcmp0 (match, "gimp-tool-blend") == 0)
+ {
+ g_string_append (new_value, "gimp-tool-gradient");
+ }
+ else
+ {
+ g_message ("(WARNING) %s: invalid match \"%s\"", G_STRFUNC, match);
+ g_string_append (new_value, match);
+ }
+
+ g_free (match);
+ return FALSE;
+}
+
+/* Actually not only for contextrc, but all other files where
+ * gimp-blend-tool may appear. Apparently that is also "devicerc", as
+ * well as "toolrc" (but this one is skipped anyway).
+ */
+#define CONTEXTRC_UPDATE_PATTERN "gimp-blend-tool"
+
+static gboolean
+user_update_contextrc_over20 (const GMatchInfo *matched_value,
+ GString *new_value,
+ gpointer data)
+{
+ gchar *match = g_match_info_fetch (matched_value, 0);
+
+ if (g_strcmp0 (match, "gimp-blend-tool") == 0)
+ {
+ g_string_append (new_value, "gimp-gradient-tool");
+ }
+ else
+ {
+ g_message ("(WARNING) %s: invalid match \"%s\"", G_STRFUNC, match);
+ g_string_append (new_value, match);
+ }
+
+ g_free (match);
+ return FALSE;
+}
+
+static gboolean
+user_install_dir_copy (GimpUserInstall *install,
+ gint level,
+ const gchar *source,
+ const gchar *base,
+ const gchar *update_pattern,
+ GRegexEvalCallback update_callback)
+{
+ GDir *source_dir = NULL;
+ GDir *dest_dir = NULL;
+ gchar dest[1024];
+ const gchar *basename;
+ gchar *dirname = NULL;
+ gchar *name;
+ GError *error = NULL;
+ gboolean success = FALSE;
+
+ if (level >= 5)
+ {
+ /* Config migration is recursive, but we can't go on forever,
+ * since we may fall into recursive symlinks in particular (which
+ * is a security risk to fill a disk, and would also block GIMP
+ * forever at migration stage).
+ * Let's just break the recursivity at 5 levels, which is just an
+ * arbitrary value (but I don't think there should be any data
+ * deeper than this).
+ */
+ goto error;
+ }
+
+ name = g_path_get_basename (source);
+ dirname = g_build_filename (base, name, NULL);
+ g_free (name);
+
+ success = user_install_mkdir (install, dirname);
+ if (! success)
+ goto error;
+
+ success = (dest_dir = g_dir_open (dirname, 0, &error)) != NULL;
+ if (! success)
+ goto error;
+
+ success = (source_dir = g_dir_open (source, 0, &error)) != NULL;
+ if (! success)
+ goto error;
+
+ while ((basename = g_dir_read_name (source_dir)) != NULL)
+ {
+ name = g_build_filename (source, basename, NULL);
+
+ if (g_file_test (name, G_FILE_TEST_IS_REGULAR))
+ {
+ g_snprintf (dest, sizeof (dest), "%s%c%s",
+ dirname, G_DIR_SEPARATOR, basename);
+
+ success = user_install_file_copy (install, name, dest,
+ update_pattern,
+ update_callback);
+ if (! success)
+ {
+ g_free (name);
+ goto error;
+ }
+ }
+ else
+ {
+ user_install_dir_copy (install, level + 1, name, dirname,
+ update_pattern, update_callback);
+ }
+
+ g_free (name);
+ }
+
+ error:
+ user_install_log_error (install, &error);
+
+ if (source_dir)
+ g_dir_close (source_dir);
+
+ if (dest_dir)
+ g_dir_close (dest_dir);
+
+ if (dirname)
+ g_free (dirname);
+
+ return success;
+}
+
+static gboolean
+user_install_create_files (GimpUserInstall *install)
+{
+ gchar dest[1024];
+ gchar source[1024];
+ gint i;
+
+ for (i = 0; i < G_N_ELEMENTS (gimp_user_install_items); i++)
+ {
+ g_snprintf (dest, sizeof (dest), "%s%c%s",
+ gimp_directory (),
+ G_DIR_SEPARATOR,
+ gimp_user_install_items[i].name);
+
+ if (g_file_test (dest, G_FILE_TEST_EXISTS))
+ continue;
+
+ switch (gimp_user_install_items[i].action)
+ {
+ case USER_INSTALL_MKDIR:
+ if (! user_install_mkdir (install, dest))
+ return FALSE;
+ break;
+
+ case USER_INSTALL_COPY:
+ g_snprintf (source, sizeof (source), "%s%c%s",
+ gimp_sysconf_directory (), G_DIR_SEPARATOR,
+ gimp_user_install_items[i].name);
+
+ if (! user_install_file_copy (install, source, dest, NULL, NULL))
+ return FALSE;
+ break;
+ }
+ }
+
+ g_snprintf (dest, sizeof (dest), "%s%c%s",
+ gimp_directory (), G_DIR_SEPARATOR, "tags.xml");
+
+ if (! g_file_test (dest, G_FILE_TEST_IS_REGULAR))
+ {
+ /* if there was no tags.xml, install it with default tag set.
+ */
+ if (! gimp_tags_user_install ())
+ {
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static gboolean
+user_install_migrate_files (GimpUserInstall *install)
+{
+ GDir *dir;
+ const gchar *basename;
+ gchar dest[1024];
+ GimpRc *gimprc;
+ GError *error = NULL;
+
+ dir = g_dir_open (install->old_dir, 0, &error);
+
+ if (! dir)
+ {
+ user_install_log_error (install, &error);
+ return FALSE;
+ }
+
+ while ((basename = g_dir_read_name (dir)) != NULL)
+ {
+ gchar *source = g_build_filename (install->old_dir, basename, NULL);
+
+ if (g_file_test (source, G_FILE_TEST_IS_REGULAR))
+ {
+ const gchar *update_pattern = NULL;
+ GRegexEvalCallback update_callback = NULL;
+
+ /* skip these files for all old versions */
+ if (strcmp (basename, "documents") == 0 ||
+ g_str_has_prefix (basename, "gimpswap.") ||
+ strcmp (basename, "pluginrc") == 0 ||
+ strcmp (basename, "themerc") == 0 ||
+ strcmp (basename, "toolrc") == 0)
+ {
+ goto next_file;
+ }
+ else if (strcmp (basename, "menurc") == 0)
+ {
+ switch (install->old_minor)
+ {
+ case 0:
+ /* skip menurc for gimp 2.0 as the format has changed */
+ goto next_file;
+ break;
+ default:
+ update_pattern = MENURC_OVER20_UPDATE_PATTERN;
+ update_callback = user_update_menurc_over20;
+ break;
+ }
+ }
+ else if (strcmp (basename, "controllerrc") == 0)
+ {
+ update_pattern = CONTROLLERRC_UPDATE_PATTERN;
+ update_callback = user_update_controllerrc;
+ }
+ else if (strcmp (basename, "gimprc") == 0)
+ {
+ update_pattern = GIMPRC_UPDATE_PATTERN;
+ update_callback = user_update_gimprc;
+ }
+ else if (strcmp (basename, "contextrc") == 0 ||
+ strcmp (basename, "devicerc") == 0)
+ {
+ update_pattern = CONTEXTRC_UPDATE_PATTERN;
+ update_callback = user_update_contextrc_over20;
+ }
+
+ g_snprintf (dest, sizeof (dest), "%s%c%s",
+ gimp_directory (), G_DIR_SEPARATOR, basename);
+
+ user_install_file_copy (install, source, dest,
+ update_pattern, update_callback);
+ }
+ else if (g_file_test (source, G_FILE_TEST_IS_DIR))
+ {
+ const gchar *update_pattern = NULL;
+ GRegexEvalCallback update_callback = NULL;
+
+ /* skip these directories for all old versions */
+ if (strcmp (basename, "tmp") == 0 ||
+ strcmp (basename, "tool-options") == 0 ||
+ strcmp (basename, "themes") == 0)
+ {
+ goto next_file;
+ }
+
+ if (strcmp (basename, "gimpressionist") == 0)
+ {
+ update_pattern = GIMPRESSIONIST_UPDATE_PATTERN;
+ update_callback = user_update_gimpressionist;
+ }
+ else if (strcmp (basename, "tool-presets") == 0)
+ {
+ update_pattern = TOOL_PRESETS_UPDATE_PATTERN;
+ update_callback = user_update_tool_presets;
+ }
+ user_install_dir_copy (install, 0, source, gimp_directory (),
+ update_pattern, update_callback);
+ }
+
+ next_file:
+ g_free (source);
+ }
+
+ /* create the tmp directory that was explicitly not copied */
+
+ g_snprintf (dest, sizeof (dest), "%s%c%s",
+ gimp_directory (), G_DIR_SEPARATOR, "tmp");
+
+ user_install_mkdir (install, dest);
+ g_dir_close (dir);
+
+ gimp_templates_migrate (install->old_dir);
+
+ gimprc = gimp_rc_new (install->gimp, NULL, NULL, FALSE);
+ gimp_rc_migrate (gimprc);
+ gimp_rc_save (gimprc);
+ g_object_unref (gimprc);
+
+ return TRUE;
+}
diff --git a/app/core/gimp-user-install.h b/app/core/gimp-user-install.h
new file mode 100644
index 0000000..1bac686
--- /dev/null
+++ b/app/core/gimp-user-install.h
@@ -0,0 +1,39 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_USER_INSTALL_H__
+#define __GIMP_USER_INSTALL_H__
+
+
+typedef struct _GimpUserInstall GimpUserInstall;
+
+typedef void (* GimpUserInstallLogFunc) (const gchar *message,
+ gboolean error,
+ gpointer user_data);
+
+
+GimpUserInstall * gimp_user_install_new (GObject *gimp,
+ gboolean verbose);
+gboolean gimp_user_install_run (GimpUserInstall *install);
+void gimp_user_install_free (GimpUserInstall *install);
+
+void gimp_user_install_set_log_handler (GimpUserInstall *install,
+ GimpUserInstallLogFunc log,
+ gpointer user_data);
+
+
+#endif /* __USER_INSTALL_H__ */
diff --git a/app/core/gimp-utils.c b/app/core/gimp-utils.c
new file mode 100644
index 0000000..d3df098
--- /dev/null
+++ b/app/core/gimp-utils.c
@@ -0,0 +1,1098 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <locale.h>
+
+#ifdef HAVE__NL_MEASUREMENT_MEASUREMENT
+#include <langinfo.h>
+#endif
+
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#include <glib.h>
+
+#ifdef G_OS_WIN32
+#define _WIN32_WINNT 0x0500
+#include <windows.h>
+#include <process.h>
+#endif
+
+#if defined(G_OS_UNIX) && defined(HAVE_EXECINFO_H)
+/* For get_backtrace() */
+#include <stdlib.h>
+#include <string.h>
+#include <execinfo.h>
+#endif
+
+#include <cairo.h>
+#include <gegl.h>
+#include <gobject/gvaluecollector.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+#include "libgimpbase/gimpbase.h"
+#include "libgimpmath/gimpmath.h"
+#include "libgimpcolor/gimpcolor.h"
+
+#include "core-types.h"
+
+#include "gimp.h"
+#include "gimp-utils.h"
+#include "gimpasync.h"
+#include "gimpcontext.h"
+#include "gimperror.h"
+
+#include "gimp-intl.h"
+
+
+#define MAX_FUNC 100
+
+
+gint
+gimp_get_pid (void)
+{
+ return (gint) getpid ();
+}
+
+guint64
+gimp_get_physical_memory_size (void)
+{
+#ifdef G_OS_UNIX
+#if defined(HAVE_UNISTD_H) && defined(_SC_PHYS_PAGES) && defined (_SC_PAGE_SIZE)
+ return (guint64) sysconf (_SC_PHYS_PAGES) * sysconf (_SC_PAGE_SIZE);
+#endif
+#endif
+
+#ifdef G_OS_WIN32
+# if defined(_MSC_VER) && (_MSC_VER <= 1200)
+ MEMORYSTATUS memory_status;
+ memory_status.dwLength = sizeof (memory_status);
+
+ GlobalMemoryStatus (&memory_status);
+ return memory_status.dwTotalPhys;
+# else
+ /* requires w2k and newer SDK than provided with msvc6 */
+ MEMORYSTATUSEX memory_status;
+
+ memory_status.dwLength = sizeof (memory_status);
+
+ if (GlobalMemoryStatusEx (&memory_status))
+ return memory_status.ullTotalPhys;
+# endif
+#endif
+
+ return 0;
+}
+
+/*
+ * basically copied from gtk_get_default_language()
+ */
+gchar *
+gimp_get_default_language (const gchar *category)
+{
+ gchar *lang;
+ gchar *p;
+ gint cat = LC_CTYPE;
+
+ if (! category)
+ category = "LC_CTYPE";
+
+#ifdef G_OS_WIN32
+
+ p = getenv ("LC_ALL");
+ if (p != NULL)
+ lang = g_strdup (p);
+ else
+ {
+ p = getenv ("LANG");
+ if (p != NULL)
+ lang = g_strdup (p);
+ else
+ {
+ p = getenv (category);
+ if (p != NULL)
+ lang = g_strdup (p);
+ else
+ lang = g_win32_getlocale ();
+ }
+ }
+
+#else
+
+ if (strcmp (category, "LC_CTYPE") == 0)
+ cat = LC_CTYPE;
+ else if (strcmp (category, "LC_MESSAGES") == 0)
+ cat = LC_MESSAGES;
+ else
+ g_warning ("unsupported category used with gimp_get_default_language()");
+
+ lang = g_strdup (setlocale (cat, NULL));
+
+#endif
+
+ p = strchr (lang, '.');
+ if (p)
+ *p = '\0';
+ p = strchr (lang, '@');
+ if (p)
+ *p = '\0';
+
+ return lang;
+}
+
+GimpUnit
+gimp_get_default_unit (void)
+{
+#if defined (HAVE__NL_MEASUREMENT_MEASUREMENT)
+ const gchar *measurement = nl_langinfo (_NL_MEASUREMENT_MEASUREMENT);
+
+ switch (*((guchar *) measurement))
+ {
+ case 1: /* metric */
+ return GIMP_UNIT_MM;
+
+ case 2: /* imperial */
+ return GIMP_UNIT_INCH;
+ }
+
+#elif defined (G_OS_WIN32)
+ DWORD measurement;
+ int ret;
+
+ ret = GetLocaleInfo(LOCALE_USER_DEFAULT,
+ LOCALE_IMEASURE | LOCALE_RETURN_NUMBER,
+ (LPTSTR)&measurement,
+ sizeof(measurement) / sizeof(TCHAR) );
+
+ if (ret != 0) /* GetLocaleInfo succeeded */
+ {
+ switch ((guint) measurement)
+ {
+ case 0: /* metric */
+ return GIMP_UNIT_MM;
+
+ case 1: /* imperial */
+ return GIMP_UNIT_INCH;
+ }
+ }
+#endif
+
+ return GIMP_UNIT_MM;
+}
+
+gchar **
+gimp_properties_append (GType object_type,
+ gint *n_properties,
+ gchar **names,
+ GValue **values,
+ ...)
+{
+ va_list args;
+
+ g_return_val_if_fail (g_type_is_a (object_type, G_TYPE_OBJECT), NULL);
+ g_return_val_if_fail (n_properties != NULL, NULL);
+ g_return_val_if_fail (names != NULL || *n_properties == 0, NULL);
+ g_return_val_if_fail (values != NULL || *n_properties == 0, NULL);
+
+ va_start (args, values);
+ names = gimp_properties_append_valist (object_type, n_properties,
+ names, values, args);
+ va_end (args);
+
+ return names;
+}
+
+gchar **
+gimp_properties_append_valist (GType object_type,
+ gint *n_properties,
+ gchar **names,
+ GValue **values,
+ va_list args)
+{
+ GObjectClass *object_class;
+ gchar *param_name;
+
+ g_return_val_if_fail (g_type_is_a (object_type, G_TYPE_OBJECT), NULL);
+ g_return_val_if_fail (n_properties != NULL, NULL);
+ g_return_val_if_fail (names != NULL || *n_properties == 0, NULL);
+ g_return_val_if_fail (values != NULL || *n_properties == 0, NULL);
+
+ object_class = g_type_class_ref (object_type);
+
+ param_name = va_arg (args, gchar *);
+
+ while (param_name)
+ {
+ GValue *value;
+ gchar *error = NULL;
+ GParamSpec *pspec = g_object_class_find_property (object_class,
+ param_name);
+
+ if (! pspec)
+ {
+ g_warning ("%s: object class `%s' has no property named `%s'",
+ G_STRFUNC, g_type_name (object_type), param_name);
+ break;
+ }
+
+ names = g_renew (gchar *, names, *n_properties + 1);
+ *values = g_renew (GValue, *values, *n_properties + 1);
+
+ value = &((*values)[*n_properties]);
+
+ names[*n_properties] = g_strdup (param_name);
+ value->g_type = 0;
+
+ g_value_init (value, G_PARAM_SPEC_VALUE_TYPE (pspec));
+
+ G_VALUE_COLLECT (value, args, 0, &error);
+
+ if (error)
+ {
+ g_warning ("%s: %s", G_STRFUNC, error);
+ g_free (error);
+ g_free (names[*n_properties]);
+ g_value_unset (value);
+ break;
+ }
+
+ *n_properties = *n_properties + 1;
+
+ param_name = va_arg (args, gchar *);
+ }
+
+ g_type_class_unref (object_class);
+
+ return names;
+}
+
+void
+gimp_properties_free (gint n_properties,
+ gchar **names,
+ GValue *values)
+{
+ g_return_if_fail (names != NULL || n_properties == 0);
+ g_return_if_fail (values != NULL || n_properties == 0);
+
+ if (names && values)
+ {
+ gint i;
+
+ for (i = 0; i < n_properties; i++)
+ {
+ g_free (names[i]);
+ g_value_unset (&values[i]);
+ }
+
+ g_free (names);
+ g_free (values);
+ }
+}
+
+/* markup unescape code stolen and adapted from gmarkup.c
+ */
+static gchar *
+char_str (gunichar c,
+ gchar *buf)
+{
+ memset (buf, 0, 8);
+ g_unichar_to_utf8 (c, buf);
+ return buf;
+}
+
+static gboolean
+unescape_gstring (GString *string)
+{
+ const gchar *from;
+ gchar *to;
+
+ /*
+ * Meeks' theorum: unescaping can only shrink text.
+ * for &lt; etc. this is obvious, for &#xffff; more
+ * thought is required, but this is patently so.
+ */
+ for (from = to = string->str; *from != '\0'; from++, to++)
+ {
+ *to = *from;
+
+ if (*to == '\r')
+ {
+ *to = '\n';
+ if (from[1] == '\n')
+ from++;
+ }
+ if (*from == '&')
+ {
+ from++;
+ if (*from == '#')
+ {
+ gboolean is_hex = FALSE;
+ gulong l;
+ gchar *end = NULL;
+
+ from++;
+
+ if (*from == 'x')
+ {
+ is_hex = TRUE;
+ from++;
+ }
+
+ /* digit is between start and p */
+ errno = 0;
+ if (is_hex)
+ l = strtoul (from, &end, 16);
+ else
+ l = strtoul (from, &end, 10);
+
+ if (end == from || errno != 0)
+ {
+ return FALSE;
+ }
+ else if (*end != ';')
+ {
+ return FALSE;
+ }
+ else
+ {
+ /* characters XML 1.1 permits */
+ if ((0 < l && l <= 0xD7FF) ||
+ (0xE000 <= l && l <= 0xFFFD) ||
+ (0x10000 <= l && l <= 0x10FFFF))
+ {
+ gchar buf[8];
+ char_str (l, buf);
+ strcpy (to, buf);
+ to += strlen (buf) - 1;
+ from = end;
+ }
+ else
+ {
+ return FALSE;
+ }
+ }
+ }
+
+ else if (strncmp (from, "lt;", 3) == 0)
+ {
+ *to = '<';
+ from += 2;
+ }
+ else if (strncmp (from, "gt;", 3) == 0)
+ {
+ *to = '>';
+ from += 2;
+ }
+ else if (strncmp (from, "amp;", 4) == 0)
+ {
+ *to = '&';
+ from += 3;
+ }
+ else if (strncmp (from, "quot;", 5) == 0)
+ {
+ *to = '"';
+ from += 4;
+ }
+ else if (strncmp (from, "apos;", 5) == 0)
+ {
+ *to = '\'';
+ from += 4;
+ }
+ else
+ {
+ return FALSE;
+ }
+ }
+ }
+
+ gimp_assert (to - string->str <= string->len);
+ if (to - string->str != string->len)
+ g_string_truncate (string, to - string->str);
+
+ return TRUE;
+}
+
+gchar *
+gimp_markup_extract_text (const gchar *markup)
+{
+ GString *string;
+ const gchar *p;
+ gboolean in_tag = FALSE;
+
+ if (! markup)
+ return NULL;
+
+ string = g_string_new (NULL);
+
+ for (p = markup; *p; p++)
+ {
+ if (in_tag)
+ {
+ if (*p == '>')
+ in_tag = FALSE;
+ }
+ else
+ {
+ if (*p == '<')
+ in_tag = TRUE;
+ else
+ g_string_append_c (string, *p);
+ }
+ }
+
+ unescape_gstring (string);
+
+ return g_string_free (string, FALSE);
+}
+
+/**
+ * gimp_enum_get_value_name:
+ * @enum_type: Enum type
+ * @value: Enum value
+ *
+ * Returns the value name for a given value of a given enum
+ * type. Useful to have inline in GIMP_LOG() messages for example.
+ *
+ * Returns: The value name.
+ **/
+const gchar *
+gimp_enum_get_value_name (GType enum_type,
+ gint value)
+{
+ const gchar *value_name = NULL;
+
+ gimp_enum_get_value (enum_type,
+ value,
+ &value_name,
+ NULL /*value_nick*/,
+ NULL /*value_desc*/,
+ NULL /*value_help*/);
+
+ return value_name;
+}
+
+gboolean
+gimp_get_fill_params (GimpContext *context,
+ GimpFillType fill_type,
+ GimpRGB *color,
+ GimpPattern **pattern,
+ GError **error)
+
+{
+ g_return_val_if_fail (GIMP_IS_CONTEXT (context), FALSE);
+ g_return_val_if_fail (color != NULL, FALSE);
+ g_return_val_if_fail (pattern != NULL, FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ *pattern = NULL;
+
+ switch (fill_type)
+ {
+ case GIMP_FILL_FOREGROUND:
+ gimp_context_get_foreground (context, color);
+ break;
+
+ case GIMP_FILL_BACKGROUND:
+ gimp_context_get_background (context, color);
+ break;
+
+ case GIMP_FILL_WHITE:
+ gimp_rgba_set (color, 1.0, 1.0, 1.0, GIMP_OPACITY_OPAQUE);
+ break;
+
+ case GIMP_FILL_TRANSPARENT:
+ gimp_rgba_set (color, 0.0, 0.0, 0.0, GIMP_OPACITY_TRANSPARENT);
+ break;
+
+ case GIMP_FILL_PATTERN:
+ *pattern = gimp_context_get_pattern (context);
+
+ if (! *pattern)
+ {
+ g_set_error_literal (error, GIMP_ERROR, GIMP_FAILED,
+ _("No patterns available for this operation."));
+
+ /* fall back to BG fill */
+ gimp_context_get_background (context, color);
+
+ return FALSE;
+ }
+ break;
+
+ default:
+ g_warning ("%s: invalid fill_type %d", G_STRFUNC, fill_type);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/**
+ * gimp_constrain_line:
+ * @start_x:
+ * @start_y:
+ * @end_x:
+ * @end_y:
+ * @n_snap_lines: Number evenly disributed lines to snap to.
+ * @offset_angle: The angle by which to offset the lines, in degrees.
+ * @xres: The horizontal resolution.
+ * @yres: The vertical resolution.
+ *
+ * Projects a line onto the specified subset of evenly radially
+ * distributed lines. @n_lines of 2 makes the line snap horizontally
+ * or vertically. @n_lines of 4 snaps on 45 degree steps. @n_lines of
+ * 12 on 15 degree steps. etc.
+ **/
+void
+gimp_constrain_line (gdouble start_x,
+ gdouble start_y,
+ gdouble *end_x,
+ gdouble *end_y,
+ gint n_snap_lines,
+ gdouble offset_angle,
+ gdouble xres,
+ gdouble yres)
+{
+ GimpVector2 diff;
+ GimpVector2 dir;
+ gdouble angle;
+
+ offset_angle *= G_PI / 180.0;
+
+ diff.x = (*end_x - start_x) / xres;
+ diff.y = (*end_y - start_y) / yres;
+
+ angle = (atan2 (diff.y, diff.x) - offset_angle) * n_snap_lines / G_PI;
+ angle = RINT (angle) * G_PI / n_snap_lines + offset_angle;
+
+ dir.x = cos (angle);
+ dir.y = sin (angle);
+
+ gimp_vector2_mul (&dir, gimp_vector2_inner_product (&dir, &diff));
+
+ *end_x = start_x + dir.x * xres;
+ *end_y = start_y + dir.y * yres;
+}
+
+gint
+gimp_file_compare (GFile *file1,
+ GFile *file2)
+{
+ if (g_file_equal (file1, file2))
+ {
+ return 0;
+ }
+ else
+ {
+ gchar *uri1 = g_file_get_uri (file1);
+ gchar *uri2 = g_file_get_uri (file2);
+ gint result = strcmp (uri1, uri2);
+
+ g_free (uri1);
+ g_free (uri2);
+
+ return result;
+ }
+}
+
+static inline gboolean
+is_script (const gchar *filename)
+{
+#ifdef G_OS_WIN32
+ /* On Windows there is no concept like the Unix executable flag.
+ * There is a weak emulation provided by the MS C Runtime using file
+ * extensions (com, exe, cmd, bat). This needs to be extended to
+ * treat scripts (Python, Perl, ...) as executables, too. We use the
+ * PATHEXT variable, which is also used by cmd.exe.
+ */
+ static gchar **exts = NULL;
+
+ const gchar *ext = strrchr (filename, '.');
+ const gchar *pathext;
+ gint i;
+
+ if (exts == NULL)
+ {
+ pathext = g_getenv ("PATHEXT");
+ if (pathext != NULL)
+ {
+ exts = g_strsplit (pathext, G_SEARCHPATH_SEPARATOR_S, 100);
+ }
+ else
+ {
+ exts = g_new (gchar *, 1);
+ exts[0] = NULL;
+ }
+ }
+
+ for (i = 0; exts[i]; i++)
+ {
+ if (g_ascii_strcasecmp (ext, exts[i]) == 0)
+ return TRUE;
+ }
+#endif /* G_OS_WIN32 */
+
+ return FALSE;
+}
+
+gboolean
+gimp_file_is_executable (GFile *file)
+{
+ GFileInfo *info;
+ gboolean executable = FALSE;
+
+ g_return_val_if_fail (G_IS_FILE (file), FALSE);
+
+ info = g_file_query_info (file,
+ G_FILE_ATTRIBUTE_STANDARD_NAME ","
+ G_FILE_ATTRIBUTE_STANDARD_TYPE ","
+ G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE ",",
+ G_FILE_QUERY_INFO_NONE,
+ NULL, NULL);
+
+ if (info)
+ {
+ GFileType file_type = g_file_info_get_file_type (info);
+ const gchar *filename = g_file_info_get_name (info);
+
+ if (file_type == G_FILE_TYPE_REGULAR &&
+ (g_file_info_get_attribute_boolean (info,
+ G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE) ||
+ is_script (filename)))
+ {
+ executable = TRUE;
+ }
+
+ g_object_unref (info);
+ }
+
+ return executable;
+}
+
+/**
+ * gimp_file_get_extension:
+ * @file: A #GFile
+ *
+ * Returns @file's extension (including the .), or NULL if there is no
+ * extension. Note that this function handles compressed files too,
+ * e.g. for "file.png.gz" it will return ".png.gz".
+ *
+ * Returns: The @file's extension. Free with g_free() when no longer needed.
+ **/
+gchar *
+gimp_file_get_extension (GFile *file)
+{
+ gchar *uri;
+ gint uri_len;
+ gchar *ext = NULL;
+ gint search_len;
+
+ g_return_val_if_fail (G_IS_FILE (file), NULL);
+
+ uri = g_file_get_uri (file);
+ uri_len = strlen (uri);
+
+ if (g_str_has_suffix (uri, ".gz"))
+ search_len = uri_len - 3;
+ else if (g_str_has_suffix (uri, ".bz2"))
+ search_len = uri_len - 4;
+ else if (g_str_has_suffix (uri, ".xz"))
+ search_len = uri_len - 3;
+ else
+ search_len = uri_len;
+
+ ext = g_strrstr_len (uri, search_len, ".");
+
+ if (ext)
+ ext = g_strdup (ext);
+
+ g_free (uri);
+
+ return ext;
+}
+
+GFile *
+gimp_file_with_new_extension (GFile *file,
+ GFile *ext_file)
+{
+ gchar *uri;
+ gchar *file_ext;
+ gint file_ext_len = 0;
+ gchar *ext_file_ext = NULL;
+ gchar *uri_without_ext;
+ gchar *new_uri;
+ GFile *ret;
+
+ g_return_val_if_fail (G_IS_FILE (file), NULL);
+ g_return_val_if_fail (ext_file == NULL || G_IS_FILE (ext_file), NULL);
+
+ uri = g_file_get_uri (file);
+ file_ext = gimp_file_get_extension (file);
+
+ if (file_ext)
+ {
+ file_ext_len = strlen (file_ext);
+ g_free (file_ext);
+ }
+
+ if (ext_file)
+ ext_file_ext = gimp_file_get_extension (ext_file);
+
+ uri_without_ext = g_strndup (uri, strlen (uri) - file_ext_len);
+
+ g_free (uri);
+
+ new_uri = g_strconcat (uri_without_ext, ext_file_ext, NULL);
+
+ ret = g_file_new_for_uri (new_uri);
+
+ g_free (ext_file_ext);
+ g_free (uri_without_ext);
+ g_free (new_uri);
+
+ return ret;
+}
+
+gchar *
+gimp_data_input_stream_read_line_always (GDataInputStream *stream,
+ gsize *length,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GError *temp_error = NULL;
+ gchar *result;
+
+ g_return_val_if_fail (G_IS_DATA_INPUT_STREAM (stream), NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ if (! error)
+ error = &temp_error;
+
+ result = g_data_input_stream_read_line (stream, length, cancellable, error);
+
+ if (! result && ! *error)
+ {
+ result = g_strdup ("");
+
+ if (length) *length = 0;
+ }
+
+ g_clear_error (&temp_error);
+
+ return result;
+}
+
+gboolean
+gimp_ascii_strtoi (const gchar *nptr,
+ gchar **endptr,
+ gint base,
+ gint *result)
+{
+ gchar *temp_endptr;
+ gint64 temp_result;
+
+ g_return_val_if_fail (nptr != NULL, FALSE);
+ g_return_val_if_fail (base == 0 || (base >= 2 && base <= 36), FALSE);
+
+ if (! endptr)
+ endptr = &temp_endptr;
+
+ temp_result = g_ascii_strtoll (nptr, endptr, base);
+
+ if (*endptr == nptr || errno == ERANGE ||
+ temp_result < G_MININT || temp_result > G_MAXINT)
+ {
+ errno = 0;
+
+ return FALSE;
+ }
+
+ if (result) *result = temp_result;
+
+ return TRUE;
+}
+
+gboolean
+gimp_ascii_strtod (const gchar *nptr,
+ gchar **endptr,
+ gdouble *result)
+{
+ gchar *temp_endptr;
+ gdouble temp_result;
+
+ g_return_val_if_fail (nptr != NULL, FALSE);
+
+ if (! endptr)
+ endptr = &temp_endptr;
+
+ temp_result = g_ascii_strtod (nptr, endptr);
+
+ if (*endptr == nptr || errno == ERANGE)
+ {
+ errno = 0;
+
+ return FALSE;
+ }
+
+ if (result) *result = temp_result;
+
+ return TRUE;
+}
+
+gint
+gimp_g_list_compare (GList *list1,
+ GList *list2)
+{
+ while (list1 && list2)
+ {
+ if (list1->data < list2->data)
+ return -1;
+ else if (list1->data > list2->data)
+ return +1;
+
+ list1 = g_list_next (list1);
+ list2 = g_list_next (list2);
+ }
+
+ if (! list1)
+ return -1;
+ else if (! list2)
+ return +1;
+
+ return 0;
+}
+
+typedef struct
+{
+ gint ref_count;
+
+ GimpAsync *async;
+ gint idle_id;
+
+ GimpRunAsyncFunc func;
+ gpointer user_data;
+ GDestroyNotify user_data_destroy_func;
+} GimpIdleRunAsyncData;
+
+static GimpIdleRunAsyncData *
+gimp_idle_run_async_data_new (void)
+{
+ GimpIdleRunAsyncData *data;
+
+ data = g_slice_new0 (GimpIdleRunAsyncData);
+
+ data->ref_count = 1;
+
+ return data;
+}
+
+static void
+gimp_idle_run_async_data_inc_ref (GimpIdleRunAsyncData *data)
+{
+ data->ref_count++;
+}
+
+static void
+gimp_idle_run_async_data_dec_ref (GimpIdleRunAsyncData *data)
+{
+ data->ref_count--;
+
+ if (data->ref_count == 0)
+ {
+ g_signal_handlers_disconnect_by_data (data->async, data);
+
+ if (! gimp_async_is_stopped (data->async))
+ gimp_async_abort (data->async);
+
+ g_object_unref (data->async);
+
+ if (data->user_data && data->user_data_destroy_func)
+ data->user_data_destroy_func (data->user_data);
+
+ g_slice_free (GimpIdleRunAsyncData, data);
+ }
+}
+
+static void
+gimp_idle_run_async_cancel (GimpAsync *async,
+ GimpIdleRunAsyncData *data)
+{
+ gimp_idle_run_async_data_inc_ref (data);
+
+ if (data->idle_id)
+ {
+ g_source_remove (data->idle_id);
+
+ data->idle_id = 0;
+ }
+
+ gimp_idle_run_async_data_dec_ref (data);
+}
+
+static void
+gimp_idle_run_async_waiting (GimpAsync *async,
+ GimpIdleRunAsyncData *data)
+{
+ gimp_idle_run_async_data_inc_ref (data);
+
+ if (data->idle_id)
+ {
+ g_source_remove (data->idle_id);
+
+ data->idle_id = 0;
+ }
+
+ g_signal_handlers_block_by_func (data->async,
+ gimp_idle_run_async_cancel,
+ data);
+
+ while (! gimp_async_is_stopped (data->async))
+ data->func (data->async, data->user_data);
+
+ g_signal_handlers_unblock_by_func (data->async,
+ gimp_idle_run_async_cancel,
+ data);
+
+ data->user_data = NULL;
+
+ gimp_idle_run_async_data_dec_ref (data);
+}
+
+static gboolean
+gimp_idle_run_async_idle (GimpIdleRunAsyncData *data)
+{
+ gimp_idle_run_async_data_inc_ref (data);
+
+ g_signal_handlers_block_by_func (data->async,
+ gimp_idle_run_async_cancel,
+ data);
+
+ data->func (data->async, data->user_data);
+
+ g_signal_handlers_unblock_by_func (data->async,
+ gimp_idle_run_async_cancel,
+ data);
+
+ if (gimp_async_is_stopped (data->async))
+ {
+ data->user_data = NULL;
+
+ gimp_idle_run_async_data_dec_ref (data);
+
+ return G_SOURCE_REMOVE;
+ }
+
+ gimp_idle_run_async_data_dec_ref (data);
+
+ return G_SOURCE_CONTINUE;
+}
+
+GimpAsync *
+gimp_idle_run_async (GimpRunAsyncFunc func,
+ gpointer user_data)
+{
+ return gimp_idle_run_async_full (G_PRIORITY_DEFAULT_IDLE, func,
+ user_data, NULL);
+}
+
+GimpAsync *
+gimp_idle_run_async_full (gint priority,
+ GimpRunAsyncFunc func,
+ gpointer user_data,
+ GDestroyNotify user_data_destroy_func)
+{
+ GimpIdleRunAsyncData *data;
+
+ g_return_val_if_fail (func != NULL, NULL);
+
+ data = gimp_idle_run_async_data_new ();
+
+ data->func = func;
+ data->user_data = user_data;
+ data->user_data_destroy_func = user_data_destroy_func;
+
+ data->async = gimp_async_new ();
+
+ g_signal_connect (data->async, "cancel",
+ G_CALLBACK (gimp_idle_run_async_cancel),
+ data);
+ g_signal_connect (data->async, "waiting",
+ G_CALLBACK (gimp_idle_run_async_waiting),
+ data);
+
+ data->idle_id = g_idle_add_full (
+ priority,
+ (GSourceFunc) gimp_idle_run_async_idle,
+ data,
+ (GDestroyNotify) gimp_idle_run_async_data_dec_ref);
+
+ return g_object_ref (data->async);
+}
+
+
+/* debug stuff */
+
+#include "gegl/gimp-babl.h"
+#include "gimpimage.h"
+#include "gimplayer.h"
+#include "gimplayer-new.h"
+
+GimpImage *
+gimp_create_image_from_buffer (Gimp *gimp,
+ GeglBuffer *buffer,
+ const gchar *image_name)
+{
+ GimpImage *image;
+ GimpLayer *layer;
+ const Babl *format;
+
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
+ g_return_val_if_fail (GEGL_IS_BUFFER (buffer), NULL);
+
+ if (! image_name)
+ image_name = "Debug Image";
+
+ format = gegl_buffer_get_format (buffer);
+
+ image = gimp_create_image (gimp,
+ gegl_buffer_get_width (buffer),
+ gegl_buffer_get_height (buffer),
+ gimp_babl_format_get_base_type (format),
+ gimp_babl_format_get_precision (format),
+ FALSE);
+
+ layer = gimp_layer_new_from_gegl_buffer (buffer, image, format,
+ image_name,
+ GIMP_OPACITY_OPAQUE,
+ GIMP_LAYER_MODE_NORMAL,
+ NULL /* same image */);
+ gimp_image_add_layer (image, layer, NULL, -1, FALSE);
+
+ gimp_create_display (gimp, image, GIMP_UNIT_PIXEL, 1.0, NULL, 0);
+
+ /* unref the image unconditionally, even when no display was created */
+ g_object_add_weak_pointer (G_OBJECT (image), (gpointer) &image);
+ g_object_unref (image);
+
+ return image;
+}
diff --git a/app/core/gimp-utils.h b/app/core/gimp-utils.h
new file mode 100644
index 0000000..8373de7
--- /dev/null
+++ b/app/core/gimp-utils.h
@@ -0,0 +1,116 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __APP_GIMP_UTILS_H__
+#define __APP_GIMP_UTILS_H__
+
+
+#define GIMP_TIMER_START() \
+ { GTimer *_timer = g_timer_new ();
+
+#define GIMP_TIMER_END(message) \
+ g_printerr ("%s: %s took %0.4f seconds\n", \
+ G_STRFUNC, message, g_timer_elapsed (_timer, NULL)); \
+ g_timer_destroy (_timer); }
+
+
+#define MIN4(a,b,c,d) MIN (MIN ((a), (b)), MIN ((c), (d)))
+#define MAX4(a,b,c,d) MAX (MAX ((a), (b)), MAX ((c), (d)))
+
+
+gint gimp_get_pid (void);
+guint64 gimp_get_physical_memory_size (void);
+gchar * gimp_get_default_language (const gchar *category);
+GimpUnit gimp_get_default_unit (void);
+
+gchar ** gimp_properties_append (GType object_type,
+ gint *n_properties,
+ gchar **names,
+ GValue **values,
+ ...) G_GNUC_NULL_TERMINATED;
+gchar ** gimp_properties_append_valist (GType object_type,
+ gint *n_properties,
+ gchar **names,
+ GValue **values,
+ va_list args);
+void gimp_properties_free (gint n_properties,
+ gchar **names,
+ GValue *values);
+
+gchar * gimp_markup_extract_text (const gchar *markup);
+
+const gchar* gimp_enum_get_value_name (GType enum_type,
+ gint value);
+
+gboolean gimp_get_fill_params (GimpContext *context,
+ GimpFillType fill_type,
+ GimpRGB *color,
+ GimpPattern **pattern,
+ GError **error);
+
+/* Common values for the n_snap_lines parameter of
+ * gimp_constrain_line.
+ */
+#define GIMP_CONSTRAIN_LINE_90_DEGREES 2
+#define GIMP_CONSTRAIN_LINE_45_DEGREES 4
+#define GIMP_CONSTRAIN_LINE_15_DEGREES 12
+
+void gimp_constrain_line (gdouble start_x,
+ gdouble start_y,
+ gdouble *end_x,
+ gdouble *end_y,
+ gint n_snap_lines,
+ gdouble offset_angle,
+ gdouble xres,
+ gdouble yres);
+
+gint gimp_file_compare (GFile *file1,
+ GFile *file2);
+gboolean gimp_file_is_executable (GFile *file);
+gchar * gimp_file_get_extension (GFile *file);
+GFile * gimp_file_with_new_extension (GFile *file,
+ GFile *ext_file);
+
+gchar * gimp_data_input_stream_read_line_always (GDataInputStream *stream,
+ gsize *length,
+ GCancellable *cancellable,
+ GError **error);
+
+gboolean gimp_ascii_strtoi (const gchar *nptr,
+ gchar **endptr,
+ gint base,
+ gint *result);
+gboolean gimp_ascii_strtod (const gchar *nptr,
+ gchar **endptr,
+ gdouble *result);
+
+gint gimp_g_list_compare (GList *list1,
+ GList *list2);
+
+GimpAsync * gimp_idle_run_async (GimpRunAsyncFunc func,
+ gpointer user_data);
+GimpAsync * gimp_idle_run_async_full (gint priority,
+ GimpRunAsyncFunc func,
+ gpointer user_data,
+ GDestroyNotify user_data_destroy_func);
+
+GimpImage * gimp_create_image_from_buffer (Gimp *gimp,
+ GeglBuffer *buffer,
+ const gchar *image_name);
+
+
+#endif /* __APP_GIMP_UTILS_H__ */
diff --git a/app/core/gimp.c b/app/core/gimp.c
new file mode 100644
index 0000000..ede44fe
--- /dev/null
+++ b/app/core/gimp.c
@@ -0,0 +1,1224 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-2002 Spencer Kimball, Peter Mattis, and others
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h> /* strlen */
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpbase/gimpbase.h"
+#include "libgimpconfig/gimpconfig.h"
+
+#include "core-types.h"
+
+#include "config/gimprc.h"
+
+#include "gegl/gimp-babl.h"
+
+#include "pdb/gimppdb.h"
+#include "pdb/gimp-pdb-compat.h"
+#include "pdb/internal-procs.h"
+
+#include "plug-in/gimppluginmanager.h"
+#include "plug-in/gimppluginmanager-restore.h"
+
+#include "paint/gimp-paint.h"
+
+#include "xcf/xcf.h"
+#include "file-data/file-data.h"
+
+#include "gimp.h"
+#include "gimp-contexts.h"
+#include "gimp-data-factories.h"
+#include "gimp-filter-history.h"
+#include "gimp-memsize.h"
+#include "gimp-modules.h"
+#include "gimp-parasites.h"
+#include "gimp-templates.h"
+#include "gimp-units.h"
+#include "gimp-utils.h"
+#include "gimpbrush.h"
+#include "gimpbuffer.h"
+#include "gimpcontext.h"
+#include "gimpdynamics.h"
+#include "gimpdocumentlist.h"
+#include "gimpgradient.h"
+#include "gimpidtable.h"
+#include "gimpimage.h"
+#include "gimpimagefile.h"
+#include "gimplist.h"
+#include "gimpmarshal.h"
+#include "gimpmybrush.h"
+#include "gimppalette.h"
+#include "gimpparasitelist.h"
+#include "gimppattern.h"
+#include "gimptemplate.h"
+#include "gimptoolinfo.h"
+#include "gimptreeproxy.h"
+
+#include "gimp-intl.h"
+
+
+enum
+{
+ INITIALIZE,
+ RESTORE,
+ EXIT,
+ CLIPBOARD_CHANGED,
+ FILTER_HISTORY_CHANGED,
+ IMAGE_OPENED,
+ LAST_SIGNAL
+};
+
+enum
+{
+ PROP_0,
+ PROP_VERBOSE
+};
+
+
+static void gimp_constructed (GObject *object);
+static void gimp_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+static void gimp_dispose (GObject *object);
+static void gimp_finalize (GObject *object);
+
+static gint64 gimp_get_memsize (GimpObject *object,
+ gint64 *gui_size);
+
+static void gimp_real_initialize (Gimp *gimp,
+ GimpInitStatusFunc status_callback);
+static void gimp_real_restore (Gimp *gimp,
+ GimpInitStatusFunc status_callback);
+static gboolean gimp_real_exit (Gimp *gimp,
+ gboolean force);
+
+static void gimp_global_config_notify (GObject *global_config,
+ GParamSpec *param_spec,
+ GObject *edit_config);
+static void gimp_edit_config_notify (GObject *edit_config,
+ GParamSpec *param_spec,
+ GObject *global_config);
+
+
+G_DEFINE_TYPE (Gimp, gimp, GIMP_TYPE_OBJECT)
+
+#define parent_class gimp_parent_class
+
+static guint gimp_signals[LAST_SIGNAL] = { 0, };
+
+
+static void
+gimp_class_init (GimpClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpObjectClass *gimp_object_class = GIMP_OBJECT_CLASS (klass);
+
+ gimp_signals[INITIALIZE] =
+ g_signal_new ("initialize",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GimpClass, initialize),
+ NULL, NULL,
+ gimp_marshal_VOID__POINTER,
+ G_TYPE_NONE, 1,
+ G_TYPE_POINTER);
+
+ gimp_signals[RESTORE] =
+ g_signal_new ("restore",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GimpClass, restore),
+ NULL, NULL,
+ gimp_marshal_VOID__POINTER,
+ G_TYPE_NONE, 1,
+ G_TYPE_POINTER);
+
+ gimp_signals[EXIT] =
+ g_signal_new ("exit",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GimpClass, exit),
+ g_signal_accumulator_true_handled, NULL,
+ gimp_marshal_BOOLEAN__BOOLEAN,
+ G_TYPE_BOOLEAN, 1,
+ G_TYPE_BOOLEAN);
+
+ gimp_signals[CLIPBOARD_CHANGED] =
+ g_signal_new ("clipboard-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GimpClass, clipboard_changed),
+ NULL, NULL,
+ gimp_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ gimp_signals[FILTER_HISTORY_CHANGED] =
+ g_signal_new ("filter-history-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GimpClass,
+ filter_history_changed),
+ NULL, NULL,
+ gimp_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ gimp_signals[IMAGE_OPENED] =
+ g_signal_new ("image-opened",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GimpClass, image_opened),
+ NULL, NULL,
+ gimp_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1, G_TYPE_FILE);
+
+ object_class->constructed = gimp_constructed;
+ object_class->set_property = gimp_set_property;
+ object_class->get_property = gimp_get_property;
+ object_class->dispose = gimp_dispose;
+ object_class->finalize = gimp_finalize;
+
+ gimp_object_class->get_memsize = gimp_get_memsize;
+
+ klass->initialize = gimp_real_initialize;
+ klass->restore = gimp_real_restore;
+ klass->exit = gimp_real_exit;
+ klass->clipboard_changed = NULL;
+
+ g_object_class_install_property (object_class, PROP_VERBOSE,
+ g_param_spec_boolean ("verbose", NULL, NULL,
+ FALSE,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+}
+
+static void
+gimp_init (Gimp *gimp)
+{
+ gimp->be_verbose = FALSE;
+ gimp->no_data = FALSE;
+ gimp->no_interface = FALSE;
+ gimp->show_gui = TRUE;
+ gimp->use_shm = FALSE;
+ gimp->use_cpu_accel = TRUE;
+ gimp->message_handler = GIMP_CONSOLE;
+ gimp->show_playground = FALSE;
+ gimp->stack_trace_mode = GIMP_STACK_TRACE_NEVER;
+ gimp->pdb_compat_mode = GIMP_PDB_COMPAT_OFF;
+
+ gimp_gui_init (gimp);
+
+ gimp->parasites = gimp_parasite_list_new ();
+
+ gimp_units_init (gimp);
+
+ gimp->images = gimp_list_new_weak (GIMP_TYPE_IMAGE, FALSE);
+ gimp_object_set_static_name (GIMP_OBJECT (gimp->images), "images");
+
+ gimp->next_guide_ID = 1;
+ gimp->next_sample_point_ID = 1;
+ gimp->image_table = gimp_id_table_new ();
+ gimp->item_table = gimp_id_table_new ();
+
+ gimp->displays = g_object_new (GIMP_TYPE_LIST,
+ "children-type", GIMP_TYPE_OBJECT,
+ "policy", GIMP_CONTAINER_POLICY_WEAK,
+ "append", TRUE,
+ NULL);
+ gimp_object_set_static_name (GIMP_OBJECT (gimp->displays), "displays");
+ gimp->next_display_ID = 1;
+
+ gimp->named_buffers = gimp_list_new (GIMP_TYPE_BUFFER, TRUE);
+ gimp_object_set_static_name (GIMP_OBJECT (gimp->named_buffers),
+ "named buffers");
+
+ gimp_data_factories_init (gimp);
+
+ gimp->tool_info_list = g_object_new (GIMP_TYPE_LIST,
+ "children-type", GIMP_TYPE_TOOL_INFO,
+ "append", TRUE,
+ NULL);
+ gimp_object_set_static_name (GIMP_OBJECT (gimp->tool_info_list),
+ "tool infos");
+
+ gimp->tool_item_list = g_object_new (GIMP_TYPE_LIST,
+ "children-type", GIMP_TYPE_TOOL_ITEM,
+ "append", TRUE,
+ NULL);
+ gimp_object_set_static_name (GIMP_OBJECT (gimp->tool_item_list),
+ "tool items");
+
+ gimp->tool_item_ui_list = gimp_tree_proxy_new_for_container (
+ gimp->tool_item_list);
+ gimp_object_set_static_name (GIMP_OBJECT (gimp->tool_item_ui_list),
+ "ui tool items");
+
+ gimp->documents = gimp_document_list_new (gimp);
+
+ gimp->templates = gimp_list_new (GIMP_TYPE_TEMPLATE, TRUE);
+ gimp_object_set_static_name (GIMP_OBJECT (gimp->templates), "templates");
+}
+
+static void
+gimp_constructed (GObject *object)
+{
+ Gimp *gimp = GIMP (object);
+
+ G_OBJECT_CLASS (parent_class)->constructed (object);
+
+ gimp_modules_init (gimp);
+
+ gimp_paint_init (gimp);
+
+ gimp->plug_in_manager = gimp_plug_in_manager_new (gimp);
+ gimp->pdb = gimp_pdb_new (gimp);
+
+ xcf_init (gimp);
+ file_data_init (gimp);
+
+ /* create user and default context */
+ gimp_contexts_init (gimp);
+}
+
+static void
+gimp_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ Gimp *gimp = GIMP (object);
+
+ switch (property_id)
+ {
+ case PROP_VERBOSE:
+ gimp->be_verbose = g_value_get_boolean (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ Gimp *gimp = GIMP (object);
+
+ switch (property_id)
+ {
+ case PROP_VERBOSE:
+ g_value_set_boolean (value, gimp->be_verbose);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_dispose (GObject *object)
+{
+ Gimp *gimp = GIMP (object);
+
+ if (gimp->be_verbose)
+ g_print ("EXIT: %s\n", G_STRFUNC);
+
+ gimp_data_factories_clear (gimp);
+
+ gimp_filter_history_clear (gimp);
+
+ g_clear_object (&gimp->edit_config);
+ g_clear_object (&gimp->config);
+
+ gimp_contexts_exit (gimp);
+
+ g_clear_object (&gimp->image_new_last_template);
+
+ G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+gimp_finalize (GObject *object)
+{
+ Gimp *gimp = GIMP (object);
+ GList *standards = NULL;
+
+ if (gimp->be_verbose)
+ g_print ("EXIT: %s\n", G_STRFUNC);
+
+ standards = g_list_prepend (standards,
+ gimp_brush_get_standard (gimp->user_context));
+ standards = g_list_prepend (standards,
+ gimp_dynamics_get_standard (gimp->user_context));
+ standards = g_list_prepend (standards,
+ gimp_mybrush_get_standard (gimp->user_context));
+ standards = g_list_prepend (standards,
+ gimp_pattern_get_standard (gimp->user_context));
+ standards = g_list_prepend (standards,
+ gimp_gradient_get_standard (gimp->user_context));
+ standards = g_list_prepend (standards,
+ gimp_palette_get_standard (gimp->user_context));
+
+ g_clear_object (&gimp->templates);
+ g_clear_object (&gimp->documents);
+
+ gimp_tool_info_set_standard (gimp, NULL);
+
+ g_clear_object (&gimp->tool_item_list);
+ g_clear_object (&gimp->tool_item_ui_list);
+
+ if (gimp->tool_info_list)
+ {
+ gimp_container_foreach (gimp->tool_info_list,
+ (GFunc) g_object_run_dispose, NULL);
+ g_clear_object (&gimp->tool_info_list);
+ }
+
+ file_data_exit (gimp);
+ xcf_exit (gimp);
+
+ g_clear_object (&gimp->pdb);
+
+ gimp_data_factories_exit (gimp);
+
+ g_clear_object (&gimp->named_buffers);
+ g_clear_object (&gimp->clipboard_buffer);
+ g_clear_object (&gimp->clipboard_image);
+ g_clear_object (&gimp->displays);
+ g_clear_object (&gimp->item_table);
+ g_clear_object (&gimp->image_table);
+ g_clear_object (&gimp->images);
+ g_clear_object (&gimp->plug_in_manager);
+
+ if (gimp->module_db)
+ gimp_modules_exit (gimp);
+
+ gimp_paint_exit (gimp);
+
+ g_clear_object (&gimp->parasites);
+ g_clear_object (&gimp->default_folder);
+
+ g_clear_pointer (&gimp->session_name, g_free);
+
+ if (gimp->context_list)
+ {
+ GList *list;
+
+ g_warning ("%s: list of contexts not empty upon exit (%d contexts left)\n",
+ G_STRFUNC, g_list_length (gimp->context_list));
+
+ for (list = gimp->context_list; list; list = g_list_next (list))
+ g_printerr ("stale context: %s\n", gimp_object_get_name (list->data));
+
+ g_list_free (gimp->context_list);
+ gimp->context_list = NULL;
+ }
+
+ g_list_foreach (standards, (GFunc) g_object_unref, NULL);
+ g_list_free (standards);
+
+ gimp_units_exit (gimp);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static gint64
+gimp_get_memsize (GimpObject *object,
+ gint64 *gui_size)
+{
+ Gimp *gimp = GIMP (object);
+ gint64 memsize = 0;
+
+ memsize += gimp_g_list_get_memsize (gimp->user_units, 0 /* FIXME */);
+
+ memsize += gimp_object_get_memsize (GIMP_OBJECT (gimp->parasites),
+ gui_size);
+
+ memsize += gimp_object_get_memsize (GIMP_OBJECT (gimp->paint_info_list),
+ gui_size);
+
+ memsize += gimp_g_object_get_memsize (G_OBJECT (gimp->module_db));
+ memsize += gimp_object_get_memsize (GIMP_OBJECT (gimp->plug_in_manager),
+ gui_size);
+
+ memsize += gimp_g_list_get_memsize_foreach (gimp->filter_history,
+ (GimpMemsizeFunc)
+ gimp_object_get_memsize,
+ gui_size);
+
+ memsize += gimp_object_get_memsize (GIMP_OBJECT (gimp->image_table), 0);
+ memsize += gimp_object_get_memsize (GIMP_OBJECT (gimp->item_table), 0);
+
+ memsize += gimp_object_get_memsize (GIMP_OBJECT (gimp->displays), gui_size);
+
+ memsize += gimp_object_get_memsize (GIMP_OBJECT (gimp->clipboard_image),
+ gui_size);
+ memsize += gimp_object_get_memsize (GIMP_OBJECT (gimp->clipboard_buffer),
+ gui_size);
+ memsize += gimp_object_get_memsize (GIMP_OBJECT (gimp->named_buffers),
+ gui_size);
+
+ memsize += gimp_data_factories_get_memsize (gimp, gui_size);
+
+ memsize += gimp_object_get_memsize (GIMP_OBJECT (gimp->pdb), gui_size);
+
+ memsize += gimp_object_get_memsize (GIMP_OBJECT (gimp->tool_info_list),
+ gui_size);
+ memsize += gimp_object_get_memsize (GIMP_OBJECT (gimp->standard_tool_info),
+ gui_size);
+ memsize += gimp_object_get_memsize (GIMP_OBJECT (gimp->documents),
+ gui_size);
+ memsize += gimp_object_get_memsize (GIMP_OBJECT (gimp->templates),
+ gui_size);
+ memsize += gimp_object_get_memsize (GIMP_OBJECT (gimp->image_new_last_template),
+ gui_size);
+
+ memsize += gimp_g_list_get_memsize (gimp->context_list, 0);
+
+ memsize += gimp_object_get_memsize (GIMP_OBJECT (gimp->default_context),
+ gui_size);
+ memsize += gimp_object_get_memsize (GIMP_OBJECT (gimp->user_context),
+ gui_size);
+
+ return memsize + GIMP_OBJECT_CLASS (parent_class)->get_memsize (object,
+ gui_size);
+}
+
+static void
+gimp_real_initialize (Gimp *gimp,
+ GimpInitStatusFunc status_callback)
+{
+ if (gimp->be_verbose)
+ g_print ("INIT: %s\n", G_STRFUNC);
+
+ status_callback (_("Initialization"), NULL, 0.0);
+
+ /* set the last values used to default values */
+ gimp->image_new_last_template =
+ gimp_config_duplicate (GIMP_CONFIG (gimp->config->default_image));
+
+ /* add data objects that need the user context */
+ gimp_data_factories_add_builtin (gimp);
+
+ /* register all internal procedures */
+ status_callback (NULL, _("Internal Procedures"), 0.2);
+ internal_procs_init (gimp->pdb);
+ gimp_pdb_compat_procs_register (gimp->pdb, gimp->pdb_compat_mode);
+
+ gimp_plug_in_manager_initialize (gimp->plug_in_manager, status_callback);
+
+ status_callback (NULL, "", 1.0);
+}
+
+static void
+gimp_real_restore (Gimp *gimp,
+ GimpInitStatusFunc status_callback)
+{
+ if (gimp->be_verbose)
+ g_print ("INIT: %s\n", G_STRFUNC);
+
+ gimp_plug_in_manager_restore (gimp->plug_in_manager,
+ gimp_get_user_context (gimp), status_callback);
+
+ /* initialize babl fishes */
+ status_callback (_("Initialization"), "Babl Fishes", 0.0);
+ gimp_babl_init_fishes (status_callback);
+
+ gimp->restored = TRUE;
+}
+
+static gboolean
+gimp_real_exit (Gimp *gimp,
+ gboolean force)
+{
+ if (gimp->be_verbose)
+ g_print ("EXIT: %s\n", G_STRFUNC);
+
+ gimp_plug_in_manager_exit (gimp->plug_in_manager);
+ gimp_modules_unload (gimp);
+
+ gimp_data_factories_save (gimp);
+
+ gimp_templates_save (gimp);
+ gimp_parasiterc_save (gimp);
+ gimp_unitrc_save (gimp);
+
+ return FALSE; /* continue exiting */
+}
+
+Gimp *
+gimp_new (const gchar *name,
+ const gchar *session_name,
+ GFile *default_folder,
+ gboolean be_verbose,
+ gboolean no_data,
+ gboolean no_fonts,
+ gboolean no_interface,
+ gboolean use_shm,
+ gboolean use_cpu_accel,
+ gboolean console_messages,
+ gboolean show_playground,
+ gboolean show_debug_menu,
+ GimpStackTraceMode stack_trace_mode,
+ GimpPDBCompatMode pdb_compat_mode)
+{
+ Gimp *gimp;
+
+ g_return_val_if_fail (name != NULL, NULL);
+
+ gimp = g_object_new (GIMP_TYPE_GIMP,
+ "name", name,
+ "verbose", be_verbose ? TRUE : FALSE,
+ NULL);
+
+ if (default_folder)
+ gimp->default_folder = g_object_ref (default_folder);
+
+ gimp->session_name = g_strdup (session_name);
+ gimp->no_data = no_data ? TRUE : FALSE;
+ gimp->no_fonts = no_fonts ? TRUE : FALSE;
+ gimp->no_interface = no_interface ? TRUE : FALSE;
+ gimp->use_shm = use_shm ? TRUE : FALSE;
+ gimp->use_cpu_accel = use_cpu_accel ? TRUE : FALSE;
+ gimp->console_messages = console_messages ? TRUE : FALSE;
+ gimp->show_playground = show_playground ? TRUE : FALSE;
+ gimp->show_debug_menu = show_debug_menu ? TRUE : FALSE;
+ gimp->stack_trace_mode = stack_trace_mode;
+ gimp->pdb_compat_mode = pdb_compat_mode;
+
+ return gimp;
+}
+
+/**
+ * gimp_set_show_gui:
+ * @gimp:
+ * @show:
+ *
+ * Test cases that tests the UI typically don't want any windows to be
+ * presented during the test run. Allow them to set this.
+ **/
+void
+gimp_set_show_gui (Gimp *gimp,
+ gboolean show_gui)
+{
+ g_return_if_fail (GIMP_IS_GIMP (gimp));
+
+ gimp->show_gui = show_gui;
+}
+
+/**
+ * gimp_get_show_gui:
+ * @gimp:
+ *
+ * Returns: %TRUE if the GUI should be shown, %FALSE otherwise.
+ **/
+gboolean
+gimp_get_show_gui (Gimp *gimp)
+{
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), FALSE);
+
+ return gimp->show_gui;
+}
+
+static void
+gimp_global_config_notify (GObject *global_config,
+ GParamSpec *param_spec,
+ GObject *edit_config)
+{
+ GValue global_value = G_VALUE_INIT;
+ GValue edit_value = G_VALUE_INIT;
+
+ g_value_init (&global_value, param_spec->value_type);
+ g_value_init (&edit_value, param_spec->value_type);
+
+ g_object_get_property (global_config, param_spec->name, &global_value);
+ g_object_get_property (edit_config, param_spec->name, &edit_value);
+
+ if (g_param_values_cmp (param_spec, &global_value, &edit_value))
+ {
+ g_signal_handlers_block_by_func (edit_config,
+ gimp_edit_config_notify,
+ global_config);
+
+ g_object_set_property (edit_config, param_spec->name, &global_value);
+
+ g_signal_handlers_unblock_by_func (edit_config,
+ gimp_edit_config_notify,
+ global_config);
+ }
+
+ g_value_unset (&global_value);
+ g_value_unset (&edit_value);
+}
+
+static void
+gimp_edit_config_notify (GObject *edit_config,
+ GParamSpec *param_spec,
+ GObject *global_config)
+{
+ GValue edit_value = G_VALUE_INIT;
+ GValue global_value = G_VALUE_INIT;
+
+ g_value_init (&edit_value, param_spec->value_type);
+ g_value_init (&global_value, param_spec->value_type);
+
+ g_object_get_property (edit_config, param_spec->name, &edit_value);
+ g_object_get_property (global_config, param_spec->name, &global_value);
+
+ if (g_param_values_cmp (param_spec, &edit_value, &global_value))
+ {
+ if (param_spec->flags & GIMP_CONFIG_PARAM_RESTART)
+ {
+#ifdef GIMP_CONFIG_DEBUG
+ g_print ("NOT Applying edit_config change of '%s' to global_config "
+ "because it needs restart\n",
+ param_spec->name);
+#endif
+ }
+ else
+ {
+#ifdef GIMP_CONFIG_DEBUG
+ g_print ("Applying edit_config change of '%s' to global_config\n",
+ param_spec->name);
+#endif
+ g_signal_handlers_block_by_func (global_config,
+ gimp_global_config_notify,
+ edit_config);
+
+ g_object_set_property (global_config, param_spec->name, &edit_value);
+
+ g_signal_handlers_unblock_by_func (global_config,
+ gimp_global_config_notify,
+ edit_config);
+ }
+ }
+
+ g_value_unset (&edit_value);
+ g_value_unset (&global_value);
+}
+
+void
+gimp_load_config (Gimp *gimp,
+ GFile *alternate_system_gimprc,
+ GFile *alternate_gimprc)
+{
+ GimpRc *gimprc;
+
+ g_return_if_fail (GIMP_IS_GIMP (gimp));
+ g_return_if_fail (alternate_system_gimprc == NULL ||
+ G_IS_FILE (alternate_system_gimprc));
+ g_return_if_fail (alternate_gimprc == NULL ||
+ G_IS_FILE (alternate_gimprc));
+ g_return_if_fail (gimp->config == NULL);
+ g_return_if_fail (gimp->edit_config == NULL);
+
+ if (gimp->be_verbose)
+ g_print ("INIT: %s\n", G_STRFUNC);
+
+ /* this needs to be done before gimprc loading because gimprc can
+ * use user defined units
+ */
+ gimp_unitrc_load (gimp);
+
+ gimprc = gimp_rc_new (G_OBJECT (gimp),
+ alternate_system_gimprc,
+ alternate_gimprc,
+ gimp->be_verbose);
+
+ gimp->config = GIMP_CORE_CONFIG (gimprc);
+
+ gimp->edit_config = gimp_config_duplicate (GIMP_CONFIG (gimp->config));
+
+ g_signal_connect_object (gimp->config, "notify",
+ G_CALLBACK (gimp_global_config_notify),
+ gimp->edit_config, 0);
+ g_signal_connect_object (gimp->edit_config, "notify",
+ G_CALLBACK (gimp_edit_config_notify),
+ gimp->config, 0);
+
+ if (! gimp->show_playground)
+ {
+ gboolean use_opencl;
+ gboolean use_npd_tool;
+ gboolean use_seamless_clone_tool;
+
+ /* Playground preferences is shown by default for unstable
+ * versions and if the associated CLI option was set. Additionally
+ * we want to show it if any of the playground options had been
+ * enabled. Otherwise you might end up getting blocked with a
+ * playground feature and forget where you can even disable it.
+ *
+ * Also we check this once at start when loading config, and not
+ * inside preferences-dialog.c because we don't want to end up
+ * with inconsistent behavior where you open once the Preferences,
+ * deactivate features, then back to preferences and the tab is
+ * gone.
+ */
+
+ g_object_get (gimp->edit_config,
+ "use-opencl", &use_opencl,
+ "playground-npd-tool", &use_npd_tool,
+ "playground-seamless-clone-tool", &use_seamless_clone_tool,
+ NULL);
+ if (use_opencl || use_npd_tool || use_seamless_clone_tool)
+ gimp->show_playground = TRUE;
+ }
+}
+
+void
+gimp_initialize (Gimp *gimp,
+ GimpInitStatusFunc status_callback)
+{
+ g_return_if_fail (GIMP_IS_GIMP (gimp));
+ g_return_if_fail (status_callback != NULL);
+ g_return_if_fail (GIMP_IS_CORE_CONFIG (gimp->config));
+
+ if (gimp->be_verbose)
+ g_print ("INIT: %s\n", G_STRFUNC);
+
+ g_signal_emit (gimp, gimp_signals[INITIALIZE], 0, status_callback);
+}
+
+/**
+ * gimp_restore:
+ * @gimp: a #Gimp object
+ * @error: a #GError for uncessful loading.
+ *
+ * This function always succeeds. If present, @error may be filled for
+ * possible feedback on data which failed to load. It doesn't imply any
+ * fatale error.
+ **/
+void
+gimp_restore (Gimp *gimp,
+ GimpInitStatusFunc status_callback,
+ GError **error)
+{
+ g_return_if_fail (GIMP_IS_GIMP (gimp));
+ g_return_if_fail (status_callback != NULL);
+
+ if (gimp->be_verbose)
+ g_print ("INIT: %s\n", G_STRFUNC);
+
+ /* initialize the global parasite table */
+ status_callback (_("Looking for data files"), _("Parasites"), 0.0);
+ gimp_parasiterc_load (gimp);
+
+ /* initialize the lists of gimp brushes, dynamics, patterns etc. */
+ gimp_data_factories_load (gimp, status_callback);
+
+ /* initialize the template list */
+ status_callback (NULL, _("Templates"), 0.8);
+ gimp_templates_load (gimp);
+
+ /* initialize the module list */
+ status_callback (NULL, _("Modules"), 0.9);
+ gimp_modules_load (gimp);
+
+ g_signal_emit (gimp, gimp_signals[RESTORE], 0, status_callback);
+
+ /* when done, make sure everything is clean, to clean out dirty
+ * states from data objects which reference each other and got
+ * dirtied by loading the referenced object
+ */
+ gimp_data_factories_data_clean (gimp);
+}
+
+/**
+ * gimp_is_restored:
+ * @gimp: a #Gimp object
+ *
+ * Return value: %TRUE if GIMP is completely started, %FALSE otherwise.
+ **/
+gboolean
+gimp_is_restored (Gimp *gimp)
+{
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), FALSE);
+
+ return gimp->restored;
+}
+
+/**
+ * gimp_exit:
+ * @gimp: a #Gimp object
+ * @force: whether to force the application to quit
+ *
+ * Exit this GIMP session. Unless @force is %TRUE, the user is queried
+ * whether unsaved images should be saved and can cancel the operation.
+ **/
+void
+gimp_exit (Gimp *gimp,
+ gboolean force)
+{
+ gboolean handled;
+ GList *image_iter;
+
+ g_return_if_fail (GIMP_IS_GIMP (gimp));
+
+ if (gimp->be_verbose)
+ g_print ("EXIT: %s\n", G_STRFUNC);
+
+ g_signal_emit (gimp, gimp_signals[EXIT], 0,
+ force ? TRUE : FALSE,
+ &handled);
+
+ if (handled)
+ return;
+
+ /* Get rid of images without display. We do this *after* handling the
+ * usual exit callbacks, because the things that are torn down there
+ * might have references to these images (for instance GimpActions
+ * in the UI manager).
+ */
+ while ((image_iter = gimp_get_image_iter (gimp)))
+ {
+ GimpImage *image = image_iter->data;
+
+ g_object_unref (image);
+ }
+}
+
+GList *
+gimp_get_image_iter (Gimp *gimp)
+{
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
+
+ return GIMP_LIST (gimp->images)->queue->head;
+}
+
+GList *
+gimp_get_display_iter (Gimp *gimp)
+{
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
+
+ return GIMP_LIST (gimp->displays)->queue->head;
+}
+
+GList *
+gimp_get_image_windows (Gimp *gimp)
+{
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
+
+ return g_list_copy (gimp->image_windows);
+}
+
+GList *
+gimp_get_paint_info_iter (Gimp *gimp)
+{
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
+
+ return GIMP_LIST (gimp->paint_info_list)->queue->head;
+}
+
+GList *
+gimp_get_tool_info_iter (Gimp *gimp)
+{
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
+
+ return GIMP_LIST (gimp->tool_info_list)->queue->head;
+}
+
+GList *
+gimp_get_tool_item_iter (Gimp *gimp)
+{
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
+
+ return GIMP_LIST (gimp->tool_item_list)->queue->head;
+}
+
+GList *
+gimp_get_tool_item_ui_iter (Gimp *gimp)
+{
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
+
+ return GIMP_LIST (gimp->tool_item_ui_list)->queue->head;
+}
+
+GimpObject *
+gimp_get_clipboard_object (Gimp *gimp)
+{
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
+
+ if (gimp->clipboard_image)
+ return GIMP_OBJECT (gimp->clipboard_image);
+
+ return GIMP_OBJECT (gimp->clipboard_buffer);
+}
+
+void
+gimp_set_clipboard_image (Gimp *gimp,
+ GimpImage *image)
+{
+ g_return_if_fail (GIMP_IS_GIMP (gimp));
+ g_return_if_fail (image == NULL || GIMP_IS_IMAGE (image));
+
+ g_clear_object (&gimp->clipboard_buffer);
+ g_set_object (&gimp->clipboard_image, image);
+
+ /* we want the signal emission */
+ g_signal_emit (gimp, gimp_signals[CLIPBOARD_CHANGED], 0);
+}
+
+GimpImage *
+gimp_get_clipboard_image (Gimp *gimp)
+{
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
+
+ return gimp->clipboard_image;
+}
+
+void
+gimp_set_clipboard_buffer (Gimp *gimp,
+ GimpBuffer *buffer)
+{
+ g_return_if_fail (GIMP_IS_GIMP (gimp));
+ g_return_if_fail (buffer == NULL || GIMP_IS_BUFFER (buffer));
+
+ g_clear_object (&gimp->clipboard_image);
+ g_set_object (&gimp->clipboard_buffer, buffer);
+
+ /* we want the signal emission */
+ g_signal_emit (gimp, gimp_signals[CLIPBOARD_CHANGED], 0);
+}
+
+GimpBuffer *
+gimp_get_clipboard_buffer (Gimp *gimp)
+{
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
+
+ return gimp->clipboard_buffer;
+}
+
+GimpImage *
+gimp_create_image (Gimp *gimp,
+ gint width,
+ gint height,
+ GimpImageBaseType type,
+ GimpPrecision precision,
+ gboolean attach_comment)
+{
+ GimpImage *image;
+
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
+
+ image = gimp_image_new (gimp, width, height, type, precision);
+
+ if (attach_comment)
+ {
+ const gchar *comment;
+
+ comment = gimp_template_get_comment (gimp->config->default_image);
+
+ if (comment)
+ {
+ GimpParasite *parasite = gimp_parasite_new ("gimp-comment",
+ GIMP_PARASITE_PERSISTENT,
+ strlen (comment) + 1,
+ comment);
+ gimp_image_parasite_attach (image, parasite, FALSE);
+ gimp_parasite_free (parasite);
+ }
+ }
+
+ return image;
+}
+
+void
+gimp_set_default_context (Gimp *gimp,
+ GimpContext *context)
+{
+ g_return_if_fail (GIMP_IS_GIMP (gimp));
+ g_return_if_fail (context == NULL || GIMP_IS_CONTEXT (context));
+
+ g_set_object (&gimp->default_context, context);
+}
+
+GimpContext *
+gimp_get_default_context (Gimp *gimp)
+{
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
+
+ return gimp->default_context;
+}
+
+void
+gimp_set_user_context (Gimp *gimp,
+ GimpContext *context)
+{
+ g_return_if_fail (GIMP_IS_GIMP (gimp));
+ g_return_if_fail (context == NULL || GIMP_IS_CONTEXT (context));
+
+ g_set_object (&gimp->user_context, context);
+}
+
+GimpContext *
+gimp_get_user_context (Gimp *gimp)
+{
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
+
+ return gimp->user_context;
+}
+
+GimpToolInfo *
+gimp_get_tool_info (Gimp *gimp,
+ const gchar *tool_id)
+{
+ gpointer info;
+
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
+ g_return_val_if_fail (tool_id != NULL, NULL);
+
+ info = gimp_container_get_child_by_name (gimp->tool_info_list, tool_id);
+
+ return (GimpToolInfo *) info;
+}
+
+/**
+ * gimp_message:
+ * @gimp: a pointer to the %Gimp object
+ * @handler: either a %GimpProgress or a %GtkWidget pointer
+ * @severity: severity of the message
+ * @format: printf-like format string
+ * @...: arguments to use with @format
+ *
+ * Present a message to the user. How exactly the message is displayed
+ * depends on the @severity, the @handler object and user preferences.
+ **/
+void
+gimp_message (Gimp *gimp,
+ GObject *handler,
+ GimpMessageSeverity severity,
+ const gchar *format,
+ ...)
+{
+ va_list args;
+
+ va_start (args, format);
+
+ gimp_message_valist (gimp, handler, severity, format, args);
+
+ va_end (args);
+}
+
+/**
+ * gimp_message_valist:
+ * @gimp: a pointer to the %Gimp object
+ * @handler: either a %GimpProgress or a %GtkWidget pointer
+ * @severity: severity of the message
+ * @format: printf-like format string
+ * @args: arguments to use with @format
+ *
+ * See documentation for gimp_message().
+ **/
+void
+gimp_message_valist (Gimp *gimp,
+ GObject *handler,
+ GimpMessageSeverity severity,
+ const gchar *format,
+ va_list args)
+{
+ gchar *message;
+
+ g_return_if_fail (GIMP_IS_GIMP (gimp));
+ g_return_if_fail (handler == NULL || G_IS_OBJECT (handler));
+ g_return_if_fail (format != NULL);
+
+ message = g_strdup_vprintf (format, args);
+
+ gimp_show_message (gimp, handler, severity, NULL, message);
+
+ g_free (message);
+}
+
+void
+gimp_message_literal (Gimp *gimp,
+ GObject *handler,
+ GimpMessageSeverity severity,
+ const gchar *message)
+{
+ g_return_if_fail (GIMP_IS_GIMP (gimp));
+ g_return_if_fail (handler == NULL || G_IS_OBJECT (handler));
+ g_return_if_fail (message != NULL);
+
+ gimp_show_message (gimp, handler, severity, NULL, message);
+}
+
+void
+gimp_filter_history_changed (Gimp *gimp)
+{
+ g_return_if_fail (GIMP_IS_GIMP (gimp));
+
+ g_signal_emit (gimp, gimp_signals[FILTER_HISTORY_CHANGED], 0);
+}
+
+void
+gimp_image_opened (Gimp *gimp,
+ GFile *file)
+{
+ g_return_if_fail (GIMP_IS_GIMP (gimp));
+ g_return_if_fail (G_IS_FILE (file));
+
+ g_signal_emit (gimp, gimp_signals[IMAGE_OPENED], 0, file);
+}
+
+GFile *
+gimp_get_temp_file (Gimp *gimp,
+ const gchar *extension)
+{
+ static gint id = 0;
+ static gint pid;
+ gchar *basename;
+ GFile *dir;
+ GFile *file;
+
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
+
+ if (id == 0)
+ pid = gimp_get_pid ();
+
+ if (extension)
+ basename = g_strdup_printf ("gimp-temp-%d%d.%s", pid, id++, extension);
+ else
+ basename = g_strdup_printf ("gimp-temp-%d%d", pid, id++);
+
+ dir = gimp_file_new_for_config_path (GIMP_GEGL_CONFIG (gimp->config)->temp_path,
+ NULL);
+ if (! g_file_query_exists (dir, NULL))
+ {
+ /* Try to make the temp directory if it doesn't exist.
+ * Ignore any error.
+ */
+ g_file_make_directory_with_parents (dir, NULL, NULL);
+ }
+ file = g_file_get_child (dir, basename);
+ g_free (basename);
+ g_object_unref (dir);
+
+ return file;
+}
diff --git a/app/core/gimp.h b/app/core/gimp.h
new file mode 100644
index 0000000..d4b1e39
--- /dev/null
+++ b/app/core/gimp.h
@@ -0,0 +1,248 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-1997 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_H__
+#define __GIMP_H__
+
+
+#include "gimpobject.h"
+#include "gimp-gui.h"
+
+
+#define GIMP_TYPE_GIMP (gimp_get_type ())
+#define GIMP(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_GIMP, Gimp))
+#define GIMP_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_GIMP, GimpClass))
+#define GIMP_IS_GIMP(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_GIMP))
+#define GIMP_IS_GIMP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_GIMP))
+
+
+typedef struct _GimpClass GimpClass;
+
+struct _Gimp
+{
+ GimpObject parent_instance;
+
+ GimpCoreConfig *config;
+ GimpCoreConfig *edit_config; /* don't use this one, it's just
+ * for the preferences dialog
+ */
+ gchar *session_name;
+ GFile *default_folder;
+
+ gboolean be_verbose;
+ gboolean no_data;
+ gboolean no_fonts;
+ gboolean no_interface;
+ gboolean show_gui;
+ gboolean use_shm;
+ gboolean use_cpu_accel;
+ GimpMessageHandlerType message_handler;
+ gboolean console_messages;
+ gboolean show_playground;
+ gboolean show_debug_menu;
+ GimpStackTraceMode stack_trace_mode;
+ GimpPDBCompatMode pdb_compat_mode;
+
+ GimpGui gui; /* gui vtable */
+
+ gboolean restored; /* becomes TRUE in gimp_restore() */
+ gboolean initialized; /* Fully initialized (only set once at start). */
+
+ gint busy;
+ guint busy_idle_id;
+
+ GList *user_units;
+ gint n_user_units;
+
+ GimpParasiteList *parasites;
+
+ GimpContainer *paint_info_list;
+ GimpPaintInfo *standard_paint_info;
+
+ GimpModuleDB *module_db;
+ gboolean write_modulerc;
+
+ GimpPlugInManager *plug_in_manager;
+
+ GList *filter_history;
+
+ GimpContainer *images;
+ guint32 next_guide_ID;
+ guint32 next_sample_point_ID;
+ GimpIdTable *image_table;
+ GimpIdTable *item_table;
+
+ GimpContainer *displays;
+ gint next_display_ID;
+
+ GList *image_windows;
+
+ GimpImage *clipboard_image;
+ GimpBuffer *clipboard_buffer;
+ GimpContainer *named_buffers;
+
+ GimpDataFactory *brush_factory;
+ GimpDataFactory *dynamics_factory;
+ GimpDataFactory *mybrush_factory;
+ GimpDataFactory *pattern_factory;
+ GimpDataFactory *gradient_factory;
+ GimpDataFactory *palette_factory;
+ GimpDataFactory *font_factory;
+ GimpDataFactory *tool_preset_factory;
+
+ GimpTagCache *tag_cache;
+
+ GimpPDB *pdb;
+
+ GimpContainer *tool_info_list;
+ GimpToolInfo *standard_tool_info;
+
+ GimpContainer *tool_item_list;
+ GimpContainer *tool_item_ui_list;
+
+ /* the opened and saved images in MRU order */
+ GimpContainer *documents;
+
+ /* image_new values */
+ GimpContainer *templates;
+ GimpTemplate *image_new_last_template;
+
+ /* the list of all contexts */
+ GList *context_list;
+
+ /* the default context which is initialized from gimprc */
+ GimpContext *default_context;
+
+ /* the context used by the interface */
+ GimpContext *user_context;
+};
+
+struct _GimpClass
+{
+ GimpObjectClass parent_class;
+
+ void (* initialize) (Gimp *gimp,
+ GimpInitStatusFunc status_callback);
+ void (* restore) (Gimp *gimp,
+ GimpInitStatusFunc status_callback);
+ gboolean (* exit) (Gimp *gimp,
+ gboolean force);
+
+ void (* clipboard_changed) (Gimp *gimp);
+
+ void (* filter_history_changed) (Gimp *gimp);
+
+ /* emitted if an image is loaded and opened with a display */
+ void (* image_opened) (Gimp *gimp,
+ GFile *file);
+};
+
+
+GType gimp_get_type (void) G_GNUC_CONST;
+
+Gimp * gimp_new (const gchar *name,
+ const gchar *session_name,
+ GFile *default_folder,
+ gboolean be_verbose,
+ gboolean no_data,
+ gboolean no_fonts,
+ gboolean no_interface,
+ gboolean use_shm,
+ gboolean use_cpu_accel,
+ gboolean console_messages,
+ gboolean show_playground,
+ gboolean show_debug_menu,
+ GimpStackTraceMode stack_trace_mode,
+ GimpPDBCompatMode pdb_compat_mode);
+void gimp_set_show_gui (Gimp *gimp,
+ gboolean show_gui);
+gboolean gimp_get_show_gui (Gimp *gimp);
+
+void gimp_load_config (Gimp *gimp,
+ GFile *alternate_system_gimprc,
+ GFile *alternate_gimprc);
+void gimp_initialize (Gimp *gimp,
+ GimpInitStatusFunc status_callback);
+void gimp_restore (Gimp *gimp,
+ GimpInitStatusFunc status_callback,
+ GError **error);
+gboolean gimp_is_restored (Gimp *gimp);
+
+void gimp_exit (Gimp *gimp,
+ gboolean force);
+
+GList * gimp_get_image_iter (Gimp *gimp);
+GList * gimp_get_display_iter (Gimp *gimp);
+GList * gimp_get_image_windows (Gimp *gimp);
+GList * gimp_get_paint_info_iter (Gimp *gimp);
+GList * gimp_get_tool_info_iter (Gimp *gimp);
+GList * gimp_get_tool_item_iter (Gimp *gimp);
+GList * gimp_get_tool_item_ui_iter (Gimp *gimp);
+
+GimpObject * gimp_get_clipboard_object (Gimp *gimp);
+
+void gimp_set_clipboard_image (Gimp *gimp,
+ GimpImage *image);
+GimpImage * gimp_get_clipboard_image (Gimp *gimp);
+
+void gimp_set_clipboard_buffer (Gimp *gimp,
+ GimpBuffer *buffer);
+GimpBuffer * gimp_get_clipboard_buffer (Gimp *gimp);
+
+GimpImage * gimp_create_image (Gimp *gimp,
+ gint width,
+ gint height,
+ GimpImageBaseType type,
+ GimpPrecision precision,
+ gboolean attach_comment);
+
+void gimp_set_default_context (Gimp *gimp,
+ GimpContext *context);
+GimpContext * gimp_get_default_context (Gimp *gimp);
+
+void gimp_set_user_context (Gimp *gimp,
+ GimpContext *context);
+GimpContext * gimp_get_user_context (Gimp *gimp);
+
+GimpToolInfo * gimp_get_tool_info (Gimp *gimp,
+ const gchar *tool_name);
+
+void gimp_message (Gimp *gimp,
+ GObject *handler,
+ GimpMessageSeverity severity,
+ const gchar *format,
+ ...) G_GNUC_PRINTF (4, 5);
+void gimp_message_valist (Gimp *gimp,
+ GObject *handler,
+ GimpMessageSeverity severity,
+ const gchar *format,
+ va_list args) G_GNUC_PRINTF (4, 0);
+void gimp_message_literal (Gimp *gimp,
+ GObject *handler,
+ GimpMessageSeverity severity,
+ const gchar *message);
+
+void gimp_filter_history_changed (Gimp *gimp);
+
+void gimp_image_opened (Gimp *gimp,
+ GFile *file);
+
+GFile * gimp_get_temp_file (Gimp *gimp,
+ const gchar *extension);
+
+
+#endif /* __GIMP_H__ */
diff --git a/app/core/gimpasync.c b/app/core/gimpasync.c
new file mode 100644
index 0000000..322d57d
--- /dev/null
+++ b/app/core/gimpasync.c
@@ -0,0 +1,752 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpasync.c
+ * Copyright (C) 2018 Ell
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "core-types.h"
+
+#include "gimpasync.h"
+#include "gimpcancelable.h"
+#include "gimpmarshal.h"
+#include "gimpwaitable.h"
+
+
+/* GimpAsync represents an asynchronous task. Both the public and the
+ * protected interfaces are intentionally minimal at this point, to keep things
+ * simple. They may be extended in the future as needed.
+ *
+ * GimpAsync implements the GimpWaitable and GimpCancelable interfaces.
+ *
+ * Upon creation, a GimpAsync object is in the "running" state. Once the task
+ * is complete (and before the object's destruction), it should be transitioned
+ * to the "stopped" state, using either 'gimp_async_finish()' or
+ * 'gimp_async_abort()'.
+ *
+ * Similarly, upon creation, a GimpAsync object is said to be "unsynced". It
+ * becomes synced once the execution of any of the completion callbacks added
+ * through 'gimp_async_add_callback()' had started, or after a successful call
+ * to one of the 'gimp_waitable_wait()' family of functions.
+ *
+ * Note that certain GimpAsync functions may only be called during a certain
+ * state, on a certain thread, or depending on whether or not the object is
+ * synced, as detailed for each function. When referring to threads, the "main
+ * thread" is the thread running the main loop, or any thread whose execution
+ * is synchronized with the main thread, and the "async thread" is the thread
+ * calling 'gimp_async_finish()' or 'gimp_async_abort()' (which may also be the
+ * main thread), or any thread whose execution is synchronized with the async
+ * thread.
+ */
+
+
+/* #define TIME_ASYNC_OPS */
+
+
+enum
+{
+ WAITING,
+ LAST_SIGNAL
+};
+
+
+typedef struct _GimpAsyncCallbackInfo GimpAsyncCallbackInfo;
+
+
+struct _GimpAsyncCallbackInfo
+{
+ GimpAsync *async;
+ GimpAsyncCallback callback;
+ gpointer data;
+ gpointer gobject;
+};
+
+struct _GimpAsyncPrivate
+{
+ GMutex mutex;
+ GCond cond;
+
+ GQueue callbacks;
+
+ gpointer result;
+ GDestroyNotify result_destroy_func;
+
+ guint idle_id;
+
+ gboolean stopped;
+ gboolean finished;
+ gboolean synced;
+ gboolean canceled;
+
+#ifdef TIME_ASYNC_OPS
+ guint64 start_time;
+#endif
+};
+
+
+/* local function prototypes */
+
+static void gimp_async_waitable_iface_init (GimpWaitableInterface *iface);
+
+static void gimp_async_cancelable_iface_init (GimpCancelableInterface *iface);
+
+static void gimp_async_finalize (GObject *object);
+
+static void gimp_async_wait (GimpWaitable *waitable);
+static gboolean gimp_async_try_wait (GimpWaitable *waitable);
+static gboolean gimp_async_wait_until (GimpWaitable *waitable,
+ gint64 end_time);
+
+static void gimp_async_cancel (GimpCancelable *cancelable);
+
+static gboolean gimp_async_idle (GimpAsync *async);
+
+static void gimp_async_callback_weak_notify (GimpAsyncCallbackInfo *callback_info,
+ GObject *gobject);
+
+static void gimp_async_stop (GimpAsync *async);
+static void gimp_async_run_callbacks (GimpAsync *async);
+
+
+G_DEFINE_TYPE_WITH_CODE (GimpAsync, gimp_async, G_TYPE_OBJECT,
+ G_ADD_PRIVATE (GimpAsync)
+ G_IMPLEMENT_INTERFACE (GIMP_TYPE_WAITABLE,
+ gimp_async_waitable_iface_init)
+ G_IMPLEMENT_INTERFACE (GIMP_TYPE_CANCELABLE,
+ gimp_async_cancelable_iface_init))
+
+#define parent_class gimp_async_parent_class
+
+static guint async_signals[LAST_SIGNAL] = { 0 };
+
+
+/* local variables */
+
+static volatile gint gimp_async_n_running = 0;
+
+
+/* private functions */
+
+
+static void
+gimp_async_class_init (GimpAsyncClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ async_signals[WAITING] =
+ g_signal_new ("waiting",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpAsyncClass, waiting),
+ NULL, NULL,
+ gimp_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ object_class->finalize = gimp_async_finalize;
+}
+
+static void
+gimp_async_waitable_iface_init (GimpWaitableInterface *iface)
+{
+ iface->wait = gimp_async_wait;
+ iface->try_wait = gimp_async_try_wait;
+ iface->wait_until = gimp_async_wait_until;
+}
+
+static void
+gimp_async_cancelable_iface_init (GimpCancelableInterface *iface)
+{
+ iface->cancel = gimp_async_cancel;
+}
+
+static void
+gimp_async_init (GimpAsync *async)
+{
+ async->priv = gimp_async_get_instance_private (async);
+
+ g_mutex_init (&async->priv->mutex);
+ g_cond_init (&async->priv->cond);
+
+ g_queue_init (&async->priv->callbacks);
+
+ g_atomic_int_inc (&gimp_async_n_running);
+
+#ifdef TIME_ASYNC_OPS
+ async->priv->start_time = g_get_monotonic_time ();
+#endif
+}
+
+static void
+gimp_async_finalize (GObject *object)
+{
+ GimpAsync *async = GIMP_ASYNC (object);
+
+ g_warn_if_fail (async->priv->stopped);
+ g_warn_if_fail (async->priv->idle_id == 0);
+ g_warn_if_fail (g_queue_is_empty (&async->priv->callbacks));
+
+ if (async->priv->finished &&
+ async->priv->result &&
+ async->priv->result_destroy_func)
+ {
+ async->priv->result_destroy_func (async->priv->result);
+
+ async->priv->result = NULL;
+ }
+
+ g_cond_clear (&async->priv->cond);
+ g_mutex_clear (&async->priv->mutex);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+/* waits for 'waitable' to transition to the "stopped" state. if 'waitable' is
+ * already stopped, returns immediately.
+ *
+ * after the call, all callbacks previously added through
+ * 'gimp_async_add_callback()' are guaranteed to have been called.
+ *
+ * may only be called on the main thread.
+ */
+static void
+gimp_async_wait (GimpWaitable *waitable)
+{
+ GimpAsync *async = GIMP_ASYNC (waitable);
+
+ g_mutex_lock (&async->priv->mutex);
+
+ if (! async->priv->stopped)
+ {
+ g_signal_emit (async, async_signals[WAITING], 0);
+
+ while (! async->priv->stopped)
+ g_cond_wait (&async->priv->cond, &async->priv->mutex);
+ }
+
+ g_mutex_unlock (&async->priv->mutex);
+
+ gimp_async_run_callbacks (async);
+}
+
+/* same as 'gimp_async_wait()', but returns immediately if 'waitable' is not in
+ * the "stopped" state.
+ *
+ * returns TRUE if 'waitable' has transitioned to the "stopped" state, or FALSE
+ * otherwise.
+ */
+static gboolean
+gimp_async_try_wait (GimpWaitable *waitable)
+{
+ GimpAsync *async = GIMP_ASYNC (waitable);
+
+ g_mutex_lock (&async->priv->mutex);
+
+ if (! async->priv->stopped)
+ {
+ g_mutex_unlock (&async->priv->mutex);
+
+ return FALSE;
+ }
+
+ g_mutex_unlock (&async->priv->mutex);
+
+ gimp_async_run_callbacks (async);
+
+ return TRUE;
+}
+
+/* same as 'gimp_async_wait()', taking an additional 'end_time' parameter,
+ * specifying the maximal monotonic time until which to wait for 'waitable' to
+ * stop.
+ *
+ * returns TRUE if 'waitable' has transitioned to the "stopped" state, or FALSE
+ * if the wait was interrupted before the transition.
+ */
+static gboolean
+gimp_async_wait_until (GimpWaitable *waitable,
+ gint64 end_time)
+{
+ GimpAsync *async = GIMP_ASYNC (waitable);
+
+ g_mutex_lock (&async->priv->mutex);
+
+ if (! async->priv->stopped)
+ {
+ g_signal_emit (async, async_signals[WAITING], 0);
+
+ while (! async->priv->stopped)
+ {
+ if (! g_cond_wait_until (&async->priv->cond, &async->priv->mutex,
+ end_time))
+ {
+ g_mutex_unlock (&async->priv->mutex);
+
+ return FALSE;
+ }
+ }
+ }
+
+ g_mutex_unlock (&async->priv->mutex);
+
+ gimp_async_run_callbacks (async);
+
+ return TRUE;
+}
+
+/* requests the cancellation of the task managed by 'cancelable'.
+ *
+ * note that 'gimp_async_cancel()' doesn't directly cause 'cancelable' to be
+ * stopped, nor synced. furthermore, 'cancelable' may still complete
+ * successfully even when cancellation has been requested.
+ *
+ * may only be called on the main thread.
+ */
+static void
+gimp_async_cancel (GimpCancelable *cancelable)
+{
+ GimpAsync *async = GIMP_ASYNC (cancelable);
+
+ async->priv->canceled = TRUE;
+}
+
+static gboolean
+gimp_async_idle (GimpAsync *async)
+{
+ gimp_waitable_wait (GIMP_WAITABLE (async));
+
+ return G_SOURCE_REMOVE;
+}
+
+static void
+gimp_async_callback_weak_notify (GimpAsyncCallbackInfo *callback_info,
+ GObject *gobject)
+{
+ GimpAsync *async = callback_info->async;
+ gboolean unref_async = FALSE;
+
+ g_mutex_lock (&async->priv->mutex);
+
+ g_queue_remove (&async->priv->callbacks, callback_info);
+
+ g_slice_free (GimpAsyncCallbackInfo, callback_info);
+
+ if (g_queue_is_empty (&async->priv->callbacks) && async->priv->idle_id)
+ {
+ g_source_remove (async->priv->idle_id);
+ async->priv->idle_id = 0;
+
+ unref_async = TRUE;
+ }
+
+ g_mutex_unlock (&async->priv->mutex);
+
+ if (unref_async)
+ g_object_unref (async);
+}
+
+static void
+gimp_async_stop (GimpAsync *async)
+{
+#ifdef TIME_ASYNC_OPS
+ {
+ guint64 time = g_get_monotonic_time ();
+
+ g_printerr ("Asynchronous operation took %g seconds%s\n",
+ (time - async->priv->start_time) / 1000000.0,
+ async->priv->finished ? "" : " (aborted)");
+ }
+#endif
+
+ g_atomic_int_dec_and_test (&gimp_async_n_running);
+
+ if (! g_queue_is_empty (&async->priv->callbacks))
+ {
+ g_object_ref (async);
+
+ async->priv->idle_id = g_idle_add_full (G_PRIORITY_DEFAULT,
+ (GSourceFunc) gimp_async_idle,
+ async, NULL);
+ }
+
+ async->priv->stopped = TRUE;
+
+ g_cond_broadcast (&async->priv->cond);
+}
+
+static void
+gimp_async_run_callbacks (GimpAsync *async)
+{
+ GimpAsyncCallbackInfo *callback_info;
+ gboolean unref_async = FALSE;
+
+ if (async->priv->idle_id)
+ {
+ g_source_remove (async->priv->idle_id);
+ async->priv->idle_id = 0;
+
+ unref_async = TRUE;
+ }
+
+ async->priv->synced = TRUE;
+
+ while ((callback_info = g_queue_pop_head (&async->priv->callbacks)))
+ {
+ if (callback_info->gobject)
+ {
+ g_object_ref (callback_info->gobject);
+
+ g_object_weak_unref (callback_info->gobject,
+ (GWeakNotify) gimp_async_callback_weak_notify,
+ callback_info);
+ }
+
+ callback_info->callback (async, callback_info->data);
+
+ if (callback_info->gobject)
+ g_object_unref (callback_info->gobject);
+
+ g_slice_free (GimpAsyncCallbackInfo, callback_info);
+ }
+
+ if (unref_async)
+ g_object_unref (async);
+}
+
+
+/* public functions */
+
+
+/* creates a new GimpAsync object, initially unsynced and placed in the
+ * "running" state.
+ */
+GimpAsync *
+gimp_async_new (void)
+{
+ return g_object_new (GIMP_TYPE_ASYNC,
+ NULL);
+}
+
+/* checks if 'async' is synced.
+ *
+ * may only be called on the main thread.
+ */
+gboolean
+gimp_async_is_synced (GimpAsync *async)
+{
+ g_return_val_if_fail (GIMP_IS_ASYNC (async), FALSE);
+
+ return async->priv->synced;
+}
+
+/* registers a callback to be called when 'async' transitions to the "stopped"
+ * state. if 'async' is already stopped, the callback may be called directly.
+ *
+ * callbacks are called in the order in which they were added. 'async' is
+ * guaranteed to be kept alive, even without an external reference, between the
+ * point where it was stopped, and until all callbacks added while 'async' was
+ * externally referenced have been called.
+ *
+ * the callback is guaranteed to be called on the main thread.
+ *
+ * may only be called on the main thread.
+ */
+void
+gimp_async_add_callback (GimpAsync *async,
+ GimpAsyncCallback callback,
+ gpointer data)
+{
+ GimpAsyncCallbackInfo *callback_info;
+
+ g_return_if_fail (GIMP_IS_ASYNC (async));
+ g_return_if_fail (callback != NULL);
+
+ g_mutex_lock (&async->priv->mutex);
+
+ if (async->priv->stopped && g_queue_is_empty (&async->priv->callbacks))
+ {
+ async->priv->synced = TRUE;
+
+ g_mutex_unlock (&async->priv->mutex);
+
+ callback (async, data);
+
+ return;
+ }
+
+ callback_info = g_slice_new0 (GimpAsyncCallbackInfo);
+ callback_info->async = async;
+ callback_info->callback = callback;
+ callback_info->data = data;
+
+ g_queue_push_tail (&async->priv->callbacks, callback_info);
+
+ g_mutex_unlock (&async->priv->mutex);
+}
+
+/* same as 'gimp_async_add_callback()', however, takes an additional 'gobject'
+ * argument, which should be a GObject.
+ *
+ * 'gobject' is guaranteed to remain alive for the duration of the callback.
+ *
+ * When 'gobject' is destroyed, the callback is automatically removed.
+ */
+void
+gimp_async_add_callback_for_object (GimpAsync *async,
+ GimpAsyncCallback callback,
+ gpointer data,
+ gpointer gobject)
+{
+ GimpAsyncCallbackInfo *callback_info;
+
+ g_return_if_fail (GIMP_IS_ASYNC (async));
+ g_return_if_fail (callback != NULL);
+ g_return_if_fail (G_IS_OBJECT (gobject));
+
+ g_mutex_lock (&async->priv->mutex);
+
+ if (async->priv->stopped && g_queue_is_empty (&async->priv->callbacks))
+ {
+ async->priv->synced = TRUE;
+
+ g_mutex_unlock (&async->priv->mutex);
+
+ g_object_ref (gobject);
+
+ callback (async, data);
+
+ g_object_unref (gobject);
+
+ return;
+ }
+
+ callback_info = g_slice_new0 (GimpAsyncCallbackInfo);
+ callback_info->async = async;
+ callback_info->callback = callback;
+ callback_info->data = data;
+ callback_info->gobject = gobject;
+
+ g_queue_push_tail (&async->priv->callbacks, callback_info);
+
+ g_object_weak_ref (gobject,
+ (GWeakNotify) gimp_async_callback_weak_notify,
+ callback_info);
+
+ g_mutex_unlock (&async->priv->mutex);
+}
+
+/* removes all callbacks previously registered through
+ * 'gimp_async_add_callback()', matching 'callback' and 'data', which hasn't
+ * been called yet.
+ *
+ * may only be called on the main thread.
+ */
+void
+gimp_async_remove_callback (GimpAsync *async,
+ GimpAsyncCallback callback,
+ gpointer data)
+{
+ GList *iter;
+ gboolean unref_async = FALSE;
+
+ g_return_if_fail (GIMP_IS_ASYNC (async));
+ g_return_if_fail (callback != NULL);
+
+ g_mutex_lock (&async->priv->mutex);
+
+ iter = g_queue_peek_head_link (&async->priv->callbacks);
+
+ while (iter)
+ {
+ GimpAsyncCallbackInfo *callback_info = iter->data;
+ GList *next = g_list_next (iter);
+
+ if (callback_info->callback == callback &&
+ callback_info->data == data)
+ {
+ if (callback_info->gobject)
+ {
+ g_object_weak_unref (
+ callback_info->gobject,
+ (GWeakNotify) gimp_async_callback_weak_notify,
+ callback_info);
+ }
+
+ g_queue_delete_link (&async->priv->callbacks, iter);
+
+ g_slice_free (GimpAsyncCallbackInfo, callback_info);
+ }
+
+ iter = next;
+ }
+
+ if (g_queue_is_empty (&async->priv->callbacks) && async->priv->idle_id)
+ {
+ g_source_remove (async->priv->idle_id);
+ async->priv->idle_id = 0;
+
+ unref_async = TRUE;
+ }
+
+ g_mutex_unlock (&async->priv->mutex);
+
+ if (unref_async)
+ g_object_unref (async);
+}
+
+/* checks if 'async' is in the "stopped" state.
+ *
+ * may only be called on the async thread.
+ */
+gboolean
+gimp_async_is_stopped (GimpAsync *async)
+{
+ g_return_val_if_fail (GIMP_IS_ASYNC (async), FALSE);
+
+ return async->priv->stopped;
+}
+
+/* transitions 'async' to the "stopped" state, indicating that the task
+ * completed normally, possibly providing a result.
+ *
+ * 'async' shall be in the "running" state.
+ *
+ * may only be called on the async thread.
+ */
+void
+gimp_async_finish (GimpAsync *async,
+ gpointer result)
+{
+ gimp_async_finish_full (async, result, NULL);
+}
+
+/* same as 'gimp_async_finish()', taking an additional GDestroyNotify function,
+ * used for freeing the result when 'async' is destroyed.
+ */
+void
+gimp_async_finish_full (GimpAsync *async,
+ gpointer result,
+ GDestroyNotify result_destroy_func)
+{
+ g_return_if_fail (GIMP_IS_ASYNC (async));
+ g_return_if_fail (! async->priv->stopped);
+
+ g_mutex_lock (&async->priv->mutex);
+
+ async->priv->finished = TRUE;
+ async->priv->result = result;
+ async->priv->result_destroy_func = result_destroy_func;
+
+ gimp_async_stop (async);
+
+ g_mutex_unlock (&async->priv->mutex);
+}
+
+/* checks if 'async' completed normally, using 'gimp_async_finish()' (in
+ * contrast to 'gimp_async_abort()').
+ *
+ * 'async' shall be in the "stopped" state.
+ *
+ * may only be called on the async thread, or on the main thread when 'async'
+ * is synced.
+ */
+gboolean
+gimp_async_is_finished (GimpAsync *async)
+{
+ g_return_val_if_fail (GIMP_IS_ASYNC (async), FALSE);
+ g_return_val_if_fail (async->priv->stopped, FALSE);
+
+ return async->priv->finished;
+}
+
+/* returns the result of 'async', as passed to 'gimp_async_finish()'.
+ *
+ * 'async' shall be in the "stopped" state, and should have completed normally.
+ *
+ * may only be called on the async thread, or on the main thread when 'async'
+ * is synced.
+ */
+gpointer
+gimp_async_get_result (GimpAsync *async)
+{
+ g_return_val_if_fail (GIMP_IS_ASYNC (async), NULL);
+ g_return_val_if_fail (async->priv->stopped, NULL);
+ g_return_val_if_fail (async->priv->finished, NULL);
+
+ return async->priv->result;
+}
+
+/* transitions 'async' to the "stopped" state, indicating that the task
+ * was stopped before completion (normally, in response to a
+ * 'gimp_cancelable_cancel()' call).
+ *
+ * 'async' shall be in the "running" state.
+ *
+ * may only be called on the async thread.
+ */
+void
+gimp_async_abort (GimpAsync *async)
+{
+ g_return_if_fail (GIMP_IS_ASYNC (async));
+ g_return_if_fail (! async->priv->stopped);
+
+ g_mutex_lock (&async->priv->mutex);
+
+ gimp_async_stop (async);
+
+ g_mutex_unlock (&async->priv->mutex);
+}
+
+/* checks if cancellation of 'async' has been requested.
+ *
+ * note that a return value of TRUE only indicates that
+ * 'gimp_cancelable_cancel()' has been called for 'async', and not that 'async'
+ * is stopped, or, if it is stopped, that it was aborted.
+ */
+gboolean
+gimp_async_is_canceled (GimpAsync *async)
+{
+ g_return_val_if_fail (GIMP_IS_ASYNC (async), FALSE);
+
+ return async->priv->canceled;
+}
+
+/* a convenience function, canceling 'async' and waiting for it to stop.
+ *
+ * may only be called on the main thread.
+ */
+void
+gimp_async_cancel_and_wait (GimpAsync *async)
+{
+ g_return_if_fail (GIMP_IS_ASYNC (async));
+
+ gimp_cancelable_cancel (GIMP_CANCELABLE (async));
+ gimp_waitable_wait (GIMP_WAITABLE (async));
+}
+
+
+/* public functions (stats) */
+
+
+gint
+gimp_async_get_n_running (void)
+{
+ return gimp_async_n_running;
+}
diff --git a/app/core/gimpasync.h b/app/core/gimpasync.h
new file mode 100644
index 0000000..0c2a671
--- /dev/null
+++ b/app/core/gimpasync.h
@@ -0,0 +1,95 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpasync.h
+ * Copyright (C) 2018 Ell
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_ASYNC_H__
+#define __GIMP_ASYNC_H__
+
+
+#define GIMP_TYPE_ASYNC (gimp_async_get_type ())
+#define GIMP_ASYNC(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_ASYNC, GimpAsync))
+#define GIMP_ASYNC_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_ASYNC, GimpAsyncClass))
+#define GIMP_IS_ASYNC(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_ASYNC))
+#define GIMP_IS_ASYNC_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_ASYNC))
+#define GIMP_ASYNC_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_ASYNC, GimpAsyncClass))
+
+
+typedef void (* GimpAsyncCallback) (GimpAsync *async,
+ gpointer data);
+
+
+typedef struct _GimpAsyncPrivate GimpAsyncPrivate;
+typedef struct _GimpAsyncClass GimpAsyncClass;
+
+struct _GimpAsync
+{
+ GObject parent_instance;
+
+ GimpAsyncPrivate *priv;
+};
+
+struct _GimpAsyncClass
+{
+ GObjectClass parent_class;
+
+ /* signals */
+ void (* waiting) (GimpAsync *async);
+};
+
+
+GType gimp_async_get_type (void) G_GNUC_CONST;
+
+GimpAsync * gimp_async_new (void);
+
+gboolean gimp_async_is_synced (GimpAsync *async);
+
+void gimp_async_add_callback (GimpAsync *async,
+ GimpAsyncCallback callback,
+ gpointer data);
+void gimp_async_add_callback_for_object (GimpAsync *async,
+ GimpAsyncCallback callback,
+ gpointer data,
+ gpointer gobject);
+void gimp_async_remove_callback (GimpAsync *async,
+ GimpAsyncCallback callback,
+ gpointer data);
+
+gboolean gimp_async_is_stopped (GimpAsync *async);
+
+void gimp_async_finish (GimpAsync *async,
+ gpointer result);
+void gimp_async_finish_full (GimpAsync *async,
+ gpointer result,
+ GDestroyNotify result_destroy_func);
+gboolean gimp_async_is_finished (GimpAsync *async);
+gpointer gimp_async_get_result (GimpAsync *async);
+
+void gimp_async_abort (GimpAsync *async);
+
+gboolean gimp_async_is_canceled (GimpAsync *async);
+
+void gimp_async_cancel_and_wait (GimpAsync *async);
+
+
+/* stats */
+
+gint gimp_async_get_n_running (void);
+
+
+#endif /* __GIMP_ASYNC_H__ */
diff --git a/app/core/gimpasyncset.c b/app/core/gimpasyncset.c
new file mode 100644
index 0000000..f1b611e
--- /dev/null
+++ b/app/core/gimpasyncset.c
@@ -0,0 +1,348 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpasyncset.c
+ * Copyright (C) 2018 Ell
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "core-types.h"
+
+#include "gimpasync.h"
+#include "gimpasyncset.h"
+#include "gimpcancelable.h"
+#include "gimpwaitable.h"
+
+
+enum
+{
+ PROP_0,
+ PROP_EMPTY
+};
+
+
+struct _GimpAsyncSetPrivate
+{
+ GHashTable *asyncs;
+};
+
+
+/* local function prototypes */
+
+static void gimp_async_set_waitable_iface_init (GimpWaitableInterface *iface);
+
+static void gimp_async_set_cancelable_iface_init (GimpCancelableInterface *iface);
+
+static void gimp_async_set_dispose (GObject *object);
+static void gimp_async_set_finalize (GObject *object);
+static void gimp_async_set_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_async_set_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static void gimp_async_set_wait (GimpWaitable *waitable);
+static gboolean gimp_async_set_try_wait (GimpWaitable *waitable);
+static gboolean gimp_async_set_wait_until (GimpWaitable *waitable,
+ gint64 end_time);
+
+static void gimp_async_set_cancel (GimpCancelable *cancelable);
+
+static void gimp_async_set_async_callback (GimpAsync *async,
+ GimpAsyncSet *async_set);
+
+
+G_DEFINE_TYPE_WITH_CODE (GimpAsyncSet, gimp_async_set, G_TYPE_OBJECT,
+ G_ADD_PRIVATE (GimpAsyncSet)
+ G_IMPLEMENT_INTERFACE (GIMP_TYPE_WAITABLE,
+ gimp_async_set_waitable_iface_init)
+ G_IMPLEMENT_INTERFACE (GIMP_TYPE_CANCELABLE,
+ gimp_async_set_cancelable_iface_init))
+
+#define parent_class gimp_async_set_parent_class
+
+
+/* private functions */
+
+
+static void
+gimp_async_set_class_init (GimpAsyncSetClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->dispose = gimp_async_set_dispose;
+ object_class->finalize = gimp_async_set_finalize;
+ object_class->set_property = gimp_async_set_set_property;
+ object_class->get_property = gimp_async_set_get_property;
+
+ g_object_class_install_property (object_class, PROP_EMPTY,
+ g_param_spec_boolean ("empty",
+ NULL, NULL,
+ FALSE,
+ GIMP_PARAM_READABLE));
+}
+
+static void
+gimp_async_set_waitable_iface_init (GimpWaitableInterface *iface)
+{
+ iface->wait = gimp_async_set_wait;
+ iface->try_wait = gimp_async_set_try_wait;
+ iface->wait_until = gimp_async_set_wait_until;
+}
+
+static void
+gimp_async_set_cancelable_iface_init (GimpCancelableInterface *iface)
+{
+ iface->cancel = gimp_async_set_cancel;
+}
+
+static void
+gimp_async_set_init (GimpAsyncSet *async_set)
+{
+ async_set->priv = gimp_async_set_get_instance_private (async_set);
+
+ async_set->priv->asyncs = g_hash_table_new (NULL, NULL);
+}
+
+static void
+gimp_async_set_dispose (GObject *object)
+{
+ GimpAsyncSet *async_set = GIMP_ASYNC_SET (object);
+
+ gimp_async_set_clear (async_set);
+
+ G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+gimp_async_set_finalize (GObject *object)
+{
+ GimpAsyncSet *async_set = GIMP_ASYNC_SET (object);
+
+ g_hash_table_unref (async_set->priv->asyncs);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gimp_async_set_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id)
+ {
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_async_set_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpAsyncSet *async_set = GIMP_ASYNC_SET (object);
+
+ switch (property_id)
+ {
+ case PROP_EMPTY:
+ g_value_set_boolean (value, gimp_async_set_is_empty (async_set));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_async_set_wait (GimpWaitable *waitable)
+{
+ GimpAsyncSet *async_set = GIMP_ASYNC_SET (waitable);
+
+ while (! gimp_async_set_is_empty (async_set))
+ {
+ GimpAsync *async;
+ GHashTableIter iter;
+
+ g_hash_table_iter_init (&iter, async_set->priv->asyncs);
+
+ g_hash_table_iter_next (&iter, (gpointer *) &async, NULL);
+
+ gimp_waitable_wait (GIMP_WAITABLE (async));
+ }
+}
+
+static gboolean
+gimp_async_set_try_wait (GimpWaitable *waitable)
+{
+ GimpAsyncSet *async_set = GIMP_ASYNC_SET (waitable);
+
+ while (! gimp_async_set_is_empty (async_set))
+ {
+ GimpAsync *async;
+ GHashTableIter iter;
+
+ g_hash_table_iter_init (&iter, async_set->priv->asyncs);
+
+ g_hash_table_iter_next (&iter, (gpointer *) &async, NULL);
+
+ if (! gimp_waitable_try_wait (GIMP_WAITABLE (async)))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+gimp_async_set_wait_until (GimpWaitable *waitable,
+ gint64 end_time)
+{
+ GimpAsyncSet *async_set = GIMP_ASYNC_SET (waitable);
+
+ while (! gimp_async_set_is_empty (async_set))
+ {
+ GimpAsync *async;
+ GHashTableIter iter;
+
+ g_hash_table_iter_init (&iter, async_set->priv->asyncs);
+
+ g_hash_table_iter_next (&iter, (gpointer *) &async, NULL);
+
+ if (! gimp_waitable_wait_until (GIMP_WAITABLE (async), end_time))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void
+gimp_async_set_cancel (GimpCancelable *cancelable)
+{
+ GimpAsyncSet *async_set = GIMP_ASYNC_SET (cancelable);
+ GList *list;
+
+ list = g_hash_table_get_keys (async_set->priv->asyncs);
+
+ g_list_foreach (list, (GFunc) g_object_ref, NULL);
+
+ g_list_foreach (list, (GFunc) gimp_cancelable_cancel, NULL);
+
+ g_list_free_full (list, g_object_unref);
+}
+
+static void
+gimp_async_set_async_callback (GimpAsync *async,
+ GimpAsyncSet *async_set)
+{
+ g_hash_table_remove (async_set->priv->asyncs, async);
+
+ if (gimp_async_set_is_empty (async_set))
+ g_object_notify (G_OBJECT (async_set), "empty");
+}
+
+
+/* public functions */
+
+
+GimpAsyncSet *
+gimp_async_set_new (void)
+{
+ return g_object_new (GIMP_TYPE_ASYNC_SET,
+ NULL);
+}
+
+void
+gimp_async_set_add (GimpAsyncSet *async_set,
+ GimpAsync *async)
+{
+ g_return_if_fail (GIMP_IS_ASYNC_SET (async_set));
+ g_return_if_fail (GIMP_IS_ASYNC (async));
+
+ if (g_hash_table_add (async_set->priv->asyncs, async))
+ {
+ if (g_hash_table_size (async_set->priv->asyncs) == 1)
+ g_object_notify (G_OBJECT (async_set), "empty");
+
+ gimp_async_add_callback (
+ async,
+ (GimpAsyncCallback) gimp_async_set_async_callback,
+ async_set);
+ }
+}
+
+void
+gimp_async_set_remove (GimpAsyncSet *async_set,
+ GimpAsync *async)
+{
+ g_return_if_fail (GIMP_IS_ASYNC_SET (async_set));
+ g_return_if_fail (GIMP_IS_ASYNC (async));
+
+ if (g_hash_table_remove (async_set->priv->asyncs, async))
+ {
+ gimp_async_remove_callback (
+ async,
+ (GimpAsyncCallback) gimp_async_set_async_callback,
+ async_set);
+
+ if (g_hash_table_size (async_set->priv->asyncs) == 0)
+ g_object_notify (G_OBJECT (async_set), "empty");
+ }
+}
+
+void
+gimp_async_set_clear (GimpAsyncSet *async_set)
+{
+ GimpAsync *async;
+ GHashTableIter iter;
+
+ g_return_if_fail (GIMP_IS_ASYNC_SET (async_set));
+
+ if (gimp_async_set_is_empty (async_set))
+ return;
+
+ g_hash_table_iter_init (&iter, async_set->priv->asyncs);
+
+ while (g_hash_table_iter_next (&iter, (gpointer *) &async, NULL))
+ {
+ gimp_async_remove_callback (
+ async,
+ (GimpAsyncCallback) gimp_async_set_async_callback,
+ async_set);
+ }
+
+ g_hash_table_remove_all (async_set->priv->asyncs);
+
+ g_object_notify (G_OBJECT (async_set), "empty");
+}
+
+gboolean
+gimp_async_set_is_empty (GimpAsyncSet *async_set)
+{
+ g_return_val_if_fail (GIMP_IS_ASYNC_SET (async_set), FALSE);
+
+ return g_hash_table_size (async_set->priv->asyncs) == 0;
+}
diff --git a/app/core/gimpasyncset.h b/app/core/gimpasyncset.h
new file mode 100644
index 0000000..62cc524
--- /dev/null
+++ b/app/core/gimpasyncset.h
@@ -0,0 +1,61 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpasyncset.h
+ * Copyright (C) 2018 Ell
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_ASYNC_SET_H__
+#define __GIMP_ASYNC_SET_H__
+
+
+#define GIMP_TYPE_ASYNC_SET (gimp_async_set_get_type ())
+#define GIMP_ASYNC_SET(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_ASYNC_SET, GimpAsyncSet))
+#define GIMP_ASYNC_SET_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_ASYNC_SET, GimpAsyncSetClass))
+#define GIMP_IS_ASYNC_SET(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_ASYNC_SET))
+#define GIMP_IS_ASYNC_SET_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_ASYNC_SET))
+#define GIMP_ASYNC_SET_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_ASYNC_SET, GimpAsyncSetClass))
+
+
+typedef struct _GimpAsyncSetPrivate GimpAsyncSetPrivate;
+typedef struct _GimpAsyncSetClass GimpAsyncSetClass;
+
+struct _GimpAsyncSet
+{
+ GObject parent_instance;
+
+ GimpAsyncSetPrivate *priv;
+};
+
+struct _GimpAsyncSetClass
+{
+ GObjectClass parent_class;
+};
+
+
+GType gimp_async_set_get_type (void) G_GNUC_CONST;
+
+GimpAsyncSet * gimp_async_set_new (void);
+
+void gimp_async_set_add (GimpAsyncSet *async_set,
+ GimpAsync *async);
+void gimp_async_set_remove (GimpAsyncSet *async_set,
+ GimpAsync *async);
+void gimp_async_set_clear (GimpAsyncSet *async_set);
+gboolean gimp_async_set_is_empty (GimpAsyncSet *async_set);
+
+
+#endif /* __GIMP_ASYNC_SET_H__ */
diff --git a/app/core/gimpauxitem.c b/app/core/gimpauxitem.c
new file mode 100644
index 0000000..cc96149
--- /dev/null
+++ b/app/core/gimpauxitem.c
@@ -0,0 +1,147 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <glib-object.h>
+
+#include "core-types.h"
+
+#include "gimpauxitem.h"
+
+
+enum
+{
+ REMOVED,
+ LAST_SIGNAL
+};
+
+enum
+{
+ PROP_0,
+ PROP_ID
+};
+
+
+struct _GimpAuxItemPrivate
+{
+ guint32 aux_item_ID;
+};
+
+
+static void gimp_aux_item_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+static void gimp_aux_item_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+
+
+G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (GimpAuxItem, gimp_aux_item, G_TYPE_OBJECT)
+
+static guint gimp_aux_item_signals[LAST_SIGNAL] = { 0 };
+
+
+static void
+gimp_aux_item_class_init (GimpAuxItemClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ gimp_aux_item_signals[REMOVED] =
+ g_signal_new ("removed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpAuxItemClass, removed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ object_class->get_property = gimp_aux_item_get_property;
+ object_class->set_property = gimp_aux_item_set_property;
+
+ klass->removed = NULL;
+
+ g_object_class_install_property (object_class, PROP_ID,
+ g_param_spec_uint ("id", NULL, NULL,
+ 0, G_MAXUINT32, 0,
+ G_PARAM_CONSTRUCT_ONLY |
+ GIMP_PARAM_READWRITE));
+}
+
+static void
+gimp_aux_item_init (GimpAuxItem *aux_item)
+{
+ aux_item->priv = gimp_aux_item_get_instance_private (aux_item);
+}
+
+static void
+gimp_aux_item_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpAuxItem *aux_item = GIMP_AUX_ITEM (object);
+
+ switch (property_id)
+ {
+ case PROP_ID:
+ g_value_set_uint (value, aux_item->priv->aux_item_ID);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_aux_item_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpAuxItem *aux_item = GIMP_AUX_ITEM (object);
+
+ switch (property_id)
+ {
+ case PROP_ID:
+ aux_item->priv->aux_item_ID = g_value_get_uint (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+guint32
+gimp_aux_item_get_ID (GimpAuxItem *aux_item)
+{
+ g_return_val_if_fail (GIMP_IS_AUX_ITEM (aux_item), 0);
+
+ return aux_item->priv->aux_item_ID;
+}
+
+void
+gimp_aux_item_removed (GimpAuxItem *aux_item)
+{
+ g_return_if_fail (GIMP_IS_AUX_ITEM (aux_item));
+
+ g_signal_emit (aux_item, gimp_aux_item_signals[REMOVED], 0);
+}
diff --git a/app/core/gimpauxitem.h b/app/core/gimpauxitem.h
new file mode 100644
index 0000000..6907b39
--- /dev/null
+++ b/app/core/gimpauxitem.h
@@ -0,0 +1,56 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_AUX_ITEM_H__
+#define __GIMP_AUX_ITEM_H__
+
+
+#define GIMP_TYPE_AUX_ITEM (gimp_aux_item_get_type ())
+#define GIMP_AUX_ITEM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_AUX_ITEM, GimpAuxItem))
+#define GIMP_AUX_ITEM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_AUX_ITEM, GimpAuxItemClass))
+#define GIMP_IS_AUX_ITEM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_AUX_ITEM))
+#define GIMP_IS_AUX_ITEM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_AUX_ITEM))
+#define GIMP_AUX_ITEM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_AUX_ITEM, GimpAuxItemClass))
+
+
+typedef struct _GimpAuxItemPrivate GimpAuxItemPrivate;
+typedef struct _GimpAuxItemClass GimpAuxItemClass;
+
+struct _GimpAuxItem
+{
+ GObject parent_instance;
+
+ GimpAuxItemPrivate *priv;
+};
+
+struct _GimpAuxItemClass
+{
+ GObjectClass parent_class;
+
+ /* signals */
+ void (* removed) (GimpAuxItem *aux_item);
+};
+
+
+GType gimp_aux_item_get_type (void) G_GNUC_CONST;
+
+guint32 gimp_aux_item_get_ID (GimpAuxItem *aux_item);
+
+void gimp_aux_item_removed (GimpAuxItem *aux_item);
+
+
+#endif /* __GIMP_AUX_ITEM_H__ */
diff --git a/app/core/gimpauxitemundo.c b/app/core/gimpauxitemundo.c
new file mode 100644
index 0000000..e0253ab
--- /dev/null
+++ b/app/core/gimpauxitemundo.c
@@ -0,0 +1,138 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "core-types.h"
+
+#include "gimpauxitem.h"
+#include "gimpauxitemundo.h"
+
+
+enum
+{
+ PROP_0,
+ PROP_AUX_ITEM
+};
+
+
+static void gimp_aux_item_undo_constructed (GObject *object);
+static void gimp_aux_item_undo_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_aux_item_undo_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static void gimp_aux_item_undo_free (GimpUndo *undo,
+ GimpUndoMode undo_mode);
+
+
+G_DEFINE_ABSTRACT_TYPE (GimpAuxItemUndo, gimp_aux_item_undo, GIMP_TYPE_UNDO)
+
+#define parent_class gimp_aux_item_undo_parent_class
+
+
+static void
+gimp_aux_item_undo_class_init (GimpAuxItemUndoClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpUndoClass *undo_class = GIMP_UNDO_CLASS (klass);
+
+ object_class->constructed = gimp_aux_item_undo_constructed;
+ object_class->set_property = gimp_aux_item_undo_set_property;
+ object_class->get_property = gimp_aux_item_undo_get_property;
+
+ undo_class->free = gimp_aux_item_undo_free;
+
+ g_object_class_install_property (object_class, PROP_AUX_ITEM,
+ g_param_spec_object ("aux-item", NULL, NULL,
+ GIMP_TYPE_AUX_ITEM,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+}
+
+static void
+gimp_aux_item_undo_init (GimpAuxItemUndo *undo)
+{
+}
+
+static void
+gimp_aux_item_undo_constructed (GObject *object)
+{
+ GimpAuxItemUndo *aux_item_undo = GIMP_AUX_ITEM_UNDO (object);
+
+ G_OBJECT_CLASS (parent_class)->constructed (object);
+
+ gimp_assert (GIMP_IS_AUX_ITEM (aux_item_undo->aux_item));
+}
+
+static void
+gimp_aux_item_undo_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpAuxItemUndo *aux_item_undo = GIMP_AUX_ITEM_UNDO (object);
+
+ switch (property_id)
+ {
+ case PROP_AUX_ITEM:
+ aux_item_undo->aux_item = g_value_dup_object (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_aux_item_undo_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpAuxItemUndo *aux_item_undo = GIMP_AUX_ITEM_UNDO (object);
+
+ switch (property_id)
+ {
+ case PROP_AUX_ITEM:
+ g_value_set_object (value, aux_item_undo->aux_item);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_aux_item_undo_free (GimpUndo *undo,
+ GimpUndoMode undo_mode)
+{
+ GimpAuxItemUndo *aux_item_undo = GIMP_AUX_ITEM_UNDO (undo);
+
+ g_clear_object (&aux_item_undo->aux_item);
+
+ GIMP_UNDO_CLASS (parent_class)->free (undo, undo_mode);
+}
diff --git a/app/core/gimpauxitemundo.h b/app/core/gimpauxitemundo.h
new file mode 100644
index 0000000..2858f4d
--- /dev/null
+++ b/app/core/gimpauxitemundo.h
@@ -0,0 +1,52 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_AUX_ITEM_UNDO_H__
+#define __GIMP_AUX_ITEM_UNDO_H__
+
+
+#include "gimpundo.h"
+
+
+#define GIMP_TYPE_AUX_ITEM_UNDO (gimp_aux_item_undo_get_type ())
+#define GIMP_AUX_ITEM_UNDO(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_AUX_ITEM_UNDO, GimpAuxItemUndo))
+#define GIMP_AUX_ITEM_UNDO_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_AUX_ITEM_UNDO, GimpAuxItemUndoClass))
+#define GIMP_IS_AUX_ITEM_UNDO(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_AUX_ITEM_UNDO))
+#define GIMP_IS_AUX_ITEM_UNDO_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_AUX_ITEM_UNDO))
+#define GIMP_AUX_ITEM_UNDO_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_AUX_ITEM_UNDO, GimpAuxItemUndoClass))
+
+
+typedef struct _GimpAuxItemUndo GimpAuxItemUndo;
+typedef struct _GimpAuxItemUndoClass GimpAuxItemUndoClass;
+
+struct _GimpAuxItemUndo
+{
+ GimpUndo parent_instance;
+
+ GimpAuxItem *aux_item;
+};
+
+struct _GimpAuxItemUndoClass
+{
+ GimpUndoClass parent_class;
+};
+
+
+GType gimp_aux_item_undo_get_type (void) G_GNUC_CONST;
+
+
+#endif /* __GIMP_AUX_ITEM_UNDO_H__ */
diff --git a/app/core/gimpbacktrace-backend.h b/app/core/gimpbacktrace-backend.h
new file mode 100644
index 0000000..4bfb1c9
--- /dev/null
+++ b/app/core/gimpbacktrace-backend.h
@@ -0,0 +1,34 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpbacktrace-backend.h
+ * Copyright (C) 2018 Ell
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_BACKTRACE_BACKEND_H__
+#define __GIMP_BACKTRACE_BACKEND_H__
+
+
+#ifdef __gnu_linux__
+# define GIMP_BACKTRACE_BACKEND_LINUX
+#elif defined (G_OS_WIN32) && defined (ARCH_X86)
+# define GIMP_BACKTRACE_BACKEND_WINDOWS
+#else
+# define GIMP_BACKTRACE_BACKEND_NONE
+#endif
+
+
+#endif /* __GIMP_BACKTRACE_BACKEND_H__ */
diff --git a/app/core/gimpbacktrace-linux.c b/app/core/gimpbacktrace-linux.c
new file mode 100644
index 0000000..a8593f8
--- /dev/null
+++ b/app/core/gimpbacktrace-linux.c
@@ -0,0 +1,725 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-1999 Spencer Kimball and Peter Mattis
+ *
+ * gimpbacktrace-linux.c
+ * Copyright (C) 2018 Ell
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+
+#define _GNU_SOURCE
+
+
+#include "config.h"
+
+#include <gio/gio.h>
+
+#include "gimpbacktrace-backend.h"
+
+
+#ifdef GIMP_BACKTRACE_BACKEND_LINUX
+
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/syscall.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <signal.h>
+#include <execinfo.h>
+#include <dlfcn.h>
+#include <string.h>
+#include <stdio.h>
+
+#ifdef HAVE_LIBBACKTRACE
+#include <backtrace.h>
+#endif
+
+#ifdef HAVE_LIBUNWIND
+#define UNW_LOCAL_ONLY
+#include <libunwind.h>
+#endif
+
+#include "core-types.h"
+
+#include "gimpbacktrace.h"
+
+
+#define MAX_N_THREADS 256
+#define MAX_N_FRAMES 256
+#define MAX_THREAD_NAME_SIZE 32
+#define N_SKIPPED_FRAMES 2
+#define MAX_WAIT_TIME (G_TIME_SPAN_SECOND / 20)
+#define BACKTRACE_SIGNAL SIGUSR1
+
+
+typedef struct _GimpBacktraceThread GimpBacktraceThread;
+
+
+struct _GimpBacktraceThread
+{
+ pid_t tid;
+ gchar name[MAX_THREAD_NAME_SIZE];
+ gchar state;
+
+ guintptr frames[MAX_N_FRAMES];
+ gint n_frames;
+};
+
+struct _GimpBacktrace
+{
+ GimpBacktraceThread *threads;
+ gint n_threads;
+};
+
+
+/* local function prototypes */
+
+static inline gint gimp_backtrace_normalize_frame (GimpBacktrace *backtrace,
+ gint thread,
+ gint frame);
+
+static gint gimp_backtrace_enumerate_threads (gboolean include_current_thread,
+ pid_t *threads,
+ gint size);
+static void gimp_backtrace_read_thread_name (pid_t tid,
+ gchar *name,
+ gint size);
+static gchar gimp_backtrace_read_thread_state (pid_t tid);
+
+static void gimp_backtrace_signal_handler (gint signum);
+
+
+/* static variables */
+
+static GMutex mutex;
+static gint n_initializations;
+static gboolean initialized;
+static struct sigaction orig_action;
+static pid_t blacklisted_threads[MAX_N_THREADS];
+static gint n_blacklisted_threads;
+static GimpBacktrace *handler_backtrace;
+static gint handler_n_remaining_threads;
+static gint handler_lock;
+
+#ifdef HAVE_LIBBACKTRACE
+static struct backtrace_state *backtrace_state;
+#endif
+
+static const gchar * const blacklisted_thread_names[] =
+{
+ "gmain",
+ "threaded-ml"
+};
+
+
+/* private functions */
+
+
+static inline gint
+gimp_backtrace_normalize_frame (GimpBacktrace *backtrace,
+ gint thread,
+ gint frame)
+{
+ if (frame >= 0)
+ return frame + N_SKIPPED_FRAMES;
+ else
+ return backtrace->threads[thread].n_frames + frame;
+}
+
+static gint
+gimp_backtrace_enumerate_threads (gboolean include_current_thread,
+ pid_t *threads,
+ gint size)
+{
+ DIR *dir;
+ struct dirent *dirent;
+ pid_t tid;
+ gint n_threads;
+
+ dir = opendir ("/proc/self/task");
+
+ if (! dir)
+ return 0;
+
+ tid = syscall (SYS_gettid);
+
+ n_threads = 0;
+
+ while (n_threads < size && (dirent = readdir (dir)))
+ {
+ pid_t id = g_ascii_strtoull (dirent->d_name, NULL, 10);
+
+ if (id)
+ {
+ if (! include_current_thread && id == tid)
+ id = 0;
+ }
+
+ if (id)
+ {
+ gint i;
+
+ for (i = 0; i < n_blacklisted_threads; i++)
+ {
+ if (id == blacklisted_threads[i])
+ {
+ id = 0;
+
+ break;
+ }
+ }
+ }
+
+ if (id)
+ threads[n_threads++] = id;
+ }
+
+ closedir (dir);
+
+ return n_threads;
+}
+
+static void
+gimp_backtrace_read_thread_name (pid_t tid,
+ gchar *name,
+ gint size)
+{
+ gchar filename[64];
+ gint fd;
+
+ if (size <= 0)
+ return;
+
+ name[0] = '\0';
+
+ g_snprintf (filename, sizeof (filename),
+ "/proc/self/task/%llu/comm",
+ (unsigned long long) tid);
+
+ fd = open (filename, O_RDONLY);
+
+ if (fd >= 0)
+ {
+ gint n = read (fd, name, size);
+
+ if (n > 0)
+ name[n - 1] = '\0';
+
+ close (fd);
+ }
+}
+
+static gchar
+gimp_backtrace_read_thread_state (pid_t tid)
+{
+ gchar buffer[64];
+ gint fd;
+ gchar state = '\0';
+
+ g_snprintf (buffer, sizeof (buffer),
+ "/proc/self/task/%llu/stat",
+ (unsigned long long) tid);
+
+ fd = open (buffer, O_RDONLY);
+
+ if (fd >= 0)
+ {
+ gint n = read (fd, buffer, sizeof (buffer));
+
+ if (n > 0)
+ buffer[n - 1] = '\0';
+
+ sscanf (buffer, "%*d %*s %c", &state);
+
+ close (fd);
+ }
+
+ return state;
+}
+
+static void
+gimp_backtrace_signal_handler (gint signum)
+{
+ GimpBacktrace *curr_backtrace;
+ gint lock;
+
+ do
+ {
+ lock = g_atomic_int_get (&handler_lock);
+
+ if (lock < 0)
+ continue;
+ }
+ while (! g_atomic_int_compare_and_exchange (&handler_lock, lock, lock + 1));
+
+ curr_backtrace = g_atomic_pointer_get (&handler_backtrace);
+
+ if (curr_backtrace)
+ {
+ pid_t tid = syscall (SYS_gettid);
+ gint i;
+
+ for (i = 0; i < curr_backtrace->n_threads; i++)
+ {
+ GimpBacktraceThread *thread = &curr_backtrace->threads[i];
+
+ if (thread->tid == tid)
+ {
+ thread->n_frames = backtrace ((gpointer *) thread->frames,
+ MAX_N_FRAMES);
+
+ g_atomic_int_dec_and_test (&handler_n_remaining_threads);
+
+ break;
+ }
+ }
+ }
+
+ g_atomic_int_dec_and_test (&handler_lock);
+}
+
+
+/* public functions */
+
+
+void
+gimp_backtrace_init (void)
+{
+#ifdef HAVE_LIBBACKTRACE
+ backtrace_state = backtrace_create_state (NULL, 0, NULL, NULL);
+#endif
+}
+
+gboolean
+gimp_backtrace_start (void)
+{
+ g_mutex_lock (&mutex);
+
+ if (n_initializations == 0)
+ {
+ struct sigaction action = {};
+
+ action.sa_handler = gimp_backtrace_signal_handler;
+ action.sa_flags = SA_RESTART;
+
+ sigemptyset (&action.sa_mask);
+
+ if (sigaction (BACKTRACE_SIGNAL, &action, &orig_action) == 0)
+ {
+ pid_t *threads;
+ gint n_threads;
+ gint i;
+
+ n_blacklisted_threads = 0;
+
+ threads = g_new (pid_t, MAX_N_THREADS);
+
+ n_threads = gimp_backtrace_enumerate_threads (TRUE,
+ threads, MAX_N_THREADS);
+
+ for (i = 0; i < n_threads; i++)
+ {
+ gchar name[MAX_THREAD_NAME_SIZE];
+ gint j;
+
+ gimp_backtrace_read_thread_name (threads[i],
+ name, MAX_THREAD_NAME_SIZE);
+
+ for (j = 0; j < G_N_ELEMENTS (blacklisted_thread_names); j++)
+ {
+ if (! strcmp (name, blacklisted_thread_names[j]))
+ {
+ blacklisted_threads[n_blacklisted_threads++] = threads[i];
+ }
+ }
+ }
+
+ g_free (threads);
+
+ initialized = TRUE;
+ }
+ }
+
+ n_initializations++;
+
+ g_mutex_unlock (&mutex);
+
+ return initialized;
+}
+
+void
+gimp_backtrace_stop (void)
+{
+ g_return_if_fail (n_initializations > 0);
+
+ g_mutex_lock (&mutex);
+
+ n_initializations--;
+
+ if (n_initializations == 0 && initialized)
+ {
+ if (sigaction (BACKTRACE_SIGNAL, &orig_action, NULL) < 0)
+ g_warning ("failed to restore origianl backtrace signal handler");
+
+ initialized = FALSE;
+ }
+
+ g_mutex_unlock (&mutex);
+}
+
+GimpBacktrace *
+gimp_backtrace_new (gboolean include_current_thread)
+{
+ GimpBacktrace *backtrace;
+ pid_t pid;
+ pid_t *threads;
+ gint n_threads;
+ gint64 start_time;
+ gint i;
+
+ if (! initialized)
+ return NULL;
+
+ pid = getpid ();
+
+ threads = g_new (pid_t, MAX_N_THREADS);
+
+ n_threads = gimp_backtrace_enumerate_threads (include_current_thread,
+ threads, MAX_N_THREADS);
+
+ if (n_threads == 0)
+ {
+ g_free (threads);
+
+ return NULL;
+ }
+
+ g_mutex_lock (&mutex);
+
+ backtrace = g_slice_new (GimpBacktrace);
+
+ backtrace->threads = g_new (GimpBacktraceThread, n_threads);
+ backtrace->n_threads = n_threads;
+
+ while (! g_atomic_int_compare_and_exchange (&handler_lock, 0, -1));
+
+ g_atomic_pointer_set (&handler_backtrace, backtrace);
+ g_atomic_int_set (&handler_n_remaining_threads, n_threads);
+
+ g_atomic_int_set (&handler_lock, 0);
+
+ for (i = 0; i < n_threads; i++)
+ {
+ GimpBacktraceThread *thread = &backtrace->threads[i];
+
+ thread->tid = threads[i];
+ thread->n_frames = 0;
+
+ gimp_backtrace_read_thread_name (thread->tid,
+ thread->name, MAX_THREAD_NAME_SIZE);
+
+ thread->state = gimp_backtrace_read_thread_state (thread->tid);
+
+ syscall (SYS_tgkill, pid, threads[i], BACKTRACE_SIGNAL);
+ }
+
+ g_free (threads);
+
+ start_time = g_get_monotonic_time ();
+
+ while (g_atomic_int_get (&handler_n_remaining_threads) > 0)
+ {
+ gint64 time = g_get_monotonic_time ();
+
+ if (time - start_time > MAX_WAIT_TIME)
+ break;
+
+ g_usleep (1000);
+ }
+
+ while (! g_atomic_int_compare_and_exchange (&handler_lock, 0, -1));
+
+ g_atomic_pointer_set (&handler_backtrace, NULL);
+
+ g_atomic_int_set (&handler_lock, 0);
+
+#if 0
+ if (handler_n_remaining_threads > 0)
+ {
+ gint j = 0;
+
+ for (i = 0; i < n_threads; i++)
+ {
+ if (backtrace->threads[i].n_frames == 0)
+ {
+ if (n_blacklisted_threads < MAX_N_THREADS)
+ {
+ blacklisted_threads[n_blacklisted_threads++] =
+ backtrace->threads[i].tid;
+ }
+ }
+ else
+ {
+ if (j < i)
+ backtrace->threads[j] = backtrace->threads[i];
+
+ j++;
+ }
+ }
+
+ n_threads = j;
+ }
+#endif
+
+ g_mutex_unlock (&mutex);
+
+ if (n_threads == 0)
+ {
+ gimp_backtrace_free (backtrace);
+
+ return NULL;
+ }
+
+ return backtrace;
+}
+
+void
+gimp_backtrace_free (GimpBacktrace *backtrace)
+{
+ if (! backtrace)
+ return;
+
+ g_free (backtrace->threads);
+
+ g_slice_free (GimpBacktrace, backtrace);
+}
+
+gint
+gimp_backtrace_get_n_threads (GimpBacktrace *backtrace)
+{
+ g_return_val_if_fail (backtrace != NULL, 0);
+
+ return backtrace->n_threads;
+}
+
+guintptr
+gimp_backtrace_get_thread_id (GimpBacktrace *backtrace,
+ gint thread)
+{
+ g_return_val_if_fail (backtrace != NULL, 0);
+ g_return_val_if_fail (thread >= 0 && thread < backtrace->n_threads, 0);
+
+ return backtrace->threads[thread].tid;
+}
+
+const gchar *
+gimp_backtrace_get_thread_name (GimpBacktrace *backtrace,
+ gint thread)
+{
+ g_return_val_if_fail (backtrace != NULL, NULL);
+ g_return_val_if_fail (thread >= 0 && thread < backtrace->n_threads, NULL);
+
+ if (backtrace->threads[thread].name[0])
+ return backtrace->threads[thread].name;
+ else
+ return NULL;
+}
+
+gboolean
+gimp_backtrace_is_thread_running (GimpBacktrace *backtrace,
+ gint thread)
+{
+ g_return_val_if_fail (backtrace != NULL, FALSE);
+ g_return_val_if_fail (thread >= 0 && thread < backtrace->n_threads, FALSE);
+
+ return backtrace->threads[thread].state == 'R';
+}
+
+gint
+gimp_backtrace_find_thread_by_id (GimpBacktrace *backtrace,
+ guintptr thread_id,
+ gint thread_hint)
+{
+ pid_t tid = thread_id;
+ gint i;
+
+ g_return_val_if_fail (backtrace != NULL, -1);
+
+ if (thread_hint >= 0 &&
+ thread_hint < backtrace->n_threads &&
+ backtrace->threads[thread_hint].tid == tid)
+ {
+ return thread_hint;
+ }
+
+ for (i = 0; i < backtrace->n_threads; i++)
+ {
+ if (backtrace->threads[i].tid == tid)
+ return i;
+ }
+
+ return -1;
+}
+
+gint
+gimp_backtrace_get_n_frames (GimpBacktrace *backtrace,
+ gint thread)
+{
+ g_return_val_if_fail (backtrace != NULL, 0);
+ g_return_val_if_fail (thread >= 0 && thread < backtrace->n_threads, 0);
+
+ return MAX (backtrace->threads[thread].n_frames - N_SKIPPED_FRAMES, 0);
+}
+
+guintptr
+gimp_backtrace_get_frame_address (GimpBacktrace *backtrace,
+ gint thread,
+ gint frame)
+{
+ g_return_val_if_fail (backtrace != NULL, 0);
+ g_return_val_if_fail (thread >= 0 && thread < backtrace->n_threads, 0);
+
+ frame = gimp_backtrace_normalize_frame (backtrace, thread, frame);
+
+ g_return_val_if_fail (frame >= N_SKIPPED_FRAMES &&
+ frame < backtrace->threads[thread].n_frames, 0);
+
+ return backtrace->threads[thread].frames[frame];
+}
+
+#ifdef HAVE_LIBBACKTRACE
+static void
+gimp_backtrace_syminfo_callback (GimpBacktraceAddressInfo *info,
+ guintptr pc,
+ const gchar *symname,
+ guintptr symval,
+ guintptr symsize)
+{
+ if (symname)
+ g_strlcpy (info->symbol_name, symname, sizeof (info->symbol_name));
+
+ info->symbol_address = symval;
+}
+
+static gint
+gimp_backtrace_pcinfo_callback (GimpBacktraceAddressInfo *info,
+ guintptr pc,
+ const gchar *filename,
+ gint lineno,
+ const gchar *function)
+{
+ if (function)
+ g_strlcpy (info->symbol_name, function, sizeof (info->symbol_name));
+
+ if (filename)
+ g_strlcpy (info->source_file, filename, sizeof (info->source_file));
+
+ info->source_line = lineno;
+
+ return 0;
+}
+#endif /* HAVE_LIBBACKTRACE */
+
+gboolean
+gimp_backtrace_get_address_info (guintptr address,
+ GimpBacktraceAddressInfo *info)
+{
+ Dl_info dl_info;
+ gboolean result = FALSE;
+
+ g_return_val_if_fail (info != NULL, FALSE);
+
+ info->object_name[0] = '\0';
+
+ info->symbol_name[0] = '\0';
+ info->symbol_address = 0;
+
+ info->source_file[0] = '\0';
+ info->source_line = 0;
+
+ if (dladdr ((gpointer) address, &dl_info))
+ {
+ if (dl_info.dli_fname)
+ {
+ g_strlcpy (info->object_name, dl_info.dli_fname,
+ sizeof (info->object_name));
+ }
+
+ if (dl_info.dli_sname)
+ {
+ g_strlcpy (info->symbol_name, dl_info.dli_sname,
+ sizeof (info->symbol_name));
+ }
+
+ info->symbol_address = (guintptr) dl_info.dli_saddr;
+
+ result = TRUE;
+ }
+
+#ifdef HAVE_LIBBACKTRACE
+ if (backtrace_state)
+ {
+ backtrace_syminfo (
+ backtrace_state, address,
+ (backtrace_syminfo_callback) gimp_backtrace_syminfo_callback,
+ NULL,
+ info);
+
+ backtrace_pcinfo (
+ backtrace_state, address,
+ (backtrace_full_callback) gimp_backtrace_pcinfo_callback,
+ NULL,
+ info);
+
+ result = TRUE;
+ }
+#endif /* HAVE_LIBBACKTRACE */
+
+#ifdef HAVE_LIBUNWIND
+/* we use libunwind to get the symbol name, when available, even if dladdr() or
+ * libbacktrace already found one, since it provides more descriptive names in
+ * some cases, and, in particular, full symbol names for C++ lambdas.
+ *
+ * note that, in some cases, this can result in a discrepancy between the
+ * symbol name, and the corresponding source location.
+ */
+#if 0
+ if (! info->symbol_name[0])
+#endif
+ {
+ unw_context_t context = {};
+ unw_cursor_t cursor;
+ unw_word_t offset;
+
+ if (unw_init_local (&cursor, &context) == 0 &&
+ unw_set_reg (&cursor, UNW_REG_IP, address) == 0 &&
+ unw_get_proc_name (&cursor,
+ info->symbol_name, sizeof (info->symbol_name),
+ &offset) == 0)
+ {
+ info->symbol_address = address - offset;
+
+ result = TRUE;
+ }
+ }
+#endif /* HAVE_LIBUNWIND */
+
+ return result;
+}
+
+
+#endif /* GIMP_BACKTRACE_BACKEND_LINUX */
diff --git a/app/core/gimpbacktrace-none.c b/app/core/gimpbacktrace-none.c
new file mode 100644
index 0000000..087e8cb
--- /dev/null
+++ b/app/core/gimpbacktrace-none.c
@@ -0,0 +1,126 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-1999 Spencer Kimball and Peter Mattis
+ *
+ * gimpbacktrace-none.c
+ * Copyright (C) 2018 Ell
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+
+#include "config.h"
+
+#include <gio/gio.h>
+
+#include "gimpbacktrace-backend.h"
+
+
+#ifdef GIMP_BACKTRACE_BACKEND_NONE
+
+
+#include "core-types.h"
+
+#include "gimpbacktrace.h"
+
+
+/* public functions */
+
+
+void
+gimp_backtrace_init (void)
+{
+}
+
+gboolean
+gimp_backtrace_start (void)
+{
+ return FALSE;
+}
+
+void
+gimp_backtrace_stop (void)
+{
+}
+
+GimpBacktrace *
+gimp_backtrace_new (gboolean include_current_thread)
+{
+ return NULL;
+}
+
+void
+gimp_backtrace_free (GimpBacktrace *backtrace)
+{
+ g_return_if_fail (backtrace == NULL);
+}
+
+gint
+gimp_backtrace_get_n_threads (GimpBacktrace *backtrace)
+{
+ g_return_val_if_reached (0);
+}
+
+guintptr
+gimp_backtrace_get_thread_id (GimpBacktrace *backtrace,
+ gint thread)
+{
+ g_return_val_if_reached (0);
+}
+
+const gchar *
+gimp_backtrace_get_thread_name (GimpBacktrace *backtrace,
+ gint thread)
+{
+ g_return_val_if_reached (NULL);
+}
+
+gboolean
+gimp_backtrace_is_thread_running (GimpBacktrace *backtrace,
+ gint thread)
+{
+ g_return_val_if_reached (FALSE);
+}
+
+gint
+gimp_backtrace_find_thread_by_id (GimpBacktrace *backtrace,
+ guintptr thread_id,
+ gint thread_hint)
+{
+ g_return_val_if_reached (-1);
+}
+
+gint
+gimp_backtrace_get_n_frames (GimpBacktrace *backtrace,
+ gint thread)
+{
+ g_return_val_if_reached (0);
+}
+
+guintptr
+gimp_backtrace_get_frame_address (GimpBacktrace *backtrace,
+ gint thread,
+ gint frame)
+{
+ g_return_val_if_reached (0);
+}
+
+gboolean
+gimp_backtrace_get_address_info (guintptr address,
+ GimpBacktraceAddressInfo *info)
+{
+ return FALSE;
+}
+
+
+#endif /* GIMP_BACKTRACE_BACKEND_NONE */
diff --git a/app/core/gimpbacktrace-windows.c b/app/core/gimpbacktrace-windows.c
new file mode 100644
index 0000000..da1fb73
--- /dev/null
+++ b/app/core/gimpbacktrace-windows.c
@@ -0,0 +1,706 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-1999 Spencer Kimball and Peter Mattis
+ *
+ * gimpbacktrace-windows.c
+ * Copyright (C) 2018 Ell
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+
+#include "config.h"
+
+#include <gio/gio.h>
+
+#include "gimpbacktrace-backend.h"
+
+
+#ifdef GIMP_BACKTRACE_BACKEND_WINDOWS
+
+
+#include <windows.h>
+#include <psapi.h>
+#include <tlhelp32.h>
+#include <dbghelp.h>
+
+#include <string.h>
+
+#include "core-types.h"
+
+#include "gimpbacktrace.h"
+
+
+#define MAX_N_THREADS 256
+#define MAX_N_FRAMES 256
+#define THREAD_ENUMERATION_INTERVAL G_TIME_SPAN_SECOND
+
+
+typedef struct _Thread Thread;
+typedef struct _GimpBacktraceThread GimpBacktraceThread;
+
+
+struct _Thread
+{
+ DWORD tid;
+
+ union
+ {
+ gchar *name;
+ guint64 time;
+ };
+};
+
+struct _GimpBacktraceThread
+{
+ DWORD tid;
+ const gchar *name;
+ guint64 time;
+ guint64 last_time;
+
+ guintptr frames[MAX_N_FRAMES];
+ gint n_frames;
+};
+
+struct _GimpBacktrace
+{
+ GimpBacktraceThread *threads;
+ gint n_threads;
+};
+
+
+/* local function prototypes */
+
+static inline gint gimp_backtrace_normalize_frame (GimpBacktrace *backtrace,
+ gint thread,
+ gint frame);
+
+static void gimp_backtrace_set_thread_name (DWORD tid,
+ const gchar *name);
+
+static gboolean gimp_backtrace_enumerate_threads (void);
+
+static LONG WINAPI gimp_backtrace_exception_handler (PEXCEPTION_POINTERS info);
+
+
+/* static variables */
+
+static GMutex mutex;
+static gint n_initializations;
+static gboolean initialized;
+Thread threads[MAX_N_THREADS];
+gint n_threads;
+gint64 last_thread_enumeration_time;
+Thread thread_names[MAX_N_THREADS];
+gint n_thread_names;
+gint thread_names_spinlock;
+Thread thread_times[MAX_N_THREADS];
+gint n_thread_times;
+
+DWORD WINAPI (* gimp_backtrace_SymSetOptions) (DWORD SymOptions);
+BOOL WINAPI (* gimp_backtrace_SymInitialize) (HANDLE hProcess,
+ PCSTR UserSearchPath,
+ BOOL fInvadeProcess);
+BOOL WINAPI (* gimp_backtrace_SymCleanup) (HANDLE hProcess);
+BOOL WINAPI (* gimp_backtrace_SymFromAddr) (HANDLE hProcess,
+ DWORD64 Address,
+ PDWORD64 Displacement,
+ PSYMBOL_INFO Symbol);
+BOOL WINAPI (* gimp_backtrace_SymGetLineFromAddr64) (HANDLE hProcess,
+ DWORD64 qwAddr,
+ PDWORD pdwDisplacement,
+ PIMAGEHLP_LINE64 Line64);
+
+
+/* private functions */
+
+
+static inline gint
+gimp_backtrace_normalize_frame (GimpBacktrace *backtrace,
+ gint thread,
+ gint frame)
+{
+ if (frame >= 0)
+ return frame;
+ else
+ return backtrace->threads[thread].n_frames + frame;
+}
+
+static void
+gimp_backtrace_set_thread_name (DWORD tid,
+ const gchar *name)
+{
+ while (! g_atomic_int_compare_and_exchange (&thread_names_spinlock,
+ 0, 1));
+
+ if (n_thread_names < MAX_N_THREADS)
+ {
+ Thread *thread = &thread_names[n_thread_names++];
+
+ thread->tid = tid;
+ thread->name = g_strdup (name);
+ }
+
+ g_atomic_int_set (&thread_names_spinlock, 0);
+}
+
+static gboolean
+gimp_backtrace_enumerate_threads (void)
+{
+ HANDLE hThreadSnap;
+ THREADENTRY32 te32;
+ DWORD pid;
+ gint64 time;
+
+ time = g_get_monotonic_time ();
+
+ if (time - last_thread_enumeration_time < THREAD_ENUMERATION_INTERVAL)
+ return n_threads > 0;
+
+ last_thread_enumeration_time = time;
+
+ n_threads = 0;
+
+ hThreadSnap = CreateToolhelp32Snapshot (TH32CS_SNAPTHREAD, 0);
+
+ if (hThreadSnap == INVALID_HANDLE_VALUE)
+ return FALSE;
+
+ te32.dwSize = sizeof (te32);
+
+ if (! Thread32First (hThreadSnap, &te32))
+ {
+ CloseHandle (hThreadSnap);
+
+ return FALSE;
+ }
+
+ pid = GetCurrentProcessId ();
+
+ while (! g_atomic_int_compare_and_exchange (&thread_names_spinlock, 0, 1));
+
+ do
+ {
+ if (n_threads == MAX_N_THREADS)
+ break;
+
+ if (te32.th32OwnerProcessID == pid)
+ {
+ Thread *thread = &threads[n_threads++];
+ gint i;
+
+ thread->tid = te32.th32ThreadID;
+ thread->name = NULL;
+
+ for (i = n_thread_names - 1; i >= 0; i--)
+ {
+ if (thread->tid == thread_names[i].tid)
+ {
+ thread->name = thread_names[i].name;
+
+ break;
+ }
+ }
+ }
+ }
+ while (Thread32Next (hThreadSnap, &te32));
+
+ g_atomic_int_set (&thread_names_spinlock, 0);
+
+ CloseHandle (hThreadSnap);
+
+ return n_threads > 0;
+}
+
+static LONG WINAPI
+gimp_backtrace_exception_handler (PEXCEPTION_POINTERS info)
+{
+ #define EXCEPTION_SET_THREAD_NAME ((DWORD) 0x406D1388)
+
+ typedef struct _THREADNAME_INFO
+ {
+ DWORD dwType; /* must be 0x1000 */
+ LPCSTR szName; /* pointer to name (in user addr space) */
+ DWORD dwThreadID; /* thread ID (-1=caller thread) */
+ DWORD dwFlags; /* reserved for future use, must be zero */
+ } THREADNAME_INFO;
+
+ if (info->ExceptionRecord != NULL &&
+ info->ExceptionRecord->ExceptionCode == EXCEPTION_SET_THREAD_NAME &&
+ info->ExceptionRecord->NumberParameters *
+ sizeof (ULONG_PTR) == sizeof (THREADNAME_INFO))
+ {
+ THREADNAME_INFO name_info;
+
+ memcpy (&name_info, info->ExceptionRecord->ExceptionInformation,
+ sizeof (name_info));
+
+ if (name_info.dwType == 0x1000)
+ {
+ DWORD tid = name_info.dwThreadID;
+
+ if (tid == -1)
+ tid = GetCurrentThreadId ();
+
+ gimp_backtrace_set_thread_name (tid, name_info.szName);
+
+ return EXCEPTION_CONTINUE_EXECUTION;
+ }
+ }
+
+ return EXCEPTION_CONTINUE_SEARCH;
+
+ #undef EXCEPTION_SET_THREAD_NAME
+}
+
+
+/* public functions */
+
+
+void
+gimp_backtrace_init (void)
+{
+ gimp_backtrace_set_thread_name (GetCurrentThreadId (), g_get_prgname ());
+
+ AddVectoredExceptionHandler (TRUE, gimp_backtrace_exception_handler);
+}
+
+gboolean
+gimp_backtrace_start (void)
+{
+ g_mutex_lock (&mutex);
+
+ if (n_initializations == 0)
+ {
+ HMODULE hModule;
+ DWORD options;
+
+ hModule = LoadLibraryA ("mgwhelp.dll");
+
+ #define INIT_PROC(name) \
+ G_STMT_START \
+ { \
+ gimp_backtrace_##name = name; \
+ \
+ if (hModule) \
+ { \
+ gpointer proc = GetProcAddress (hModule, #name); \
+ \
+ if (proc) \
+ gimp_backtrace_##name = proc; \
+ } \
+ } \
+ G_STMT_END
+
+ INIT_PROC (SymSetOptions);
+ INIT_PROC (SymInitialize);
+ INIT_PROC (SymCleanup);
+ INIT_PROC (SymFromAddr);
+ INIT_PROC (SymGetLineFromAddr64);
+
+ #undef INIT_PROC
+
+ options = SymGetOptions ();
+
+ options &= ~SYMOPT_UNDNAME;
+ options |= SYMOPT_OMAP_FIND_NEAREST |
+ SYMOPT_DEFERRED_LOADS |
+ SYMOPT_DEBUG;
+
+#ifdef ARCH_X86_64
+ options |= SYMOPT_INCLUDE_32BIT_MODULES;
+#endif
+
+ gimp_backtrace_SymSetOptions (options);
+
+ if (gimp_backtrace_SymInitialize (GetCurrentProcess (), NULL, TRUE))
+ {
+ n_threads = 0;
+ last_thread_enumeration_time = 0;
+ n_thread_times = 0;
+
+ initialized = TRUE;
+ }
+ }
+
+ n_initializations++;
+
+ g_mutex_unlock (&mutex);
+
+ return initialized;
+}
+
+void
+gimp_backtrace_stop (void)
+{
+ g_return_if_fail (n_initializations > 0);
+
+ g_mutex_lock (&mutex);
+
+ n_initializations--;
+
+ if (n_initializations == 0)
+ {
+ if (initialized)
+ {
+ gimp_backtrace_SymCleanup (GetCurrentProcess ());
+
+ initialized = FALSE;
+ }
+ }
+
+ g_mutex_unlock (&mutex);
+}
+
+GimpBacktrace *
+gimp_backtrace_new (gboolean include_current_thread)
+{
+ GimpBacktrace *backtrace;
+ HANDLE hProcess;
+ DWORD tid;
+ gint i;
+
+ if (! initialized)
+ return NULL;
+
+ g_mutex_lock (&mutex);
+
+ if (! gimp_backtrace_enumerate_threads ())
+ {
+ g_mutex_unlock (&mutex);
+
+ return NULL;
+ }
+
+ hProcess = GetCurrentProcess ();
+ tid = GetCurrentThreadId ();
+
+ backtrace = g_slice_new (GimpBacktrace);
+
+ backtrace->threads = g_new (GimpBacktraceThread, n_threads);
+ backtrace->n_threads = 0;
+
+ for (i = 0; i < n_threads; i++)
+ {
+ GimpBacktraceThread *thread = &backtrace->threads[backtrace->n_threads];
+ HANDLE hThread;
+ CONTEXT context = {};
+ STACKFRAME64 frame = {};
+ DWORD machine_type;
+ FILETIME creation_time;
+ FILETIME exit_time;
+ FILETIME kernel_time;
+ FILETIME user_time;
+
+ if (! include_current_thread && threads[i].tid == tid)
+ continue;
+
+ hThread = OpenThread (THREAD_QUERY_INFORMATION |
+ THREAD_GET_CONTEXT |
+ THREAD_SUSPEND_RESUME,
+ FALSE,
+ threads[i].tid);
+
+ if (hThread == INVALID_HANDLE_VALUE)
+ continue;
+
+ if (threads[i].tid != tid && SuspendThread (hThread) == (DWORD) -1)
+ {
+ CloseHandle (hThread);
+
+ continue;
+ }
+
+ context.ContextFlags = CONTEXT_FULL;
+
+ if (! GetThreadContext (hThread, &context))
+ {
+ if (threads[i].tid != tid)
+ ResumeThread (hThread);
+
+ CloseHandle (hThread);
+
+ continue;
+ }
+
+#ifdef ARCH_X86_64
+ machine_type = IMAGE_FILE_MACHINE_AMD64;
+ frame.AddrPC.Offset = context.Rip;
+ frame.AddrPC.Mode = AddrModeFlat;
+ frame.AddrStack.Offset = context.Rsp;
+ frame.AddrStack.Mode = AddrModeFlat;
+ frame.AddrFrame.Offset = context.Rbp;
+ frame.AddrFrame.Mode = AddrModeFlat;
+#elif defined (ARCH_X86)
+ machine_type = IMAGE_FILE_MACHINE_I386;
+ frame.AddrPC.Offset = context.Eip;
+ frame.AddrPC.Mode = AddrModeFlat;
+ frame.AddrStack.Offset = context.Esp;
+ frame.AddrStack.Mode = AddrModeFlat;
+ frame.AddrFrame.Offset = context.Ebp;
+ frame.AddrFrame.Mode = AddrModeFlat;
+#else
+#error unsupported architecture
+#endif
+
+ thread->tid = threads[i].tid;
+ thread->name = threads[i].name;
+ thread->last_time = 0;
+ thread->time = 0;
+
+ thread->n_frames = 0;
+
+ while (thread->n_frames < MAX_N_FRAMES &&
+ StackWalk64 (machine_type, hProcess, hThread, &frame, &context,
+ NULL,
+ SymFunctionTableAccess64,
+ SymGetModuleBase64,
+ NULL))
+ {
+ thread->frames[thread->n_frames++] = frame.AddrPC.Offset;
+
+ if (frame.AddrPC.Offset == frame.AddrReturn.Offset)
+ break;
+ }
+
+ if (GetThreadTimes (hThread,
+ &creation_time, &exit_time,
+ &kernel_time, &user_time))
+ {
+ thread->time = (((guint64) kernel_time.dwHighDateTime << 32) |
+ ((guint64) kernel_time.dwLowDateTime)) +
+ (((guint64) user_time.dwHighDateTime << 32) |
+ ((guint64) user_time.dwLowDateTime));
+
+ if (i < n_thread_times && thread->tid == thread_times[i].tid)
+ {
+ thread->last_time = thread_times[i].time;
+ }
+ else
+ {
+ gint j;
+
+ for (j = 0; j < n_thread_times; j++)
+ {
+ if (thread->tid == thread_times[j].tid)
+ {
+ thread->last_time = thread_times[j].time;
+
+ break;
+ }
+ }
+ }
+ }
+
+ if (threads[i].tid != tid)
+ ResumeThread (hThread);
+
+ CloseHandle (hThread);
+
+ if (thread->n_frames > 0)
+ backtrace->n_threads++;
+ }
+
+ n_thread_times = backtrace->n_threads;
+
+ for (i = 0; i < backtrace->n_threads; i++)
+ {
+ thread_times[i].tid = backtrace->threads[i].tid;
+ thread_times[i].time = backtrace->threads[i].time;
+ }
+
+ g_mutex_unlock (&mutex);
+
+ if (backtrace->n_threads == 0)
+ {
+ gimp_backtrace_free (backtrace);
+
+ return NULL;
+ }
+
+ return backtrace;
+}
+
+void
+gimp_backtrace_free (GimpBacktrace *backtrace)
+{
+ if (backtrace)
+ {
+ g_free (backtrace->threads);
+
+ g_slice_free (GimpBacktrace, backtrace);
+ }
+}
+
+gint
+gimp_backtrace_get_n_threads (GimpBacktrace *backtrace)
+{
+ g_return_val_if_fail (backtrace != NULL, 0);
+
+ return backtrace->n_threads;
+}
+
+guintptr
+gimp_backtrace_get_thread_id (GimpBacktrace *backtrace,
+ gint thread)
+{
+ g_return_val_if_fail (backtrace != NULL, 0);
+ g_return_val_if_fail (thread >= 0 && thread < backtrace->n_threads, 0);
+
+ return backtrace->threads[thread].tid;
+}
+
+const gchar *
+gimp_backtrace_get_thread_name (GimpBacktrace *backtrace,
+ gint thread)
+{
+ g_return_val_if_fail (backtrace != NULL, NULL);
+ g_return_val_if_fail (thread >= 0 && thread < backtrace->n_threads, NULL);
+
+ return backtrace->threads[thread].name;
+}
+
+gboolean
+gimp_backtrace_is_thread_running (GimpBacktrace *backtrace,
+ gint thread)
+{
+ g_return_val_if_fail (backtrace != NULL, FALSE);
+ g_return_val_if_fail (thread >= 0 && thread < backtrace->n_threads, FALSE);
+
+ return backtrace->threads[thread].time >
+ backtrace->threads[thread].last_time;
+}
+
+gint
+gimp_backtrace_find_thread_by_id (GimpBacktrace *backtrace,
+ guintptr thread_id,
+ gint thread_hint)
+{
+ DWORD tid = thread_id;
+ gint i;
+
+ g_return_val_if_fail (backtrace != NULL, -1);
+
+ if (thread_hint >= 0 &&
+ thread_hint < backtrace->n_threads &&
+ backtrace->threads[thread_hint].tid == tid)
+ {
+ return thread_hint;
+ }
+
+ for (i = 0; i < backtrace->n_threads; i++)
+ {
+ if (backtrace->threads[i].tid == tid)
+ return i;
+ }
+
+ return -1;
+}
+
+gint
+gimp_backtrace_get_n_frames (GimpBacktrace *backtrace,
+ gint thread)
+{
+ g_return_val_if_fail (backtrace != NULL, 0);
+ g_return_val_if_fail (thread >= 0 && thread < backtrace->n_threads, 0);
+
+ return backtrace->threads[thread].n_frames;
+}
+
+guintptr
+gimp_backtrace_get_frame_address (GimpBacktrace *backtrace,
+ gint thread,
+ gint frame)
+{
+ g_return_val_if_fail (backtrace != NULL, 0);
+ g_return_val_if_fail (thread >= 0 && thread < backtrace->n_threads, 0);
+
+ frame = gimp_backtrace_normalize_frame (backtrace, thread, frame);
+
+ g_return_val_if_fail (frame >= 0 &&
+ frame < backtrace->threads[thread].n_frames, 0);
+
+ return backtrace->threads[thread].frames[frame];
+}
+
+gboolean
+gimp_backtrace_get_address_info (guintptr address,
+ GimpBacktraceAddressInfo *info)
+{
+ SYMBOL_INFO *symbol_info;
+ HANDLE hProcess;
+ HMODULE hModule;
+ DWORD64 offset = 0;
+ IMAGEHLP_LINE64 line = {};
+ DWORD line_offset = 0;
+ gboolean result = FALSE;
+
+ hProcess = GetCurrentProcess ();
+ hModule = (HMODULE) (guintptr) SymGetModuleBase64 (hProcess, address);
+
+ if (hModule && GetModuleFileNameExA (hProcess, hModule,
+ info->object_name,
+ sizeof (info->object_name)))
+ {
+ result = TRUE;
+ }
+ else
+ {
+ info->object_name[0] = '\0';
+ }
+
+ symbol_info = g_malloc (sizeof (SYMBOL_INFO) +
+ sizeof (info->symbol_name) - 1);
+
+ symbol_info->SizeOfStruct = sizeof (SYMBOL_INFO);
+ symbol_info->MaxNameLen = sizeof (info->symbol_name);
+
+ if (gimp_backtrace_SymFromAddr (hProcess, address,
+ &offset, symbol_info))
+ {
+ g_strlcpy (info->symbol_name, symbol_info->Name,
+ sizeof (info->symbol_name));
+
+ info->symbol_address = offset ? address - offset : 0;
+
+ result = TRUE;
+ }
+ else
+ {
+ info->symbol_name[0] = '\0';
+ info->symbol_address = 0;
+ }
+
+ g_free (symbol_info);
+
+ if (gimp_backtrace_SymGetLineFromAddr64 (hProcess, address,
+ &line_offset, &line))
+ {
+ g_strlcpy (info->source_file, line.FileName,
+ sizeof (info->source_file));
+
+ info->source_line = line.LineNumber;
+
+ result = TRUE;
+ }
+ else
+ {
+ info->source_file[0] = '\0';
+ info->source_line = 0;
+ }
+
+ return result;
+}
+
+
+#endif /* GIMP_BACKTRACE_BACKEND_WINDOWS */
diff --git a/app/core/gimpbacktrace.h b/app/core/gimpbacktrace.h
new file mode 100644
index 0000000..8c172b2
--- /dev/null
+++ b/app/core/gimpbacktrace.h
@@ -0,0 +1,70 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpbacktrace.h
+ * Copyright (C) 2018 Ell
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_BACKTRACE_H__
+#define __GIMP_BACKTRACE_H__
+
+
+typedef struct _GimpBacktraceAddressInfo GimpBacktraceAddressInfo;
+
+
+struct _GimpBacktraceAddressInfo
+{
+ gchar object_name[256];
+
+ gchar symbol_name[256];
+ guintptr symbol_address;
+
+ gchar source_file[256];
+ gint source_line;
+};
+
+
+void gimp_backtrace_init (void);
+
+gboolean gimp_backtrace_start (void);
+void gimp_backtrace_stop (void);
+
+GimpBacktrace * gimp_backtrace_new (gboolean include_current_thread);
+void gimp_backtrace_free (GimpBacktrace *backtrace);
+
+gint gimp_backtrace_get_n_threads (GimpBacktrace *backtrace);
+guintptr gimp_backtrace_get_thread_id (GimpBacktrace *backtrace,
+ gint thread);
+const gchar * gimp_backtrace_get_thread_name (GimpBacktrace *backtrace,
+ gint thread);
+gboolean gimp_backtrace_is_thread_running (GimpBacktrace *backtrace,
+ gint thread);
+
+gint gimp_backtrace_find_thread_by_id (GimpBacktrace *backtrace,
+ guintptr thread_id,
+ gint thread_hint);
+
+gint gimp_backtrace_get_n_frames (GimpBacktrace *backtrace,
+ gint thread);
+guintptr gimp_backtrace_get_frame_address (GimpBacktrace *backtrace,
+ gint thread,
+ gint frame);
+
+gboolean gimp_backtrace_get_address_info (guintptr address,
+ GimpBacktraceAddressInfo *info);
+
+
+#endif /* __GIMP_BACKTRACE_H__ */
diff --git a/app/core/gimpbezierdesc.c b/app/core/gimpbezierdesc.c
new file mode 100644
index 0000000..6047544
--- /dev/null
+++ b/app/core/gimpbezierdesc.c
@@ -0,0 +1,202 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpbezierdesc.c
+ * Copyright (C) 2010 Michael Natterer <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <cairo.h>
+
+#include "core-types.h"
+
+#include "gimpbezierdesc.h"
+#include "gimpboundary.h"
+
+
+GType
+gimp_bezier_desc_get_type (void)
+{
+ static GType type = 0;
+
+ if (! type)
+ type = g_boxed_type_register_static ("GimpBezierDesc",
+ (GBoxedCopyFunc) gimp_bezier_desc_copy,
+ (GBoxedFreeFunc) gimp_bezier_desc_free);
+
+ return type;
+}
+
+GimpBezierDesc *
+gimp_bezier_desc_new (cairo_path_data_t *data,
+ gint n_data)
+{
+ GimpBezierDesc *desc;
+
+ g_return_val_if_fail (n_data == 0 || data != NULL, NULL);
+
+ desc = g_slice_new (GimpBezierDesc);
+
+ desc->status = CAIRO_STATUS_SUCCESS;
+ desc->num_data = n_data;
+ desc->data = data;
+
+ return desc;
+}
+
+static void
+add_polyline (GArray *path_data,
+ const GimpVector2 *points,
+ gint n_points,
+ gboolean closed)
+{
+ GimpVector2 prev = { 0.0, 0.0, };
+ cairo_path_data_t pd;
+ gint i;
+
+ for (i = 0; i < n_points; i++)
+ {
+ /* compress multiple identical coordinates */
+ if (i == 0 ||
+ prev.x != points[i].x ||
+ prev.y != points[i].y)
+ {
+ pd.header.type = (i == 0) ? CAIRO_PATH_MOVE_TO : CAIRO_PATH_LINE_TO;
+ pd.header.length = 2;
+
+ g_array_append_val (path_data, pd);
+
+ pd.point.x = points[i].x;
+ pd.point.y = points[i].y;
+
+ g_array_append_val (path_data, pd);
+
+ prev = points[i];
+ }
+ }
+
+ /* close the polyline when needed */
+ if (closed)
+ {
+ pd.header.type = CAIRO_PATH_CLOSE_PATH;
+ pd.header.length = 1;
+
+ g_array_append_val (path_data, pd);
+ }
+}
+
+GimpBezierDesc *
+gimp_bezier_desc_new_from_bound_segs (GimpBoundSeg *bound_segs,
+ gint n_bound_segs,
+ gint n_bound_groups)
+{
+ GArray *path_data;
+ GimpVector2 *points;
+ gint n_points;
+ gint seg;
+ gint i;
+ guint path_data_len;
+
+ g_return_val_if_fail (bound_segs != NULL, NULL);
+ g_return_val_if_fail (n_bound_segs > 0, NULL);
+
+ path_data = g_array_new (FALSE, FALSE, sizeof (cairo_path_data_t));
+
+ points = g_new0 (GimpVector2, n_bound_segs + 4);
+
+ seg = 0;
+ n_points = 0;
+
+ points[n_points].x = (gdouble) (bound_segs[0].x1);
+ points[n_points].y = (gdouble) (bound_segs[0].y1);
+
+ n_points++;
+
+ for (i = 0; i < n_bound_groups; i++)
+ {
+ while (bound_segs[seg].x1 != -1 ||
+ bound_segs[seg].x2 != -1 ||
+ bound_segs[seg].y1 != -1 ||
+ bound_segs[seg].y2 != -1)
+ {
+ points[n_points].x = (gdouble) (bound_segs[seg].x1);
+ points[n_points].y = (gdouble) (bound_segs[seg].y1);
+
+ n_points++;
+ seg++;
+ }
+
+ /* Close the stroke points up */
+ points[n_points] = points[0];
+
+ n_points++;
+
+ add_polyline (path_data, points, n_points, TRUE);
+
+ n_points = 0;
+ seg++;
+
+ points[n_points].x = (gdouble) (bound_segs[seg].x1);
+ points[n_points].y = (gdouble) (bound_segs[seg].y1);
+
+ n_points++;
+ }
+
+ g_free (points);
+
+ path_data_len = path_data->len;
+
+ return gimp_bezier_desc_new ((cairo_path_data_t *) g_array_free (path_data, FALSE),
+ path_data_len);
+}
+
+void
+gimp_bezier_desc_translate (GimpBezierDesc *desc,
+ gdouble offset_x,
+ gdouble offset_y)
+{
+ gint i, j;
+
+ g_return_if_fail (desc != NULL);
+
+ for (i = 0; i < desc->num_data; i += desc->data[i].header.length)
+ for (j = 1; j < desc->data[i].header.length; ++j)
+ {
+ desc->data[i+j].point.x += offset_x;
+ desc->data[i+j].point.y += offset_y;
+ }
+}
+
+GimpBezierDesc *
+gimp_bezier_desc_copy (const GimpBezierDesc *desc)
+{
+ g_return_val_if_fail (desc != NULL, NULL);
+
+ return gimp_bezier_desc_new (g_memdup (desc->data,
+ desc->num_data * sizeof (cairo_path_data_t)),
+ desc->num_data);
+}
+
+void
+gimp_bezier_desc_free (GimpBezierDesc *desc)
+{
+ g_return_if_fail (desc != NULL);
+
+ g_free (desc->data);
+ g_slice_free (GimpBezierDesc, desc);
+}
diff --git a/app/core/gimpbezierdesc.h b/app/core/gimpbezierdesc.h
new file mode 100644
index 0000000..3d09dc2
--- /dev/null
+++ b/app/core/gimpbezierdesc.h
@@ -0,0 +1,47 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpbezierdesc.h
+ * Copyright (C) 2010 Michael Natterer <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_BEZIER_DESC_H__
+#define __GIMP_BEZIER_DESC_H__
+
+
+#define GIMP_TYPE_BEZIER_DESC (gimp_bezier_desc_get_type ())
+
+GType gimp_bezier_desc_get_type (void) G_GNUC_CONST;
+
+
+/* takes ownership of "data" */
+GimpBezierDesc * gimp_bezier_desc_new (cairo_path_data_t *data,
+ gint n_data);
+
+/* expects sorted GimpBoundSegs */
+GimpBezierDesc * gimp_bezier_desc_new_from_bound_segs (GimpBoundSeg *bound_segs,
+ gint n_bound_segs,
+ gint n_bound_groups);
+
+void gimp_bezier_desc_translate (GimpBezierDesc *desc,
+ gdouble offset_x,
+ gdouble offset_y);
+
+GimpBezierDesc * gimp_bezier_desc_copy (const GimpBezierDesc *desc);
+void gimp_bezier_desc_free (GimpBezierDesc *desc);
+
+
+#endif /* __GIMP_BEZIER_DESC_H__ */
diff --git a/app/core/gimpboundary.c b/app/core/gimpboundary.c
new file mode 100644
index 0000000..995242c
--- /dev/null
+++ b/app/core/gimpboundary.c
@@ -0,0 +1,1016 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <gegl.h>
+
+#include "libgimpmath/gimpmath.h"
+
+#include "core-types.h"
+
+#include "gimpboundary.h"
+
+
+/* GimpBoundSeg array growth parameter */
+#define MAX_SEGS_INC 2048
+
+
+typedef struct _GimpBoundary GimpBoundary;
+
+struct _GimpBoundary
+{
+ /* The array of segments */
+ GimpBoundSeg *segs;
+ gint num_segs;
+ gint max_segs;
+
+ /* The array of vertical segments */
+ gint *vert_segs;
+
+ /* The empty segment arrays */
+ gint *empty_segs_n;
+ gint *empty_segs_c;
+ gint *empty_segs_l;
+ gint max_empty_segs;
+};
+
+
+/* local function prototypes */
+
+static GimpBoundary * gimp_boundary_new (const GeglRectangle *region);
+static GimpBoundSeg * gimp_boundary_free (GimpBoundary *boundary,
+ gboolean free_segs);
+
+static void gimp_boundary_add_seg (GimpBoundary *bounrady,
+ gint x1,
+ gint y1,
+ gint x2,
+ gint y2,
+ gboolean open);
+
+static void find_empty_segs (const GeglRectangle *region,
+ const gfloat *line_data,
+ gint scanline,
+ gint empty_segs[],
+ gint max_empty,
+ gint *num_empty,
+ GimpBoundaryType type,
+ gint x1,
+ gint y1,
+ gint x2,
+ gint y2,
+ gfloat threshold);
+static void process_horiz_seg (GimpBoundary *boundary,
+ gint x1,
+ gint y1,
+ gint x2,
+ gint y2,
+ gboolean open);
+static void make_horiz_segs (GimpBoundary *boundary,
+ gint start,
+ gint end,
+ gint scanline,
+ gint empty[],
+ gint num_empty,
+ gint top);
+static GimpBoundary * generate_boundary (GeglBuffer *buffer,
+ const GeglRectangle *region,
+ const Babl *format,
+ GimpBoundaryType type,
+ gint x1,
+ gint y1,
+ gint x2,
+ gint y2,
+ gfloat threshold);
+
+static gint cmp_segptr_xy1_addr (const GimpBoundSeg **seg_ptr_a,
+ const GimpBoundSeg **seg_ptr_b);
+static gint cmp_segptr_xy2_addr (const GimpBoundSeg **seg_ptr_a,
+ const GimpBoundSeg **seg_ptr_b);
+
+static gint cmp_segptr_xy1 (const GimpBoundSeg **seg_ptr_a,
+ const GimpBoundSeg **seg_ptr_b);
+static gint cmp_segptr_xy2 (const GimpBoundSeg **seg_ptr_a,
+ const GimpBoundSeg **seg_ptr_b);
+
+static const GimpBoundSeg * find_segment (const GimpBoundSeg **segs_by_xy1,
+ const GimpBoundSeg **segs_by_xy2,
+ gint num_segs,
+ gint x,
+ gint y);
+
+static const GimpBoundSeg * find_segment_with_func (const GimpBoundSeg **segs,
+ gint num_segs,
+ const GimpBoundSeg *search_seg,
+ GCompareFunc cmp_func);
+
+static void simplify_subdivide (const GimpBoundSeg *segs,
+ gint start_idx,
+ gint end_idx,
+ GArray **ret_points);
+
+
+/* public functions */
+
+/**
+ * gimp_boundary_find:
+ * @buffer: a #GeglBuffer
+ * @format: a #Babl float format representing the component to analyze
+ * @type: type of bounds
+ * @x1: left side of bounds
+ * @y1: top side of bounds
+ * @x2: right side of bounds
+ * @y2: bottom side of bounds
+ * @threshold: pixel value of boundary line
+ * @num_segs: number of returned #GimpBoundSeg's
+ *
+ * This function returns an array of #GimpBoundSeg's which describe all
+ * outlines along pixel value @threahold, optionally within specified
+ * bounds instead of the whole region.
+ *
+ * The @maskPR parameter can be any PixelRegion. If the region has
+ * more than 1 bytes/pixel, the last byte of each pixel is used to
+ * determine the boundary outline.
+ *
+ * Return value: the boundary array.
+ **/
+GimpBoundSeg *
+gimp_boundary_find (GeglBuffer *buffer,
+ const GeglRectangle *region,
+ const Babl *format,
+ GimpBoundaryType type,
+ int x1,
+ int y1,
+ int x2,
+ int y2,
+ gfloat threshold,
+ int *num_segs)
+{
+ GimpBoundary *boundary;
+ GeglRectangle rect = { 0, };
+
+ g_return_val_if_fail (GEGL_IS_BUFFER (buffer), NULL);
+ g_return_val_if_fail (num_segs != NULL, NULL);
+ g_return_val_if_fail (format != NULL, NULL);
+ g_return_val_if_fail (babl_format_get_bytes_per_pixel (format) ==
+ sizeof (gfloat), NULL);
+
+ if (region)
+ {
+ rect = *region;
+ }
+ else
+ {
+ rect.width = gegl_buffer_get_width (buffer);
+ rect.height = gegl_buffer_get_height (buffer);
+ }
+
+ boundary = generate_boundary (buffer, &rect, format, type,
+ x1, y1, x2, y2, threshold);
+
+ *num_segs = boundary->num_segs;
+
+ return gimp_boundary_free (boundary, FALSE);
+}
+
+/**
+ * gimp_boundary_sort:
+ * @segs: unsorted input segs.
+ * @num_segs: number of input segs
+ * @num_groups: number of groups in the sorted segs
+ *
+ * This function takes an array of #GimpBoundSeg's as returned by
+ * gimp_boundary_find() and sorts it by contiguous groups. The returned
+ * array contains markers consisting of -1 coordinates and is
+ * @num_groups elements longer than @segs.
+ *
+ * Return value: the sorted segs
+ **/
+GimpBoundSeg *
+gimp_boundary_sort (const GimpBoundSeg *segs,
+ gint num_segs,
+ gint *num_groups)
+{
+ GimpBoundary *boundary;
+ const GimpBoundSeg **segs_ptrs_by_xy1;
+ const GimpBoundSeg **segs_ptrs_by_xy2;
+ gint index;
+ gint x, y;
+ gint startx, starty;
+
+ g_return_val_if_fail ((segs == NULL && num_segs == 0) ||
+ (segs != NULL && num_segs > 0), NULL);
+ g_return_val_if_fail (num_groups != NULL, NULL);
+
+ *num_groups = 0;
+
+ if (num_segs == 0)
+ return NULL;
+
+ /* prepare arrays with GimpBoundSeg pointers sorted by xy1 and xy2
+ * accordingly
+ */
+ segs_ptrs_by_xy1 = g_new (const GimpBoundSeg *, num_segs);
+ segs_ptrs_by_xy2 = g_new (const GimpBoundSeg *, num_segs);
+
+ for (index = 0; index < num_segs; index++)
+ {
+ segs_ptrs_by_xy1[index] = segs + index;
+ segs_ptrs_by_xy2[index] = segs + index;
+ }
+
+ qsort (segs_ptrs_by_xy1, num_segs, sizeof (GimpBoundSeg *),
+ (GCompareFunc) cmp_segptr_xy1_addr);
+ qsort (segs_ptrs_by_xy2, num_segs, sizeof (GimpBoundSeg *),
+ (GCompareFunc) cmp_segptr_xy2_addr);
+
+ for (index = 0; index < num_segs; index++)
+ ((GimpBoundSeg *) segs)[index].visited = FALSE;
+
+ boundary = gimp_boundary_new (NULL);
+
+ for (index = 0; index < num_segs; index++)
+ {
+ const GimpBoundSeg *cur_seg;
+
+ if (segs[index].visited)
+ continue;
+
+ gimp_boundary_add_seg (boundary,
+ segs[index].x1, segs[index].y1,
+ segs[index].x2, segs[index].y2,
+ segs[index].open);
+
+ ((GimpBoundSeg *) segs)[index].visited = TRUE;
+
+ startx = segs[index].x1;
+ starty = segs[index].y1;
+ x = segs[index].x2;
+ y = segs[index].y2;
+
+ while ((cur_seg = find_segment (segs_ptrs_by_xy1, segs_ptrs_by_xy2,
+ num_segs, x, y)) != NULL)
+ {
+ /* make sure ordering is correct */
+ if (x == cur_seg->x1 && y == cur_seg->y1)
+ {
+ gimp_boundary_add_seg (boundary,
+ cur_seg->x1, cur_seg->y1,
+ cur_seg->x2, cur_seg->y2,
+ cur_seg->open);
+ x = cur_seg->x2;
+ y = cur_seg->y2;
+ }
+ else
+ {
+ gimp_boundary_add_seg (boundary,
+ cur_seg->x2, cur_seg->y2,
+ cur_seg->x1, cur_seg->y1,
+ cur_seg->open);
+ x = cur_seg->x1;
+ y = cur_seg->y1;
+ }
+
+ ((GimpBoundSeg *) cur_seg)->visited = TRUE;
+ }
+
+ if (G_UNLIKELY (x != startx || y != starty))
+ g_warning ("sort_boundary(): Unconnected boundary group!");
+
+ /* Mark the end of a group */
+ *num_groups = *num_groups + 1;
+ gimp_boundary_add_seg (boundary, -1, -1, -1, -1, 0);
+ }
+
+ g_free (segs_ptrs_by_xy1);
+ g_free (segs_ptrs_by_xy2);
+
+ return gimp_boundary_free (boundary, FALSE);
+}
+
+/**
+ * gimp_boundary_simplify:
+ * @sorted_segs: sorted input segs
+ * @num_groups: number of groups in the sorted segs
+ * @num_segs: number of returned segs.
+ *
+ * This function takes an array of #GimpBoundSeg's which has been sorted
+ * with gimp_boundary_sort() and reduces the number of segments while
+ * preserving the general shape as close as possible.
+ *
+ * Return value: the simplified segs.
+ **/
+GimpBoundSeg *
+gimp_boundary_simplify (GimpBoundSeg *sorted_segs,
+ gint num_groups,
+ gint *num_segs)
+{
+ GArray *new_bounds;
+ gint i, seg;
+
+ g_return_val_if_fail ((sorted_segs == NULL && num_groups == 0) ||
+ (sorted_segs != NULL && num_groups > 0), NULL);
+ g_return_val_if_fail (num_segs != NULL, NULL);
+
+ new_bounds = g_array_new (FALSE, FALSE, sizeof (GimpBoundSeg));
+
+ seg = 0;
+
+ for (i = 0; i < num_groups; i++)
+ {
+ gint start = seg;
+ gint n_points = 0;
+
+ while (sorted_segs[seg].x1 != -1 ||
+ sorted_segs[seg].x2 != -1 ||
+ sorted_segs[seg].y1 != -1 ||
+ sorted_segs[seg].y2 != -1)
+ {
+ n_points++;
+ seg++;
+ }
+
+ if (n_points > 0)
+ {
+ GArray *tmp_points;
+ GimpBoundSeg tmp_seg;
+ gint j;
+
+ tmp_points = g_array_new (FALSE, FALSE, sizeof (gint));
+
+ /* temporarily use the delimiter to close the polygon */
+ tmp_seg = sorted_segs[seg];
+ sorted_segs[seg] = sorted_segs[start];
+ simplify_subdivide (sorted_segs,
+ start, start + n_points, &tmp_points);
+ sorted_segs[seg] = tmp_seg;
+
+ for (j = 0; j < tmp_points->len; j++)
+ g_array_append_val (new_bounds,
+ sorted_segs[g_array_index (tmp_points,
+ gint, j)]);
+
+ g_array_append_val (new_bounds, sorted_segs[seg]);
+
+ g_array_free (tmp_points, TRUE);
+ }
+
+ seg++;
+ }
+
+ *num_segs = new_bounds->len;
+
+ return (GimpBoundSeg *) g_array_free (new_bounds, FALSE);
+}
+
+void
+gimp_boundary_offset (GimpBoundSeg *segs,
+ gint num_segs,
+ gint off_x,
+ gint off_y)
+{
+ gint i;
+
+ g_return_if_fail ((segs == NULL && num_segs == 0) ||
+ (segs != NULL && num_segs > 0));
+
+ for (i = 0; i < num_segs; i++)
+ {
+ /* don't offset sorting sentinels */
+ if (!(segs[i].x1 == -1 &&
+ segs[i].y1 == -1 &&
+ segs[i].x2 == -1 &&
+ segs[i].y2 == -1))
+ {
+ segs[i].x1 += off_x;
+ segs[i].y1 += off_y;
+ segs[i].x2 += off_x;
+ segs[i].y2 += off_y;
+ }
+ }
+}
+
+
+/* private functions */
+
+static GimpBoundary *
+gimp_boundary_new (const GeglRectangle *region)
+{
+ GimpBoundary *boundary = g_slice_new0 (GimpBoundary);
+
+ if (region)
+ {
+ gint i;
+
+ /* array for determining the vertical line segments
+ * which must be drawn
+ */
+ boundary->vert_segs = g_new (gint, region->width + region->x + 1);
+
+ for (i = 0; i <= (region->width + region->x); i++)
+ boundary->vert_segs[i] = -1;
+
+ /* find the maximum possible number of empty segments
+ * given the current mask
+ */
+ boundary->max_empty_segs = region->width + 3;
+
+ boundary->empty_segs_n = g_new (gint, boundary->max_empty_segs);
+ boundary->empty_segs_c = g_new (gint, boundary->max_empty_segs);
+ boundary->empty_segs_l = g_new (gint, boundary->max_empty_segs);
+ }
+
+ return boundary;
+}
+
+static GimpBoundSeg *
+gimp_boundary_free (GimpBoundary *boundary,
+ gboolean free_segs)
+{
+ GimpBoundSeg *segs = NULL;
+
+ if (free_segs)
+ g_free (boundary->segs);
+ else
+ segs = boundary->segs;
+
+ g_free (boundary->vert_segs);
+ g_free (boundary->empty_segs_n);
+ g_free (boundary->empty_segs_c);
+ g_free (boundary->empty_segs_l);
+
+ g_slice_free (GimpBoundary, boundary);
+
+ return segs;
+}
+
+static void
+gimp_boundary_add_seg (GimpBoundary *boundary,
+ gint x1,
+ gint y1,
+ gint x2,
+ gint y2,
+ gboolean open)
+{
+ if (boundary->num_segs >= boundary->max_segs)
+ {
+ boundary->max_segs += MAX_SEGS_INC;
+
+ boundary->segs = g_renew (GimpBoundSeg, boundary->segs, boundary->max_segs);
+ }
+
+ boundary->segs[boundary->num_segs].x1 = x1;
+ boundary->segs[boundary->num_segs].y1 = y1;
+ boundary->segs[boundary->num_segs].x2 = x2;
+ boundary->segs[boundary->num_segs].y2 = y2;
+ boundary->segs[boundary->num_segs].open = open;
+
+ boundary->num_segs ++;
+}
+
+static void
+find_empty_segs (const GeglRectangle *region,
+ const gfloat *line_data,
+ gint scanline,
+ gint empty_segs[],
+ gint max_empty,
+ gint *num_empty,
+ GimpBoundaryType type,
+ gint x1,
+ gint y1,
+ gint x2,
+ gint y2,
+ gfloat threshold)
+{
+ gint start = 0;
+ gint end = 0;
+ gint endx = 0;
+ gint last = -1;
+ gint l_num_empty;
+ gint x;
+
+ *num_empty = 0;
+
+ if (scanline < region->y || scanline >= (region->y + region->height))
+ {
+ empty_segs[(*num_empty)++] = 0;
+ empty_segs[(*num_empty)++] = G_MAXINT;
+ return;
+ }
+
+ if (type == GIMP_BOUNDARY_WITHIN_BOUNDS)
+ {
+ if (scanline < y1 || scanline >= y2)
+ {
+ empty_segs[(*num_empty)++] = 0;
+ empty_segs[(*num_empty)++] = G_MAXINT;
+ return;
+ }
+
+ start = x1;
+ end = x2;
+ }
+ else if (type == GIMP_BOUNDARY_IGNORE_BOUNDS)
+ {
+ start = region->x;
+ end = region->x + region->width;
+ if (scanline < y1 || scanline >= y2)
+ x2 = -1;
+ }
+
+ empty_segs[(*num_empty)++] = 0;
+
+ l_num_empty = *num_empty;
+
+ endx = end;
+
+ line_data += start;
+
+ for (x = start; x < end;)
+ {
+ if (type == GIMP_BOUNDARY_IGNORE_BOUNDS && (endx > x1 || x < x2))
+ {
+ for (; x < endx; x++)
+ {
+ gint val;
+
+ if (*line_data > threshold)
+ {
+ if (x >= x1 && x < x2)
+ val = -1;
+ else
+ val = 1;
+ }
+ else
+ {
+ val = -1;
+ }
+
+ line_data++;
+
+ if (last != val)
+ empty_segs[l_num_empty++] = x;
+
+ last = val;
+ }
+ }
+ else
+ {
+ for (; x < endx; x++)
+ {
+ gint val;
+
+ if (*line_data > threshold)
+ val = 1;
+ else
+ val = -1;
+
+ line_data++;
+
+ if (last != val)
+ empty_segs[l_num_empty++] = x;
+
+ last = val;
+ }
+ }
+ }
+
+ *num_empty = l_num_empty;
+
+ if (last > 0)
+ empty_segs[(*num_empty)++] = x;
+
+ empty_segs[(*num_empty)++] = G_MAXINT;
+}
+
+static void
+process_horiz_seg (GimpBoundary *boundary,
+ gint x1,
+ gint y1,
+ gint x2,
+ gint y2,
+ gboolean open)
+{
+ /* This procedure accounts for any vertical segments that must be
+ drawn to close in the horizontal segments. */
+
+ if (boundary->vert_segs[x1] >= 0)
+ {
+ gimp_boundary_add_seg (boundary, x1, boundary->vert_segs[x1], x1, y1, !open);
+ boundary->vert_segs[x1] = -1;
+ }
+ else
+ boundary->vert_segs[x1] = y1;
+
+ if (boundary->vert_segs[x2] >= 0)
+ {
+ gimp_boundary_add_seg (boundary, x2, boundary->vert_segs[x2], x2, y2, open);
+ boundary->vert_segs[x2] = -1;
+ }
+ else
+ boundary->vert_segs[x2] = y2;
+
+ gimp_boundary_add_seg (boundary, x1, y1, x2, y2, open);
+}
+
+static void
+make_horiz_segs (GimpBoundary *boundary,
+ gint start,
+ gint end,
+ gint scanline,
+ gint empty[],
+ gint num_empty,
+ gint top)
+{
+ gint empty_index;
+ gint e_s, e_e; /* empty segment start and end values */
+
+ for (empty_index = 0; empty_index < num_empty; empty_index += 2)
+ {
+ e_s = *empty++;
+ e_e = *empty++;
+
+ if (e_s <= start && e_e >= end)
+ {
+ process_horiz_seg (boundary,
+ start, scanline, end, scanline, top);
+ }
+ else if ((e_s > start && e_s < end) ||
+ (e_e < end && e_e > start))
+ {
+ process_horiz_seg (boundary,
+ MAX (e_s, start), scanline,
+ MIN (e_e, end), scanline, top);
+ }
+ }
+}
+
+static GimpBoundary *
+generate_boundary (GeglBuffer *buffer,
+ const GeglRectangle *region,
+ const Babl *format,
+ GimpBoundaryType type,
+ gint x1,
+ gint y1,
+ gint x2,
+ gint y2,
+ gfloat threshold)
+{
+ GimpBoundary *boundary;
+ GeglRectangle line_rect = { 0, };
+ gfloat *line_data;
+ gint scanline;
+ gint i;
+ gint start, end;
+ gint *tmp_segs;
+
+ gint num_empty_n = 0;
+ gint num_empty_c = 0;
+ gint num_empty_l = 0;
+
+ boundary = gimp_boundary_new (region);
+
+ line_rect.width = gegl_buffer_get_width (buffer);
+ line_rect.height = 1;
+
+ line_data = g_alloca (sizeof (gfloat) * line_rect.width);
+
+ start = 0;
+ end = 0;
+
+ if (type == GIMP_BOUNDARY_WITHIN_BOUNDS)
+ {
+ start = y1;
+ end = y2;
+ }
+ else if (type == GIMP_BOUNDARY_IGNORE_BOUNDS)
+ {
+ start = region->y;
+ end = region->y + region->height;
+ }
+
+ /* Find the empty segments for the previous and current scanlines */
+ find_empty_segs (region, NULL,
+ start - 1, boundary->empty_segs_l,
+ boundary->max_empty_segs, &num_empty_l,
+ type, x1, y1, x2, y2,
+ threshold);
+
+ line_rect.y = start;
+ gegl_buffer_get (buffer, &line_rect, 1.0, format,
+ line_data, GEGL_AUTO_ROWSTRIDE,
+ GEGL_ABYSS_NONE);
+
+ find_empty_segs (region, line_data,
+ start, boundary->empty_segs_c,
+ boundary->max_empty_segs, &num_empty_c,
+ type, x1, y1, x2, y2,
+ threshold);
+
+ for (scanline = start; scanline < end; scanline++)
+ {
+ /* find the empty segment list for the next scanline */
+ line_rect.y = scanline + 1;
+ if (scanline + 1 == end)
+ line_data = NULL;
+ else
+ gegl_buffer_get (buffer, &line_rect, 1.0, format,
+ line_data, GEGL_AUTO_ROWSTRIDE,
+ GEGL_ABYSS_NONE);
+
+ find_empty_segs (region, line_data,
+ scanline + 1, boundary->empty_segs_n,
+ boundary->max_empty_segs, &num_empty_n,
+ type, x1, y1, x2, y2,
+ threshold);
+
+ /* process the segments on the current scanline */
+ for (i = 1; i < num_empty_c - 1; i += 2)
+ {
+ make_horiz_segs (boundary,
+ boundary->empty_segs_c [i],
+ boundary->empty_segs_c [i+1],
+ scanline,
+ boundary->empty_segs_l, num_empty_l, 1);
+ make_horiz_segs (boundary,
+ boundary->empty_segs_c [i],
+ boundary->empty_segs_c [i+1],
+ scanline + 1,
+ boundary->empty_segs_n, num_empty_n, 0);
+ }
+
+ /* get the next scanline of empty segments, swap others */
+ tmp_segs = boundary->empty_segs_l;
+ boundary->empty_segs_l = boundary->empty_segs_c;
+ num_empty_l = num_empty_c;
+ boundary->empty_segs_c = boundary->empty_segs_n;
+ num_empty_c = num_empty_n;
+ boundary->empty_segs_n = tmp_segs;
+ }
+
+ return boundary;
+}
+
+/* sorting utility functions */
+
+static inline gint
+cmp_xy (const gint ax,
+ const gint ay,
+ const gint bx,
+ const gint by)
+{
+ if (ay < by)
+ {
+ return -1;
+ }
+ else if (ay > by)
+ {
+ return 1;
+ }
+ else if (ax < bx)
+ {
+ return -1;
+ }
+ else if (ax > bx)
+ {
+ return 1;
+ }
+ else
+ {
+ return 0;
+ }
+}
+
+
+/*
+ * Compares (x1, y1) pairs in specified segments, using their addresses if
+ * (x1, y1) pairs are equal.
+ */
+static gint
+cmp_segptr_xy1_addr (const GimpBoundSeg **seg_ptr_a,
+ const GimpBoundSeg **seg_ptr_b)
+{
+ const GimpBoundSeg *seg_a = *seg_ptr_a;
+ const GimpBoundSeg *seg_b = *seg_ptr_b;
+
+ gint result = cmp_xy (seg_a->x1, seg_a->y1, seg_b->x1, seg_b->y1);
+
+ if (result == 0)
+ {
+ if (seg_a < seg_b)
+ result = -1;
+ else if (seg_a > seg_b)
+ result = 1;
+ }
+
+ return result;
+}
+
+/*
+ * Compares (x2, y2) pairs in specified segments, using their addresses if
+ * (x2, y2) pairs are equal.
+ */
+static gint
+cmp_segptr_xy2_addr (const GimpBoundSeg **seg_ptr_a,
+ const GimpBoundSeg **seg_ptr_b)
+{
+ const GimpBoundSeg *seg_a = *seg_ptr_a;
+ const GimpBoundSeg *seg_b = *seg_ptr_b;
+
+ gint result = cmp_xy (seg_a->x2, seg_a->y2, seg_b->x2, seg_b->y2);
+
+ if (result == 0)
+ {
+ if (seg_a < seg_b)
+ result = -1;
+ else if (seg_a > seg_b)
+ result = 1;
+ }
+
+ return result;
+}
+
+
+/*
+ * Compares (x1, y1) pairs in specified segments.
+ */
+static gint
+cmp_segptr_xy1 (const GimpBoundSeg **seg_ptr_a,
+ const GimpBoundSeg **seg_ptr_b)
+{
+ const GimpBoundSeg *seg_a = *seg_ptr_a, *seg_b = *seg_ptr_b;
+
+ return cmp_xy (seg_a->x1, seg_a->y1, seg_b->x1, seg_b->y1);
+}
+
+/*
+ * Compares (x2, y2) pairs in specified segments.
+ */
+static gint
+cmp_segptr_xy2 (const GimpBoundSeg **seg_ptr_a,
+ const GimpBoundSeg **seg_ptr_b)
+{
+ const GimpBoundSeg *seg_a = *seg_ptr_a;
+ const GimpBoundSeg *seg_b = *seg_ptr_b;
+
+ return cmp_xy (seg_a->x2, seg_a->y2, seg_b->x2, seg_b->y2);
+}
+
+
+static const GimpBoundSeg *
+find_segment (const GimpBoundSeg **segs_by_xy1,
+ const GimpBoundSeg **segs_by_xy2,
+ gint num_segs,
+ gint x,
+ gint y)
+{
+ const GimpBoundSeg *segptr_xy1;
+ const GimpBoundSeg *segptr_xy2;
+ GimpBoundSeg search_seg;
+
+ search_seg.x1 = search_seg.x2 = x;
+ search_seg.y1 = search_seg.y2 = y;
+
+ segptr_xy1 = find_segment_with_func (segs_by_xy1, num_segs, &search_seg,
+ (GCompareFunc) cmp_segptr_xy1);
+ segptr_xy2 = find_segment_with_func (segs_by_xy2, num_segs, &search_seg,
+ (GCompareFunc) cmp_segptr_xy2);
+
+ /* return segment with smaller address */
+ if (segptr_xy1 != NULL && segptr_xy2 != NULL)
+ return MIN(segptr_xy1, segptr_xy2);
+ else if (segptr_xy1 != NULL)
+ return segptr_xy1;
+ else if (segptr_xy2 != NULL)
+ return segptr_xy2;
+
+ return NULL;
+}
+
+
+static const GimpBoundSeg *
+find_segment_with_func (const GimpBoundSeg **segs,
+ gint num_segs,
+ const GimpBoundSeg *search_seg,
+ GCompareFunc cmp_func)
+{
+ const GimpBoundSeg **seg;
+ const GimpBoundSeg *found_seg = NULL;
+
+ seg = bsearch (&search_seg, segs, num_segs, sizeof (GimpBoundSeg *), cmp_func);
+
+ if (seg != NULL)
+ {
+ /* find first matching segment */
+ while (seg > segs && cmp_func (seg - 1, &search_seg) == 0)
+ seg--;
+
+ /* find first non-visited segment */
+ while (seg != segs + num_segs && cmp_func (seg, &search_seg) == 0)
+ if (!(*seg)->visited)
+ {
+ found_seg = *seg;
+ break;
+ }
+ else
+ seg++;
+ }
+
+ return found_seg;
+}
+
+
+/* simplifying utility functions */
+
+static void
+simplify_subdivide (const GimpBoundSeg *segs,
+ gint start_idx,
+ gint end_idx,
+ GArray **ret_points)
+{
+ gint maxdist_idx;
+ gint maxdist;
+ gint threshold;
+ gint i, dx, dy;
+
+ if (end_idx - start_idx < 2)
+ {
+ *ret_points = g_array_append_val (*ret_points, start_idx);
+ return;
+ }
+
+ maxdist = 0;
+
+ if (segs[start_idx].x1 == segs[end_idx].x1 &&
+ segs[start_idx].y1 == segs[end_idx].y1)
+ {
+ /* start and endpoint are at the same coordinates */
+ for (i = start_idx + 1; i < end_idx; i++)
+ {
+ /* compare the sqared distances */
+ gint dist = (SQR (segs[i].x1 - segs[start_idx].x1) +
+ SQR (segs[i].y1 - segs[start_idx].y1));
+
+ if (dist > maxdist)
+ {
+ maxdist = dist;
+ }
+ }
+
+ threshold = 1;
+ }
+ else
+ {
+ dx = segs[end_idx].x1 - segs[start_idx].x1;
+ dy = segs[end_idx].y1 - segs[start_idx].y1;
+
+ for (i = start_idx + 1; i < end_idx; i++)
+ {
+ /* this is not really the euclidic distance, but is
+ * proportional for this part of the line
+ * (for the real distance we'd have to divide by
+ * (SQR(dx)+SQR(dy)))
+ */
+ gint dist = abs (dx * (segs[start_idx].y1 - segs[i].y1) -
+ dy * (segs[start_idx].x1 - segs[i].x1));
+
+ if (dist > maxdist)
+ {
+ maxdist = dist;
+ }
+ }
+
+ /* threshold is chosen to catch 45 degree stairs */
+ threshold = SQR (dx) + SQR (dy);
+ }
+
+ if (maxdist <= threshold)
+ {
+ *ret_points = g_array_append_val (*ret_points, start_idx);
+ return;
+ }
+
+ /* Simons hack */
+ maxdist_idx = (start_idx + end_idx) / 2;
+
+ simplify_subdivide (segs, start_idx, maxdist_idx, ret_points);
+ simplify_subdivide (segs, maxdist_idx, end_idx, ret_points);
+}
diff --git a/app/core/gimpboundary.h b/app/core/gimpboundary.h
new file mode 100644
index 0000000..1e05eec
--- /dev/null
+++ b/app/core/gimpboundary.h
@@ -0,0 +1,68 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_BOUNDARY_H__
+#define __GIMP_BOUNDARY_H__
+
+
+/* half intensity for mask */
+#define GIMP_BOUNDARY_HALF_WAY 0.5
+
+
+typedef enum
+{
+ GIMP_BOUNDARY_WITHIN_BOUNDS,
+ GIMP_BOUNDARY_IGNORE_BOUNDS
+} GimpBoundaryType;
+
+
+struct _GimpBoundSeg
+{
+ gint x1;
+ gint y1;
+ gint x2;
+ gint y2;
+ guint open : 1;
+ guint visited : 1;
+};
+
+
+GimpBoundSeg * gimp_boundary_find (GeglBuffer *buffer,
+ const GeglRectangle *region,
+ const Babl *format,
+ GimpBoundaryType type,
+ gint x1,
+ gint y1,
+ gint x2,
+ gint y2,
+ gfloat threshold,
+ gint *num_segs);
+GimpBoundSeg * gimp_boundary_sort (const GimpBoundSeg *segs,
+ gint num_segs,
+ gint *num_groups);
+GimpBoundSeg * gimp_boundary_simplify (GimpBoundSeg *sorted_segs,
+ gint num_groups,
+ gint *num_segs);
+
+/* offsets in-place */
+void gimp_boundary_offset (GimpBoundSeg *segs,
+ gint num_segs,
+ gint off_x,
+ gint off_y);
+
+
+#endif /* __GIMP_BOUNDARY_H__ */
diff --git a/app/core/gimpbrush-boundary.c b/app/core/gimpbrush-boundary.c
new file mode 100644
index 0000000..9e5cbaa
--- /dev/null
+++ b/app/core/gimpbrush-boundary.c
@@ -0,0 +1,130 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <cairo.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "core-types.h"
+
+#include "gimpbezierdesc.h"
+#include "gimpboundary.h"
+#include "gimpbrush.h"
+#include "gimpbrush-boundary.h"
+#include "gimptempbuf.h"
+
+
+static GimpBezierDesc *
+gimp_brush_transform_boundary_exact (GimpBrush *brush,
+ gdouble scale,
+ gdouble aspect_ratio,
+ gdouble angle,
+ gboolean reflect,
+ gdouble hardness)
+{
+ const GimpTempBuf *mask;
+
+ mask = gimp_brush_transform_mask (brush,
+ scale, aspect_ratio,
+ angle, reflect, hardness);
+
+ if (mask)
+ {
+ GeglBuffer *buffer;
+ GimpBoundSeg *bound_segs;
+ gint n_bound_segs;
+
+ buffer = gimp_temp_buf_create_buffer ((GimpTempBuf *) mask);
+
+ bound_segs = gimp_boundary_find (buffer, NULL,
+ babl_format ("Y float"),
+ GIMP_BOUNDARY_WITHIN_BOUNDS,
+ 0, 0,
+ gegl_buffer_get_width (buffer),
+ gegl_buffer_get_height (buffer),
+ 0.0,
+ &n_bound_segs);
+
+ g_object_unref (buffer);
+
+ if (bound_segs)
+ {
+ GimpBoundSeg *stroke_segs;
+ gint n_stroke_groups;
+
+ stroke_segs = gimp_boundary_sort (bound_segs, n_bound_segs,
+ &n_stroke_groups);
+
+ g_free (bound_segs);
+
+ if (stroke_segs)
+ {
+ GimpBezierDesc *path;
+
+ path = gimp_bezier_desc_new_from_bound_segs (stroke_segs,
+ n_bound_segs,
+ n_stroke_groups);
+
+ g_free (stroke_segs);
+
+ return path;
+ }
+ }
+ }
+
+ return NULL;
+}
+
+static GimpBezierDesc *
+gimp_brush_transform_boundary_approx (GimpBrush *brush,
+ gdouble scale,
+ gdouble aspect_ratio,
+ gdouble angle,
+ gboolean reflect,
+ gdouble hardness)
+{
+ return gimp_brush_transform_boundary_exact (brush,
+ scale, aspect_ratio,
+ angle, reflect, hardness);
+}
+
+GimpBezierDesc *
+gimp_brush_real_transform_boundary (GimpBrush *brush,
+ gdouble scale,
+ gdouble aspect_ratio,
+ gdouble angle,
+ gboolean reflect,
+ gdouble hardness,
+ gint *width,
+ gint *height)
+{
+ gimp_brush_transform_size (brush, scale, aspect_ratio, angle, reflect,
+ width, height);
+
+ if (*width < 256 && *height < 256)
+ {
+ return gimp_brush_transform_boundary_exact (brush,
+ scale, aspect_ratio,
+ angle, reflect, hardness);
+ }
+
+ return gimp_brush_transform_boundary_approx (brush,
+ scale, aspect_ratio,
+ angle, reflect, hardness);
+}
diff --git a/app/core/gimpbrush-boundary.h b/app/core/gimpbrush-boundary.h
new file mode 100644
index 0000000..0bc99db
--- /dev/null
+++ b/app/core/gimpbrush-boundary.h
@@ -0,0 +1,32 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_BRUSH_BOUNDARY_H__
+#define __GIMP_BRUSH_BOUNDARY_H__
+
+
+GimpBezierDesc * gimp_brush_real_transform_boundary (GimpBrush *brush,
+ gdouble scale,
+ gdouble aspect_ratio,
+ gdouble angle,
+ gboolean reflect,
+ gdouble hardness,
+ gint *width,
+ gint *height);
+
+
+#endif /* __GIMP_BRUSH_BOUNDARY_H__ */
diff --git a/app/core/gimpbrush-header.h b/app/core/gimpbrush-header.h
new file mode 100644
index 0000000..0e66715
--- /dev/null
+++ b/app/core/gimpbrush-header.h
@@ -0,0 +1,49 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_BRUSH_HEADER_H__
+#define __GIMP_BRUSH_HEADER_H__
+
+
+#define GIMP_BRUSH_MAGIC (('G' << 24) + ('I' << 16) + \
+ ('M' << 8) + ('P' << 0))
+#define GIMP_BRUSH_MAX_SIZE 10000 /* Max size in either dimension in px */
+#define GIMP_BRUSH_MAX_NAME 256 /* Max length of the brush's name */
+
+
+/* All field entries are MSB */
+
+typedef struct _GimpBrushHeader GimpBrushHeader;
+
+struct _GimpBrushHeader
+{
+ guint32 header_size; /* = sizeof (GimpBrushHeader) + brush name */
+ guint32 version; /* brush file version # */
+ guint32 width; /* width of brush */
+ guint32 height; /* height of brush */
+ guint32 bytes; /* depth of brush in bytes */
+ guint32 magic_number; /* GIMP brush magic number */
+ guint32 spacing; /* brush spacing */
+};
+
+/* In a brush file, next comes the brush name, null-terminated.
+ * After that comes the brush data -- width * height * bytes bytes of
+ * it...
+ */
+
+
+#endif /* __GIMP_BRUSH_HEADER_H__ */
diff --git a/app/core/gimpbrush-load.c b/app/core/gimpbrush-load.c
new file mode 100644
index 0000000..51bf1da
--- /dev/null
+++ b/app/core/gimpbrush-load.c
@@ -0,0 +1,1213 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpbrush-load.c
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpbase/gimpbase.h"
+
+#include "core-types.h"
+
+#include "gimpbrush.h"
+#include "gimpbrush-header.h"
+#include "gimpbrush-load.h"
+#include "gimpbrush-private.h"
+#include "gimppattern-header.h"
+#include "gimptempbuf.h"
+
+#include "gimp-intl.h"
+
+/* stuff from abr2gbr Copyright (C) 2001 Marco Lamberto <lm@sunnyspot.org> */
+/* the above is GPL see http://the.sunnyspot.org/gimp/ */
+
+typedef struct _AbrHeader AbrHeader;
+typedef struct _AbrBrushHeader AbrBrushHeader;
+typedef struct _AbrSampledBrushHeader AbrSampledBrushHeader;
+
+struct _AbrHeader
+{
+ gint16 version;
+ gint16 count;
+};
+
+struct _AbrBrushHeader
+{
+ gint16 type;
+ gint32 size;
+};
+
+struct _AbrSampledBrushHeader
+{
+ gint32 misc;
+ gint16 spacing;
+ gchar antialiasing;
+ gint16 bounds[4];
+ gint32 bounds_long[4];
+ gint16 depth;
+ gboolean wide;
+};
+
+
+/* local function prototypes */
+
+static GList * gimp_brush_load_abr_v12 (GDataInputStream *input,
+ AbrHeader *abr_hdr,
+ GFile *file,
+ GError **error);
+static GList * gimp_brush_load_abr_v6 (GDataInputStream *input,
+ AbrHeader *abr_hdr,
+ GFile *file,
+ GError **error);
+static GimpBrush * gimp_brush_load_abr_brush_v12 (GDataInputStream *input,
+ AbrHeader *abr_hdr,
+ gint index,
+ GFile *file,
+ GError **error);
+static GimpBrush * gimp_brush_load_abr_brush_v6 (GDataInputStream *input,
+ AbrHeader *abr_hdr,
+ gint32 max_offset,
+ gint index,
+ GFile *file,
+ GError **error);
+
+static gchar abr_read_char (GDataInputStream *input,
+ GError **error);
+static gint16 abr_read_short (GDataInputStream *input,
+ GError **error);
+static gint32 abr_read_long (GDataInputStream *input,
+ GError **error);
+static gchar * abr_read_ucs2_text (GDataInputStream *input,
+ GError **error);
+static gboolean abr_supported (AbrHeader *abr_hdr,
+ GError **error);
+static gboolean abr_reach_8bim_section (GDataInputStream *input,
+ const gchar *name,
+ GError **error);
+static gboolean abr_rle_decode (GDataInputStream *input,
+ gchar *buffer,
+ gsize buffer_size,
+ gint32 height,
+ GError **error);
+
+
+/* public functions */
+
+GList *
+gimp_brush_load (GimpContext *context,
+ GFile *file,
+ GInputStream *input,
+ GError **error)
+{
+ GimpBrush *brush;
+
+ g_return_val_if_fail (G_IS_FILE (file), NULL);
+ g_return_val_if_fail (G_IS_INPUT_STREAM (input), NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ brush = gimp_brush_load_brush (context, file, input, error);
+ if (! brush)
+ return NULL;
+
+ return g_list_prepend (NULL, brush);
+}
+
+GimpBrush *
+gimp_brush_load_brush (GimpContext *context,
+ GFile *file,
+ GInputStream *input,
+ GError **error)
+{
+ GimpBrush *brush;
+ gsize bn_size;
+ GimpBrushHeader header;
+ gchar *name = NULL;
+ guchar *mask;
+ gsize bytes_read;
+ gssize i, size;
+ gboolean success = TRUE;
+
+ g_return_val_if_fail (G_IS_FILE (file), NULL);
+ g_return_val_if_fail (G_IS_INPUT_STREAM (input), NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ /* read the header */
+ if (! g_input_stream_read_all (input, &header, sizeof (header),
+ &bytes_read, NULL, error) ||
+ bytes_read != sizeof (header))
+ {
+ return NULL;
+ }
+
+ /* rearrange the bytes in each unsigned int */
+ header.header_size = g_ntohl (header.header_size);
+ header.version = g_ntohl (header.version);
+ header.width = g_ntohl (header.width);
+ header.height = g_ntohl (header.height);
+ header.bytes = g_ntohl (header.bytes);
+ header.magic_number = g_ntohl (header.magic_number);
+ header.spacing = g_ntohl (header.spacing);
+
+ /* Check for correct file format */
+
+ if (header.width == 0)
+ {
+ g_set_error (error, GIMP_DATA_ERROR, GIMP_DATA_ERROR_READ,
+ _("Fatal parse error in brush file: Width = 0."));
+ return NULL;
+ }
+
+ if (header.height == 0)
+ {
+ g_set_error (error, GIMP_DATA_ERROR, GIMP_DATA_ERROR_READ,
+ _("Fatal parse error in brush file: Height = 0."));
+ return NULL;
+ }
+
+ if (header.bytes == 0)
+ {
+ g_set_error (error, GIMP_DATA_ERROR, GIMP_DATA_ERROR_READ,
+ _("Fatal parse error in brush file: Bytes = 0."));
+ return NULL;
+ }
+
+ if (header.width > GIMP_BRUSH_MAX_SIZE ||
+ header.height > GIMP_BRUSH_MAX_SIZE ||
+ G_MAXSIZE / header.width / header.height / MAX (4, header.bytes) < 1)
+ {
+ g_set_error (error, GIMP_DATA_ERROR, GIMP_DATA_ERROR_READ,
+ _("Fatal parse error in brush file: %dx%d over max size."),
+ header.width, header.height);
+ return NULL;
+ }
+
+ switch (header.version)
+ {
+ case 1:
+ /* If this is a version 1 brush, set the fp back 8 bytes */
+ if (! g_seekable_seek (G_SEEKABLE (input), -8, G_SEEK_CUR,
+ NULL, error))
+ return NULL;
+
+ header.header_size += 8;
+ /* spacing is not defined in version 1 */
+ header.spacing = 25;
+ break;
+
+ case 3: /* cinepaint brush */
+ if (header.bytes == 18 /* FLOAT16_GRAY_GIMAGE */)
+ {
+ header.bytes = 2;
+ }
+ else
+ {
+ g_set_error (error, GIMP_DATA_ERROR, GIMP_DATA_ERROR_READ,
+ _("Fatal parse error in brush file: Unknown depth %d."),
+ header.bytes);
+ return NULL;
+ }
+ /* fallthrough */
+
+ case 2:
+ if (header.magic_number == GIMP_BRUSH_MAGIC)
+ break;
+
+ default:
+ g_set_error (error, GIMP_DATA_ERROR, GIMP_DATA_ERROR_READ,
+ _("Fatal parse error in brush file: Unknown version %d."),
+ header.version);
+ return NULL;
+ }
+
+ if (header.header_size < sizeof (GimpBrushHeader))
+ {
+ g_set_error (error, GIMP_DATA_ERROR, GIMP_DATA_ERROR_READ,
+ _("Unsupported brush format"));
+ return NULL;
+ }
+
+ /* Read in the brush name */
+ if ((bn_size = (header.header_size - sizeof (header))))
+ {
+ gchar *utf8;
+
+ if (bn_size > GIMP_BRUSH_MAX_NAME)
+ {
+ g_set_error (error, GIMP_DATA_ERROR, GIMP_DATA_ERROR_READ,
+ _("Invalid header data in '%s': "
+ "Brush name is too long: %lu"),
+ gimp_file_get_utf8_name (file),
+ (gulong) bn_size);
+ return NULL;
+ }
+
+ name = g_new0 (gchar, bn_size + 1);
+
+ if (! g_input_stream_read_all (input, name, bn_size,
+ &bytes_read, NULL, error) ||
+ bytes_read != bn_size)
+ {
+ g_free (name);
+ return NULL;
+ }
+
+ utf8 = gimp_any_to_utf8 (name, bn_size - 1,
+ _("Invalid UTF-8 string in brush file '%s'."),
+ gimp_file_get_utf8_name (file));
+ g_free (name);
+ name = utf8;
+ }
+
+ if (! name)
+ name = g_strdup (_("Unnamed"));
+
+ brush = g_object_new (GIMP_TYPE_BRUSH,
+ "name", name,
+ "mime-type", "image/x-gimp-gbr",
+ NULL);
+ g_free (name);
+
+ brush->priv->mask = gimp_temp_buf_new (header.width, header.height,
+ babl_format ("Y u8"));
+
+ mask = gimp_temp_buf_get_data (brush->priv->mask);
+ size = header.width * header.height * header.bytes;
+
+ switch (header.bytes)
+ {
+ case 1:
+ success = (g_input_stream_read_all (input, mask, size,
+ &bytes_read, NULL, error) &&
+ bytes_read == size);
+
+ /* For backwards-compatibility, check if a pattern follows.
+ * The obsolete .gpb format did it this way.
+ */
+ if (success)
+ {
+ GimpPatternHeader ph;
+ goffset rewind;
+
+ rewind = g_seekable_tell (G_SEEKABLE (input));
+
+ if (g_input_stream_read_all (input, &ph, sizeof (GimpPatternHeader),
+ &bytes_read, NULL, NULL) &&
+ bytes_read == sizeof (GimpPatternHeader))
+ {
+ /* rearrange the bytes in each unsigned int */
+ ph.header_size = g_ntohl (ph.header_size);
+ ph.version = g_ntohl (ph.version);
+ ph.width = g_ntohl (ph.width);
+ ph.height = g_ntohl (ph.height);
+ ph.bytes = g_ntohl (ph.bytes);
+ ph.magic_number = g_ntohl (ph.magic_number);
+
+ if (ph.magic_number == GIMP_PATTERN_MAGIC &&
+ ph.version == 1 &&
+ ph.header_size > sizeof (GimpPatternHeader) &&
+ ph.bytes == 3 &&
+ ph.width == header.width &&
+ ph.height == header.height &&
+ g_input_stream_skip (input,
+ ph.header_size -
+ sizeof (GimpPatternHeader),
+ NULL, NULL) ==
+ ph.header_size - sizeof (GimpPatternHeader))
+ {
+ guchar *pixmap;
+ gssize pixmap_size;
+
+ brush->priv->pixmap =
+ gimp_temp_buf_new (header.width, header.height,
+ babl_format ("R'G'B' u8"));
+
+ pixmap = gimp_temp_buf_get_data (brush->priv->pixmap);
+
+ pixmap_size = gimp_temp_buf_get_data_size (brush->priv->pixmap);
+
+ success = (g_input_stream_read_all (input, pixmap,
+ pixmap_size,
+ &bytes_read, NULL,
+ error) &&
+ bytes_read == pixmap_size);
+ }
+ else
+ {
+ /* seek back if pattern wasn't found */
+ success = g_seekable_seek (G_SEEKABLE (input),
+ rewind, G_SEEK_SET,
+ NULL, error);
+ }
+ }
+ }
+ break;
+
+ case 2: /* cinepaint brush, 16 bit floats */
+ {
+ guchar buf[8 * 1024];
+
+ for (i = 0; success && i < size;)
+ {
+ gssize bytes = MIN (size - i, sizeof (buf));
+
+ success = (g_input_stream_read_all (input, buf, bytes,
+ &bytes_read, NULL, error) &&
+ bytes_read == bytes);
+
+ if (success)
+ {
+ guint16 *b = (guint16 *) buf;
+
+ i += bytes;
+
+ for (; bytes > 0; bytes -= 2, mask++, b++)
+ {
+ union
+ {
+ guint16 u[2];
+ gfloat f;
+ } short_float;
+
+#if G_BYTE_ORDER == G_LITTLE_ENDIAN
+ short_float.u[0] = 0;
+ short_float.u[1] = GUINT16_FROM_BE (*b);
+#else
+ short_float.u[0] = GUINT16_FROM_BE (*b);
+ short_float.u[1] = 0;
+#endif
+
+ *mask = (guchar) (short_float.f * 255.0 + 0.5);
+ }
+ }
+ }
+ }
+ break;
+
+ case 4:
+ {
+ guchar *pixmap;
+ guchar buf[8 * 1024];
+
+ brush->priv->pixmap = gimp_temp_buf_new (header.width, header.height,
+ babl_format ("R'G'B' u8"));
+ pixmap = gimp_temp_buf_get_data (brush->priv->pixmap);
+
+ for (i = 0; success && i < size;)
+ {
+ gssize bytes = MIN (size - i, sizeof (buf));
+
+ success = (g_input_stream_read_all (input, buf, bytes,
+ &bytes_read, NULL, error) &&
+ bytes_read == bytes);
+
+ if (success)
+ {
+ guchar *b = buf;
+
+ i += bytes;
+
+ for (; bytes > 0; bytes -= 4, pixmap += 3, mask++, b += 4)
+ {
+ pixmap[0] = b[0];
+ pixmap[1] = b[1];
+ pixmap[2] = b[2];
+
+ mask[0] = b[3];
+ }
+ }
+ }
+ }
+ break;
+
+ default:
+ g_object_unref (brush);
+ g_set_error (error, GIMP_DATA_ERROR, GIMP_DATA_ERROR_READ,
+ _("Fatal parse error in brush file:\n"
+ "Unsupported brush depth %d\n"
+ "GIMP brushes must be GRAY or RGBA."),
+ header.bytes);
+ return NULL;
+ }
+
+ if (! success)
+ {
+ g_object_unref (brush);
+ return NULL;
+ }
+
+ brush->priv->spacing = header.spacing;
+ brush->priv->x_axis.x = header.width / 2.0;
+ brush->priv->x_axis.y = 0.0;
+ brush->priv->y_axis.x = 0.0;
+ brush->priv->y_axis.y = header.height / 2.0;
+
+ return brush;
+}
+
+GList *
+gimp_brush_load_abr (GimpContext *context,
+ GFile *file,
+ GInputStream *input,
+ GError **error)
+{
+ GDataInputStream *data_input;
+ AbrHeader abr_hdr;
+ GList *brush_list = NULL;
+ GError *my_error = NULL;
+
+ g_return_val_if_fail (G_IS_FILE (file), NULL);
+ g_return_val_if_fail (G_IS_INPUT_STREAM (input), NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ data_input = g_data_input_stream_new (input);
+
+ g_data_input_stream_set_byte_order (data_input,
+ G_DATA_STREAM_BYTE_ORDER_BIG_ENDIAN);
+
+ abr_hdr.version = abr_read_short (data_input, &my_error);
+ if (my_error)
+ goto done;
+
+ /* sub-version for ABR v6 */
+ abr_hdr.count = abr_read_short (data_input, &my_error);
+ if (my_error)
+ goto done;
+
+ if (abr_supported (&abr_hdr, &my_error))
+ {
+ switch (abr_hdr.version)
+ {
+ case 1:
+ case 2:
+ brush_list = gimp_brush_load_abr_v12 (data_input, &abr_hdr,
+ file, &my_error);
+ break;
+
+ case 10:
+ case 6:
+ brush_list = gimp_brush_load_abr_v6 (data_input, &abr_hdr,
+ file, &my_error);
+ break;
+ }
+ }
+
+ done:
+
+ g_object_unref (data_input);
+
+ if (! brush_list)
+ {
+ if (! my_error)
+ g_set_error (&my_error, GIMP_DATA_ERROR, GIMP_DATA_ERROR_READ,
+ _("Unable to decode abr format version %d."),
+ abr_hdr.version);
+ }
+
+ if (my_error)
+ g_propagate_error (error, my_error);
+
+ return g_list_reverse (brush_list);
+}
+
+
+/* private functions */
+
+static GList *
+gimp_brush_load_abr_v12 (GDataInputStream *input,
+ AbrHeader *abr_hdr,
+ GFile *file,
+ GError **error)
+{
+ GList *brush_list = NULL;
+ gint i;
+
+ for (i = 0; i < abr_hdr->count; i++)
+ {
+ GimpBrush *brush;
+ GError *my_error = NULL;
+
+ brush = gimp_brush_load_abr_brush_v12 (input, abr_hdr, i,
+ file, &my_error);
+
+ /* a NULL brush without an error means an unsupported brush
+ * type was encountered, silently skip it and try the next one
+ */
+
+ if (brush)
+ {
+ brush_list = g_list_prepend (brush_list, brush);
+ }
+ else if (my_error)
+ {
+ g_propagate_error (error, my_error);
+ break;
+ }
+ }
+
+ return brush_list;
+}
+
+static GList *
+gimp_brush_load_abr_v6 (GDataInputStream *input,
+ AbrHeader *abr_hdr,
+ GFile *file,
+ GError **error)
+{
+ GList *brush_list = NULL;
+ gint32 sample_section_size;
+ goffset sample_section_end;
+ gint i = 1;
+
+ if (! abr_reach_8bim_section (input, "samp", error))
+ return brush_list;
+
+ sample_section_size = abr_read_long (input, error);
+ if (error && *error)
+ return brush_list;
+
+ sample_section_end = (sample_section_size +
+ g_seekable_tell (G_SEEKABLE (input)));
+
+ while (g_seekable_tell (G_SEEKABLE (input)) < sample_section_end)
+ {
+ GimpBrush *brush;
+ GError *my_error = NULL;
+
+ brush = gimp_brush_load_abr_brush_v6 (input, abr_hdr, sample_section_end,
+ i, file, &my_error);
+
+ /* a NULL brush without an error means an unsupported brush
+ * type was encountered, silently skip it and try the next one
+ */
+
+ if (brush)
+ {
+ brush_list = g_list_prepend (brush_list, brush);
+ }
+ else if (my_error)
+ {
+ g_propagate_error (error, my_error);
+ break;
+ }
+
+ i++;
+ }
+
+ return brush_list;
+}
+
+static GimpBrush *
+gimp_brush_load_abr_brush_v12 (GDataInputStream *input,
+ AbrHeader *abr_hdr,
+ gint index,
+ GFile *file,
+ GError **error)
+{
+ GimpBrush *brush = NULL;
+ AbrBrushHeader abr_brush_hdr;
+
+ abr_brush_hdr.type = abr_read_short (input, error);
+ if (error && *error)
+ return NULL;
+
+ abr_brush_hdr.size = abr_read_long (input, error);
+ if (error && *error)
+ return NULL;
+
+ if (abr_brush_hdr.size < 0)
+ {
+ g_set_error (error, GIMP_DATA_ERROR, GIMP_DATA_ERROR_READ,
+ _("Fatal parse error in brush file: "
+ "Brush size value corrupt."));
+ return NULL;
+ }
+
+ /* g_print(" + BRUSH\n | << type: %i block size: %i bytes\n",
+ * abr_brush_hdr.type, abr_brush_hdr.size);
+ */
+
+ switch (abr_brush_hdr.type)
+ {
+ case 1: /* computed brush */
+ /* FIXME: support it!
+ *
+ * We can probabaly feed the info into the generated brush code
+ * and get a usable brush back. It seems to support the same
+ * types -akl
+ */
+ g_printerr ("WARNING: computed brush unsupported, skipping.\n");
+ g_seekable_seek (G_SEEKABLE (input), abr_brush_hdr.size,
+ G_SEEK_CUR, NULL, NULL);
+ break;
+
+ case 2: /* sampled brush */
+ {
+ AbrSampledBrushHeader abr_sampled_brush_hdr;
+ gint width, height;
+ gint bytes;
+ gint size;
+ guchar *mask;
+ gint i;
+ gchar *name;
+ gchar *sample_name = NULL;
+ gchar *tmp;
+ gshort compress;
+
+ abr_sampled_brush_hdr.misc = abr_read_long (input, error);
+ if (error && *error)
+ break;
+
+ abr_sampled_brush_hdr.spacing = abr_read_short (input, error);
+ if (error && *error)
+ break;
+
+ if (abr_hdr->version == 2)
+ {
+ sample_name = abr_read_ucs2_text (input, error);
+ if (error && *error)
+ break;
+ }
+
+ abr_sampled_brush_hdr.antialiasing = abr_read_char (input, error);
+ if (error && *error)
+ break;
+
+ for (i = 0; i < 4; i++)
+ {
+ abr_sampled_brush_hdr.bounds[i] = abr_read_short (input, error);
+ if (error && *error)
+ break;
+ }
+
+ for (i = 0; i < 4; i++)
+ {
+ abr_sampled_brush_hdr.bounds_long[i] = abr_read_long (input, error);
+ if (error && *error)
+ break;
+ }
+
+ abr_sampled_brush_hdr.depth = abr_read_short (input, error);
+ if (error && *error)
+ break;
+
+ height = (abr_sampled_brush_hdr.bounds_long[2] -
+ abr_sampled_brush_hdr.bounds_long[0]); /* bottom - top */
+ width = (abr_sampled_brush_hdr.bounds_long[3] -
+ abr_sampled_brush_hdr.bounds_long[1]); /* right - left */
+ bytes = abr_sampled_brush_hdr.depth >> 3;
+
+ /* g_print ("width %i height %i bytes %i\n", width, height, bytes); */
+
+ if (width < 1 || width > 10000 ||
+ height < 1 || height > 10000 ||
+ bytes < 1 || bytes > 1 ||
+ G_MAXSIZE / width / height / bytes < 1)
+ {
+ g_set_error (error, GIMP_DATA_ERROR, GIMP_DATA_ERROR_READ,
+ _("Fatal parse error in brush file: "
+ "Brush dimensions out of range."));
+ break;
+ }
+
+ abr_sampled_brush_hdr.wide = height > 16384;
+
+ if (abr_sampled_brush_hdr.wide)
+ {
+ /* FIXME: support wide brushes */
+
+ g_set_error (error, GIMP_DATA_ERROR, GIMP_DATA_ERROR_READ,
+ _("Fatal parse error in brush file: "
+ "Wide brushes are not supported."));
+ break;
+ }
+
+ tmp = g_path_get_basename (gimp_file_get_utf8_name (file));
+ if (! sample_name)
+ {
+ /* build name from filename and index */
+ name = g_strdup_printf ("%s-%03d", tmp, index);
+ }
+ else
+ {
+ /* build name from filename and sample name */
+ name = g_strdup_printf ("%s-%s", tmp, sample_name);
+ g_free (sample_name);
+ }
+ g_free (tmp);
+
+ brush = g_object_new (GIMP_TYPE_BRUSH,
+ "name", name,
+ /* FIXME: MIME type!! */
+ "mime-type", "application/x-photoshop-abr",
+ NULL);
+
+ g_free (name);
+
+ brush->priv->spacing = abr_sampled_brush_hdr.spacing;
+ brush->priv->x_axis.x = width / 2.0;
+ brush->priv->x_axis.y = 0.0;
+ brush->priv->y_axis.x = 0.0;
+ brush->priv->y_axis.y = height / 2.0;
+ brush->priv->mask = gimp_temp_buf_new (width, height,
+ babl_format ("Y u8"));
+
+ mask = gimp_temp_buf_get_data (brush->priv->mask);
+ size = width * height * bytes;
+
+ compress = abr_read_char (input, error);
+ if (error && *error)
+ {
+ g_object_unref (brush);
+ brush = NULL;
+ break;
+ }
+
+ /* g_print(" | << size: %dx%d %d bit (%d bytes) %s\n",
+ * width, height, abr_sampled_brush_hdr.depth, size,
+ * comppres ? "compressed" : "raw");
+ */
+
+ if (! compress)
+ {
+ gsize bytes_read;
+
+ if (! g_input_stream_read_all (G_INPUT_STREAM (input),
+ mask, size,
+ &bytes_read, NULL, error) ||
+ bytes_read != size)
+ {
+ g_object_unref (brush);
+ brush = NULL;
+ break;
+ }
+ }
+ else
+ {
+ if (! abr_rle_decode (input, (gchar *) mask, size, height, error))
+ {
+ g_object_unref (brush);
+ brush = NULL;
+ break;
+ }
+ }
+ }
+ break;
+
+ default:
+ g_printerr ("WARNING: unknown brush type, skipping.\n");
+ g_seekable_seek (G_SEEKABLE (input), abr_brush_hdr.size,
+ G_SEEK_CUR, NULL, NULL);
+ break;
+ }
+
+ return brush;
+}
+
+static GimpBrush *
+gimp_brush_load_abr_brush_v6 (GDataInputStream *input,
+ AbrHeader *abr_hdr,
+ gint32 max_offset,
+ gint index,
+ GFile *file,
+ GError **error)
+{
+ GimpBrush *brush = NULL;
+ guchar *mask;
+
+ gint32 brush_size;
+ gint32 brush_end;
+ goffset next_brush;
+
+ gint32 top, left, bottom, right;
+ gint16 depth;
+ gchar compress;
+
+ gint32 width, height;
+ gint32 size;
+
+ gchar *tmp;
+ gchar *name;
+ gboolean r;
+
+ brush_size = abr_read_long (input, error);
+ if (error && *error)
+ return NULL;
+
+ if (brush_size < 0)
+ {
+ g_set_error (error, GIMP_DATA_ERROR, GIMP_DATA_ERROR_READ,
+ _("Fatal parse error in brush file: "
+ "Brush size value corrupt."));
+ return NULL;
+ }
+
+ brush_end = brush_size;
+
+ /* complement to 4 */
+ while (brush_end % 4 != 0)
+ brush_end++;
+
+ next_brush = (brush_end + g_seekable_tell (G_SEEKABLE (input)));
+
+ if (abr_hdr->count == 1)
+ {
+ /* discard key and short coordinates and unknown short */
+ r = g_seekable_seek (G_SEEKABLE (input), 47, G_SEEK_CUR,
+ NULL, error);
+ }
+ else
+ {
+ /* discard key and unknown bytes */
+ r = g_seekable_seek (G_SEEKABLE (input), 301, G_SEEK_CUR,
+ NULL, error);
+ }
+
+ if (! r)
+ {
+ g_prefix_error (error,
+ _("Fatal parse error in brush file: "
+ "File appears truncated: "));
+ return NULL;
+ }
+
+ top = abr_read_long (input, error); if (error && *error) return NULL;
+ left = abr_read_long (input, error); if (error && *error) return NULL;
+ bottom = abr_read_long (input, error); if (error && *error) return NULL;
+ right = abr_read_long (input, error); if (error && *error) return NULL;
+ depth = abr_read_short (input, error); if (error && *error) return NULL;
+ compress = abr_read_char (input, error); if (error && *error) return NULL;
+
+ depth = depth >> 3;
+
+ width = right - left;
+ height = bottom - top;
+ size = width * depth * height;
+
+#if 0
+ g_printerr ("width %i height %i depth %i compress %i\n",
+ width, height, depth, compress);
+#endif
+
+ if (width < 1 || width > 10000 ||
+ height < 1 || height > 10000 ||
+ depth < 1 || depth > 1 ||
+ G_MAXSIZE / width / height / depth < 1)
+ {
+ g_set_error (error, GIMP_DATA_ERROR, GIMP_DATA_ERROR_READ,
+ _("Fatal parse error in brush file: "
+ "Brush dimensions out of range."));
+ return NULL;
+ }
+
+ if (compress < 0 || compress > 1)
+ {
+ g_set_error (error, GIMP_DATA_ERROR, GIMP_DATA_ERROR_READ,
+ _("Fatal parse error in brush file: "
+ "Unknown compression method."));
+ return NULL;
+ }
+
+ tmp = g_path_get_basename (gimp_file_get_utf8_name (file));
+ name = g_strdup_printf ("%s-%03d", tmp, index);
+ g_free (tmp);
+
+ brush = g_object_new (GIMP_TYPE_BRUSH,
+ "name", name,
+ /* FIXME: MIME type!! */
+ "mime-type", "application/x-photoshop-abr",
+ NULL);
+
+ g_free (name);
+
+ brush->priv->spacing = 25; /* real value needs 8BIMdesc section parser */
+ brush->priv->x_axis.x = width / 2.0;
+ brush->priv->x_axis.y = 0.0;
+ brush->priv->y_axis.x = 0.0;
+ brush->priv->y_axis.y = height / 2.0;
+ brush->priv->mask = gimp_temp_buf_new (width, height,
+ babl_format ("Y u8"));
+
+ mask = gimp_temp_buf_get_data (brush->priv->mask);
+
+ /* data decoding */
+ if (! compress)
+ {
+ /* not compressed - read raw bytes as brush data */
+ gsize bytes_read;
+
+ if (! g_input_stream_read_all (G_INPUT_STREAM (input),
+ mask, size,
+ &bytes_read, NULL, error) ||
+ bytes_read != size)
+ {
+ g_object_unref (brush);
+ return NULL;
+ }
+ }
+ else
+ {
+ if (! abr_rle_decode (input, (gchar *) mask, size, height, error))
+ {
+ g_object_unref (brush);
+ return NULL;
+ }
+ }
+
+ if (g_seekable_tell (G_SEEKABLE (input)) <= next_brush)
+ g_seekable_seek (G_SEEKABLE (input), next_brush, G_SEEK_SET,
+ NULL, NULL);
+
+ return brush;
+}
+
+static gchar
+abr_read_char (GDataInputStream *input,
+ GError **error)
+{
+ return g_data_input_stream_read_byte (input, NULL, error);
+}
+
+static gint16
+abr_read_short (GDataInputStream *input,
+ GError **error)
+{
+ return g_data_input_stream_read_int16 (input, NULL, error);
+}
+
+static gint32
+abr_read_long (GDataInputStream *input,
+ GError **error)
+{
+ return g_data_input_stream_read_int32 (input, NULL, error);
+}
+
+static gchar *
+abr_read_ucs2_text (GDataInputStream *input,
+ GError **error)
+{
+ gchar *name_ucs2;
+ gchar *name_utf8;
+ gint len;
+ gint i;
+
+ /* two-bytes characters encoded (UCS-2)
+ * format:
+ * long : number of characters in string
+ * data : zero terminated UCS-2 string
+ */
+
+ len = 2 * abr_read_long (input, error);
+ if (len <= 0)
+ return NULL;
+
+ name_ucs2 = g_new (gchar, len);
+
+ for (i = 0; i < len; i++)
+ {
+ name_ucs2[i] = abr_read_char (input, error);
+ if (error && *error)
+ {
+ g_free (name_ucs2);
+ return NULL;
+ }
+ }
+
+ name_utf8 = g_convert (name_ucs2, len,
+ "UTF-8", "UCS-2BE",
+ NULL, NULL, NULL);
+
+ g_free (name_ucs2);
+
+ return name_utf8;
+}
+
+static gboolean
+abr_supported (AbrHeader *abr_hdr,
+ GError **error)
+{
+ switch (abr_hdr->version)
+ {
+ case 1:
+ case 2:
+ return TRUE;
+ break;
+
+ case 10:
+ case 6:
+ /* in this case, count contains format sub-version */
+ if (abr_hdr->count == 1 || abr_hdr->count == 2)
+ return TRUE;
+
+ g_set_error (error, GIMP_DATA_ERROR, GIMP_DATA_ERROR_READ,
+ _("Fatal parse error in brush file: "
+ "Unable to decode abr format version %d."),
+
+ /* horrid subversion display, but better than
+ * having yet another translatable string for
+ * this
+ */
+ abr_hdr->version * 10 + abr_hdr->count);
+ break;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+abr_reach_8bim_section (GDataInputStream *input,
+ const gchar *name,
+ GError **error)
+{
+ while (TRUE)
+ {
+ gchar tag[4];
+ gchar tagname[5];
+ guint32 section_size;
+ gsize bytes_read;
+
+ if (! g_input_stream_read_all (G_INPUT_STREAM (input),
+ tag, 4,
+ &bytes_read, NULL, error) ||
+ bytes_read != 4)
+ return FALSE;
+
+ if (strncmp (tag, "8BIM", 4))
+ return FALSE;
+
+ if (! g_input_stream_read_all (G_INPUT_STREAM (input),
+ tagname, 4,
+ &bytes_read, NULL, error) ||
+ bytes_read != 4)
+ return FALSE;
+
+ tagname[4] = '\0';
+
+ if (! strncmp (tagname, name, 4))
+ return TRUE;
+
+ section_size = abr_read_long (input, error);
+ if (error && *error)
+ return FALSE;
+
+ if (! g_seekable_seek (G_SEEKABLE (input), section_size, G_SEEK_CUR,
+ NULL, error))
+ return FALSE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+abr_rle_decode (GDataInputStream *input,
+ gchar *buffer,
+ gsize buffer_size,
+ gint32 height,
+ GError **error)
+{
+ gint i, j;
+ gshort *cscanline_len = NULL;
+ gchar *cdata = NULL;
+ gchar *data = buffer;
+
+ /* read compressed size foreach scanline */
+ cscanline_len = gegl_scratch_new (gshort, height);
+ for (i = 0; i < height; i++)
+ {
+ cscanline_len[i] = abr_read_short (input, error);
+ if ((error && *error) || cscanline_len[i] <= 0)
+ goto err;
+ }
+
+ /* unpack each scanline data */
+ for (i = 0; i < height; i++)
+ {
+ gint len;
+ gsize bytes_read;
+
+ len = cscanline_len[i];
+
+ cdata = gegl_scratch_alloc (len);
+
+ if (! g_input_stream_read_all (G_INPUT_STREAM (input),
+ cdata, len,
+ &bytes_read, NULL, error) ||
+ bytes_read != len)
+ {
+ goto err;
+ }
+
+ for (j = 0; j < len;)
+ {
+ gint32 n = cdata[j++];
+
+ if (n >= 128) /* force sign */
+ n -= 256;
+
+ if (n < 0)
+ {
+ /* copy the following char -n + 1 times */
+
+ if (n == -128) /* it's a nop */
+ continue;
+
+ n = -n + 1;
+
+ if (j + 1 > len || (data - buffer) + n > buffer_size)
+ goto err;
+
+ memset (data, cdata[j], n);
+
+ j += 1;
+ data += n;
+ }
+ else
+ {
+ /* read the following n + 1 chars (no compr) */
+
+ n = n + 1;
+
+ if (j + n > len || (data - buffer) + n > buffer_size)
+ goto err;
+
+ memcpy (data, &cdata[j], n);
+
+ j += n;
+ data += n;
+ }
+ }
+
+ g_clear_pointer (&cdata, gegl_scratch_free);
+ }
+
+ g_clear_pointer (&cscanline_len, gegl_scratch_free);
+
+ return TRUE;
+
+err:
+ g_clear_pointer (&cdata, gegl_scratch_free);
+ g_clear_pointer (&cscanline_len, gegl_scratch_free);
+ if (error && ! *error)
+ {
+ g_set_error (error, GIMP_DATA_ERROR, GIMP_DATA_ERROR_READ,
+ _("Fatal parse error in brush file: "
+ "RLE compressed brush data corrupt."));
+ }
+ return FALSE;
+}
diff --git a/app/core/gimpbrush-load.h b/app/core/gimpbrush-load.h
new file mode 100644
index 0000000..6c4f0af
--- /dev/null
+++ b/app/core/gimpbrush-load.h
@@ -0,0 +1,43 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_BRUSH_LOAD_H__
+#define __GIMP_BRUSH_LOAD_H__
+
+
+#define GIMP_BRUSH_FILE_EXTENSION ".gbr"
+#define GIMP_BRUSH_PIXMAP_FILE_EXTENSION ".gpb"
+#define GIMP_BRUSH_PS_FILE_EXTENSION ".abr"
+#define GIMP_BRUSH_PSP_FILE_EXTENSION ".jbr"
+
+
+GList * gimp_brush_load (GimpContext *context,
+ GFile *file,
+ GInputStream *input,
+ GError **error);
+GimpBrush * gimp_brush_load_brush (GimpContext *context,
+ GFile *file,
+ GInputStream *input,
+ GError **error);
+
+GList * gimp_brush_load_abr (GimpContext *context,
+ GFile *file,
+ GInputStream *input,
+ GError **error);
+
+
+#endif /* __GIMP_BRUSH_LOAD_H__ */
diff --git a/app/core/gimpbrush-mipmap.cc b/app/core/gimpbrush-mipmap.cc
new file mode 100644
index 0000000..7c3b061
--- /dev/null
+++ b/app/core/gimpbrush-mipmap.cc
@@ -0,0 +1,514 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpbrush-mipmap.c
+ * Copyright (C) 2020 Ell
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpmath/gimpmath.h"
+
+extern "C"
+{
+
+#include "core-types.h"
+
+#include "gimpbrush.h"
+#include "gimpbrush-mipmap.h"
+#include "gimpbrush-private.h"
+#include "gimptempbuf.h"
+
+} /* extern "C" */
+
+
+#define PIXELS_PER_THREAD \
+ (/* each thread costs as much as */ 64.0 * 64.0 /* pixels */)
+
+#define GIMP_BRUSH_MIPMAP(brush, mipmaps, x, y) \
+ ((*(mipmaps))[(y) * (brush)->priv->n_horz_mipmaps + (x)])
+
+
+/* local function prototypes */
+
+static void gimp_brush_mipmap_clear (GimpBrush *brush,
+ GimpTempBuf ***mipmaps);
+
+static const GimpTempBuf * gimp_brush_mipmap_get (GimpBrush *brush,
+ const GimpTempBuf *source,
+ GimpTempBuf ***mipmaps,
+ gdouble *scale_x,
+ gdouble *scale_y);
+
+static GimpTempBuf * gimp_brush_mipmap_downscale (const GimpTempBuf *source);
+static GimpTempBuf * gimp_brush_mipmap_downscale_horz (const GimpTempBuf *source);
+static GimpTempBuf * gimp_brush_mipmap_downscale_vert (const GimpTempBuf *source);
+
+
+/* private functions */
+
+static void
+gimp_brush_mipmap_clear (GimpBrush *brush,
+ GimpTempBuf ***mipmaps)
+{
+ if (*mipmaps)
+ {
+ gint i;
+
+ for (i = 0;
+ i < brush->priv->n_horz_mipmaps * brush->priv->n_vert_mipmaps;
+ i++)
+ {
+ g_clear_pointer (&(*mipmaps)[i], gimp_temp_buf_unref);
+ }
+
+ g_clear_pointer (mipmaps, g_free);
+ }
+}
+
+static const GimpTempBuf *
+gimp_brush_mipmap_get (GimpBrush *brush,
+ const GimpTempBuf *source,
+ GimpTempBuf ***mipmaps,
+ gdouble *scale_x,
+ gdouble *scale_y)
+{
+ gint x;
+ gint y;
+ gint i;
+
+ if (! source)
+ return NULL;
+
+ if (! *mipmaps)
+ {
+ gint width = gimp_temp_buf_get_width (source);
+ gint height = gimp_temp_buf_get_height (source);
+
+ brush->priv->n_horz_mipmaps = floor (log (width) / M_LN2) + 1;
+ brush->priv->n_vert_mipmaps = floor (log (height) / M_LN2) + 1;
+
+ *mipmaps = g_new0 (GimpTempBuf *, brush->priv->n_horz_mipmaps *
+ brush->priv->n_vert_mipmaps);
+
+ GIMP_BRUSH_MIPMAP (brush, mipmaps, 0, 0) = gimp_temp_buf_ref (source);
+ }
+
+ x = floor (SAFE_CLAMP (log (1.0 / MAX (*scale_x, 0.0)) / M_LN2,
+ 0, brush->priv->n_horz_mipmaps - 1));
+ y = floor (SAFE_CLAMP (log (1.0 / MAX (*scale_y, 0.0)) / M_LN2,
+ 0, brush->priv->n_vert_mipmaps - 1));
+
+ *scale_x *= pow (2.0, x);
+ *scale_y *= pow (2.0, y);
+
+ if (GIMP_BRUSH_MIPMAP (brush, mipmaps, x, y))
+ return GIMP_BRUSH_MIPMAP (brush, mipmaps, x, y);
+
+ g_return_val_if_fail (x >= 0 || y >= 0, NULL);
+
+ for (i = 1; i <= x + y; i++)
+ {
+ gint u = x - i;
+ gint v = y;
+
+ if (u < 0)
+ {
+ v += u;
+ u = 0;
+ }
+
+ while (u <= x && v >= 0)
+ {
+ if (GIMP_BRUSH_MIPMAP (brush, mipmaps, u, v))
+ {
+ for (; x - u > y - v; u++)
+ {
+ GIMP_BRUSH_MIPMAP (brush, mipmaps, u + 1, v) =
+ gimp_brush_mipmap_downscale_horz (
+ GIMP_BRUSH_MIPMAP (brush, mipmaps, u, v));
+ }
+
+ for (; y - v > x - u; v++)
+ {
+ GIMP_BRUSH_MIPMAP (brush, mipmaps, u, v + 1) =
+ gimp_brush_mipmap_downscale_vert (
+ GIMP_BRUSH_MIPMAP (brush, mipmaps, u, v));
+ }
+
+ for (; u < x; u++, v++)
+ {
+ GIMP_BRUSH_MIPMAP (brush, mipmaps, u + 1, v + 1) =
+ gimp_brush_mipmap_downscale (
+ GIMP_BRUSH_MIPMAP (brush, mipmaps, u, v));
+ }
+
+ return GIMP_BRUSH_MIPMAP (brush, mipmaps, u, v);
+ }
+
+ u++;
+ v--;
+ }
+ }
+
+ g_return_val_if_reached (NULL);
+}
+
+template <class T>
+struct MipmapTraits;
+
+template <>
+struct MipmapTraits<guint8>
+{
+ static guint8
+ mix (guint8 a,
+ guint8 b)
+ {
+ return ((guint32) a + (guint32) b + 1) / 2;
+ }
+
+ static guint8
+ mix (guint8 a,
+ guint8 b,
+ guint8 c,
+ guint8 d)
+ {
+ return ((guint32) a + (guint32) b + (guint32) c + (guint32) d + 2) / 4;
+ }
+};
+
+template <>
+struct MipmapTraits<gfloat>
+{
+ static gfloat
+ mix (gfloat a,
+ gfloat b)
+ {
+ return (a + b) / 2.0;
+ }
+
+ static gfloat
+ mix (gfloat a,
+ gfloat b,
+ gfloat c,
+ gfloat d)
+ {
+ return (a + b + c + d) / 4.0;
+ }
+};
+
+template <class T,
+ gint N>
+struct MipmapAlgorithms
+{
+ static GimpTempBuf *
+ downscale (const GimpTempBuf *source)
+ {
+ GimpTempBuf *destination;
+ gint width = gimp_temp_buf_get_width (source);
+ gint height = gimp_temp_buf_get_height (source);
+
+ width /= 2;
+ height /= 2;
+
+ destination = gimp_temp_buf_new (width, height,
+ gimp_temp_buf_get_format (source));
+
+ gegl_parallel_distribute_area (
+ GEGL_RECTANGLE (0, 0, width, height), PIXELS_PER_THREAD,
+ [=] (const GeglRectangle *area)
+ {
+ const T *src0 = (const T *) gimp_temp_buf_get_data (source);
+ T *dest0 = (T *) gimp_temp_buf_get_data (destination);
+ gint src_stride = N * gimp_temp_buf_get_width (source);
+ gint dest_stride = N * gimp_temp_buf_get_width (destination);
+ gint y;
+
+ src0 += 2 * (area->y * src_stride + N * area->x);
+ dest0 += area->y * dest_stride + N * area->x;
+
+ for (y = 0; y < area->height; y++)
+ {
+ const T *src = src0;
+ T *dest = dest0;
+ gint x;
+
+ for (x = 0; x < area->width; x++)
+ {
+ gint c;
+
+ for (c = 0; c < N; c++)
+ {
+ dest[c] = MipmapTraits<T>::mix (src[c],
+ src[N + c],
+ src[src_stride + c],
+ src[src_stride + N + c]);
+ }
+
+ src += 2 * N;
+ dest += N;
+ }
+
+ src0 += 2 * src_stride;
+ dest0 += dest_stride;
+ }
+ });
+
+ return destination;
+ }
+
+ static GimpTempBuf *
+ downscale_horz (const GimpTempBuf *source)
+ {
+ GimpTempBuf *destination;
+ gint width = gimp_temp_buf_get_width (source);
+ gint height = gimp_temp_buf_get_height (source);
+
+ width /= 2;
+
+ destination = gimp_temp_buf_new (width, height,
+ gimp_temp_buf_get_format (source));
+
+ gegl_parallel_distribute_range (
+ height, PIXELS_PER_THREAD / width,
+ [=] (gint offset,
+ gint size)
+ {
+ const T *src0 = (const T *) gimp_temp_buf_get_data (source);
+ T *dest0 = (T *) gimp_temp_buf_get_data (destination);
+ gint src_stride = N * gimp_temp_buf_get_width (source);
+ gint dest_stride = N * gimp_temp_buf_get_width (destination);
+ gint y;
+
+ src0 += offset * src_stride;
+ dest0 += offset * dest_stride;
+
+ for (y = 0; y < size; y++)
+ {
+ const T *src = src0;
+ T *dest = dest0;
+ gint x;
+
+ for (x = 0; x < width; x++)
+ {
+ gint c;
+
+ for (c = 0; c < N; c++)
+ dest[c] = MipmapTraits<T>::mix (src[c], src[N + c]);
+
+ src += 2 * N;
+ dest += N;
+ }
+
+ src0 += src_stride;
+ dest0 += dest_stride;
+ }
+ });
+
+ return destination;
+ }
+
+ static GimpTempBuf *
+ downscale_vert (const GimpTempBuf *source)
+ {
+ GimpTempBuf *destination;
+ gint width = gimp_temp_buf_get_width (source);
+ gint height = gimp_temp_buf_get_height (source);
+
+ height /= 2;
+
+ destination = gimp_temp_buf_new (width, height,
+ gimp_temp_buf_get_format (source));
+
+ gegl_parallel_distribute_range (
+ width, PIXELS_PER_THREAD / height,
+ [=] (gint offset,
+ gint size)
+ {
+ const T *src0 = (const T *) gimp_temp_buf_get_data (source);
+ T *dest0 = (T *) gimp_temp_buf_get_data (destination);
+ gint src_stride = N * gimp_temp_buf_get_width (source);
+ gint dest_stride = N * gimp_temp_buf_get_width (destination);
+ gint x;
+
+ src0 += offset * N;
+ dest0 += offset * N;
+
+ for (x = 0; x < size; x++)
+ {
+ const T *src = src0;
+ T *dest = dest0;
+ gint y;
+
+ for (y = 0; y < height; y++)
+ {
+ gint c;
+
+ for (c = 0; c < N; c++)
+ dest[c] = MipmapTraits<T>::mix (src[c], src[src_stride + c]);
+
+ src += 2 * src_stride;
+ dest += dest_stride;
+ }
+
+ src0 += N;
+ dest0 += N;
+ }
+ });
+
+ return destination;
+ }
+};
+
+template <class Func>
+static GimpTempBuf *
+gimp_brush_mipmap_dispatch (const GimpTempBuf *source,
+ Func func)
+{
+ const Babl *format = gimp_temp_buf_get_format (source);
+ const Babl *type;
+ gint n_components;
+
+ type = babl_format_get_type (format, 0);
+ n_components = babl_format_get_n_components (format);
+
+ if (type == babl_type ("u8"))
+ {
+ switch (n_components)
+ {
+ case 1:
+ return func (MipmapAlgorithms<guint8, 1> ());
+
+ case 3:
+ return func (MipmapAlgorithms<guint8, 3> ());
+ }
+ }
+ else if (type == babl_type ("float"))
+ {
+ switch (n_components)
+ {
+ case 1:
+ return func (MipmapAlgorithms<gfloat, 1> ());
+
+ case 3:
+ return func (MipmapAlgorithms<gfloat, 3> ());
+ }
+ }
+
+ g_return_val_if_reached (NULL);
+}
+
+static GimpTempBuf *
+gimp_brush_mipmap_downscale (const GimpTempBuf *source)
+{
+ return gimp_brush_mipmap_dispatch (
+ source,
+ [&] (auto algorithms)
+ {
+ return algorithms.downscale (source);
+ });
+}
+
+static GimpTempBuf *
+gimp_brush_mipmap_downscale_horz (const GimpTempBuf *source)
+{
+ return gimp_brush_mipmap_dispatch (
+ source,
+ [&] (auto algorithms)
+ {
+ return algorithms.downscale_horz (source);
+ });
+}
+
+static GimpTempBuf *
+gimp_brush_mipmap_downscale_vert (const GimpTempBuf *source)
+{
+ return gimp_brush_mipmap_dispatch (
+ source,
+ [&] (auto algorithms)
+ {
+ return algorithms.downscale_vert (source);
+ });
+}
+
+
+/* public functions */
+
+void
+gimp_brush_mipmap_clear (GimpBrush *brush)
+{
+ gimp_brush_mipmap_clear (brush, &brush->priv->mask_mipmaps);
+ gimp_brush_mipmap_clear (brush, &brush->priv->pixmap_mipmaps);
+}
+
+const GimpTempBuf *
+gimp_brush_mipmap_get_mask (GimpBrush *brush,
+ gdouble *scale_x,
+ gdouble *scale_y)
+{
+ return gimp_brush_mipmap_get (brush,
+ brush->priv->mask,
+ &brush->priv->mask_mipmaps,
+ scale_x, scale_y);
+}
+
+const GimpTempBuf *
+gimp_brush_mipmap_get_pixmap (GimpBrush *brush,
+ gdouble *scale_x,
+ gdouble *scale_y)
+{
+ return gimp_brush_mipmap_get (brush,
+ brush->priv->pixmap,
+ &brush->priv->pixmap_mipmaps,
+ scale_x, scale_y);
+}
+
+gsize
+gimp_brush_mipmap_get_memsize (GimpBrush *brush)
+{
+ gsize memsize = 0;
+
+ if (brush->priv->mask_mipmaps)
+ {
+ gint i;
+
+ for (i = 1;
+ i < brush->priv->n_horz_mipmaps * brush->priv->n_vert_mipmaps;
+ i++)
+ {
+ memsize += gimp_temp_buf_get_memsize (brush->priv->mask_mipmaps[i]);
+ }
+ }
+
+ if (brush->priv->pixmap_mipmaps)
+ {
+ gint i;
+
+ for (i = 1;
+ i < brush->priv->n_horz_mipmaps * brush->priv->n_vert_mipmaps;
+ i++)
+ {
+ memsize += gimp_temp_buf_get_memsize (brush->priv->pixmap_mipmaps[i]);
+ }
+ }
+
+ return memsize;
+}
diff --git a/app/core/gimpbrush-mipmap.h b/app/core/gimpbrush-mipmap.h
new file mode 100644
index 0000000..b2b8fd3
--- /dev/null
+++ b/app/core/gimpbrush-mipmap.h
@@ -0,0 +1,38 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpbrush-mipmap.h
+ * Copyright (C) 2020 Ell
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_BRUSH_MIPMAP_H__
+#define __GIMP_BRUSH_MIPMAP_H__
+
+
+void gimp_brush_mipmap_clear (GimpBrush *brush);
+
+const GimpTempBuf * gimp_brush_mipmap_get_mask (GimpBrush *brush,
+ gdouble *scale_x,
+ gdouble *scale_y);
+
+const GimpTempBuf * gimp_brush_mipmap_get_pixmap (GimpBrush *brush,
+ gdouble *scale_x,
+ gdouble *scale_y);
+
+gsize gimp_brush_mipmap_get_memsize (GimpBrush *brush);
+
+
+#endif /* __GIMP_BRUSH_MIPMAP_H__ */
diff --git a/app/core/gimpbrush-private.h b/app/core/gimpbrush-private.h
new file mode 100644
index 0000000..c8d4d29
--- /dev/null
+++ b/app/core/gimpbrush-private.h
@@ -0,0 +1,47 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_BRUSH_PRIVATE_H__
+#define __GIMP_BRUSH_PRIVATE_H__
+
+
+struct _GimpBrushPrivate
+{
+ GimpTempBuf *mask; /* the actual mask */
+ GimpTempBuf *blurred_mask; /* blurred actual mask cached */
+ GimpTempBuf *pixmap; /* optional pixmap data */
+ GimpTempBuf *blurred_pixmap; /* optional pixmap data blurred cache */
+
+ gdouble blur_hardness;
+
+ gint n_horz_mipmaps;
+ gint n_vert_mipmaps;
+ GimpTempBuf **mask_mipmaps;
+ GimpTempBuf **pixmap_mipmaps;
+
+ gint spacing; /* brush's spacing */
+ GimpVector2 x_axis; /* for calculating brush spacing */
+ GimpVector2 y_axis; /* for calculating brush spacing */
+
+ gint use_count; /* for keeping the caches alive */
+ GimpBrushCache *mask_cache;
+ GimpBrushCache *pixmap_cache;
+ GimpBrushCache *boundary_cache;
+};
+
+
+#endif /* __GIMP_BRUSH_PRIVATE_H__ */
diff --git a/app/core/gimpbrush-save.c b/app/core/gimpbrush-save.c
new file mode 100644
index 0000000..38d4bcd
--- /dev/null
+++ b/app/core/gimpbrush-save.c
@@ -0,0 +1,107 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+#include "core-types.h"
+
+#include "gimpbrush.h"
+#include "gimpbrush-header.h"
+#include "gimpbrush-save.h"
+#include "gimptempbuf.h"
+
+
+gboolean
+gimp_brush_save (GimpData *data,
+ GOutputStream *output,
+ GError **error)
+{
+ GimpBrush *brush = GIMP_BRUSH (data);
+ GimpTempBuf *mask = gimp_brush_get_mask (brush);
+ GimpTempBuf *pixmap = gimp_brush_get_pixmap (brush);
+ GimpBrushHeader header;
+ const gchar *name;
+ gint width;
+ gint height;
+
+ name = gimp_object_get_name (brush);
+ width = gimp_temp_buf_get_width (mask);
+ height = gimp_temp_buf_get_height (mask);
+
+ header.header_size = g_htonl (sizeof (GimpBrushHeader) +
+ strlen (name) + 1);
+ header.version = g_htonl (2);
+ header.width = g_htonl (width);
+ header.height = g_htonl (height);
+ header.bytes = g_htonl (pixmap ? 4 : 1);
+ header.magic_number = g_htonl (GIMP_BRUSH_MAGIC);
+ header.spacing = g_htonl (gimp_brush_get_spacing (brush));
+
+ if (! g_output_stream_write_all (output, &header, sizeof (header),
+ NULL, NULL, error))
+ {
+ return FALSE;
+ }
+
+ if (! g_output_stream_write_all (output, name, strlen (name) + 1,
+ NULL, NULL, error))
+ {
+ return FALSE;
+ }
+
+ if (pixmap)
+ {
+ gsize size = width * height * 4;
+ guchar *data = g_malloc (size);
+ guchar *p = gimp_temp_buf_get_data (pixmap);
+ guchar *m = gimp_temp_buf_get_data (mask);
+ guchar *d = data;
+ gint i;
+
+ for (i = 0; i < width * height; i++)
+ {
+ *d++ = *p++;
+ *d++ = *p++;
+ *d++ = *p++;
+ *d++ = *m++;
+ }
+
+ if (! g_output_stream_write_all (output, data, size,
+ NULL, NULL, error))
+ {
+ g_free (data);
+
+ return FALSE;
+ }
+
+ g_free (data);
+ }
+ else
+ {
+ if (! g_output_stream_write_all (output,
+ gimp_temp_buf_get_data (mask),
+ gimp_temp_buf_get_data_size (mask),
+ NULL, NULL, error))
+ {
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
diff --git a/app/core/gimpbrush-save.h b/app/core/gimpbrush-save.h
new file mode 100644
index 0000000..f400cf0
--- /dev/null
+++ b/app/core/gimpbrush-save.h
@@ -0,0 +1,28 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_BRUSH_SAVE_H__
+#define __GIMP_BRUSH_SAVE_H__
+
+
+/* don't call this function directly, use gimp_data_save() instead */
+gboolean gimp_brush_save (GimpData *data,
+ GOutputStream *output,
+ GError **error);
+
+
+#endif /* __GIMP_BRUSH_SAVE_H__ */
diff --git a/app/core/gimpbrush-transform.cc b/app/core/gimpbrush-transform.cc
new file mode 100644
index 0000000..45673cd
--- /dev/null
+++ b/app/core/gimpbrush-transform.cc
@@ -0,0 +1,1043 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpbrush-transform.c
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpmath/gimpmath.h"
+
+extern "C"
+{
+
+#include "core-types.h"
+
+#include "gegl/gimp-gegl-loops.h"
+
+#include "gimpbrush.h"
+#include "gimpbrush-mipmap.h"
+#include "gimpbrush-transform.h"
+#include "gimptempbuf.h"
+
+
+#define PIXELS_PER_THREAD \
+ (/* each thread costs as much as */ 64.0 * 64.0 /* pixels */)
+
+
+/* local function prototypes */
+
+static void gimp_brush_transform_bounding_box (const GimpTempBuf *temp_buf,
+ const GimpMatrix3 *matrix,
+ gint *x,
+ gint *y,
+ gint *width,
+ gint *height);
+
+static void gimp_brush_transform_blur (GimpTempBuf *buf,
+ gint r);
+static gint gimp_brush_transform_blur_radius (gint height,
+ gint width,
+ gdouble hardness);
+static void gimp_brush_transform_adjust_hardness_matrix (gdouble width,
+ gdouble height,
+ gdouble blur_radius,
+ GimpMatrix3 *matrix);
+
+
+/* public functions */
+
+void
+gimp_brush_real_transform_size (GimpBrush *brush,
+ gdouble scale,
+ gdouble aspect_ratio,
+ gdouble angle,
+ gboolean reflect,
+ gint *width,
+ gint *height)
+{
+ const GimpTempBuf *source;
+ GimpMatrix3 matrix;
+ gdouble scale_x, scale_y;
+ gint x, y;
+
+ gimp_brush_transform_get_scale (scale, aspect_ratio,
+ &scale_x, &scale_y);
+
+ source = gimp_brush_mipmap_get_mask (brush, &scale_x, &scale_y);
+
+ gimp_brush_transform_matrix (gimp_temp_buf_get_width (source),
+ gimp_temp_buf_get_height (source),
+ scale_x, scale_y, angle, reflect, &matrix);
+
+ gimp_brush_transform_bounding_box (source, &matrix, &x, &y, width, height);
+}
+
+/*
+ * Transforms the brush mask with bilinear interpolation.
+ *
+ * Rather than calculating the inverse transform for each point in the
+ * transformed image, this algorithm uses the inverse transformed
+ * corner points of the destination image to work out the starting
+ * position in the source image and the U and V deltas in the source
+ * image space. It then uses a scan-line approach, looping through
+ * rows and columns in the transformed (destination) image while
+ * walking along the corresponding rows and columns (named U and V) in
+ * the source image.
+ *
+ * The horizontal in destination space (transform result) is reverse
+ * transformed into source image space to get U. The vertical in
+ * destination space (transform result) is reverse transformed into
+ * source image space to get V.
+ *
+ * The strength of this particular algorithm is that calculation work
+ * should depend more upon the final transformed brush size rather
+ * than the input brush size.
+ *
+ * There are no floating point calculations in the inner loop for speed.
+ *
+ * Some variables end with the suffix _i to indicate they have been
+ * premultiplied by int_multiple
+ */
+GimpTempBuf *
+gimp_brush_real_transform_mask (GimpBrush *brush,
+ gdouble scale,
+ gdouble aspect_ratio,
+ gdouble angle,
+ gboolean reflect,
+ gdouble hardness)
+{
+ GimpTempBuf *result;
+ const GimpTempBuf *source;
+ const guchar *src;
+ GimpMatrix3 matrix;
+ gdouble scale_x, scale_y;
+ gint src_width;
+ gint src_height;
+ gint src_width_minus_one;
+ gint src_height_minus_one;
+ gint dest_width;
+ gint dest_height;
+ gint blur_radius;
+ gint x, y;
+ gdouble b_lx, b_rx, t_lx, t_rx;
+ gdouble b_ly, b_ry, t_ly, t_ry;
+ gdouble src_tl_to_tr_delta_x;
+ gdouble src_tl_to_tr_delta_y;
+ gdouble src_tl_to_bl_delta_x;
+ gdouble src_tl_to_bl_delta_y;
+ gint src_walk_ux_i;
+ gint src_walk_uy_i;
+ gint src_walk_vx_i;
+ gint src_walk_vy_i;
+ gint src_x_min_i;
+ gint src_y_min_i;
+ gint src_x_max_i;
+ gint src_y_max_i;
+
+ /*
+ * tl, tr etc are used because it is easier to visualize top left,
+ * top right etc corners of the forward transformed source image
+ * rectangle.
+ */
+ const gint fraction_bits = 12;
+ const gint int_multiple = pow (2, fraction_bits);
+
+ /* In inner loop's bilinear calculation, two numbers that were each
+ * previously multiplied by int_multiple are multiplied together.
+ * To get back the right result, the multiplication result must be
+ * divided *twice* by 2^fraction_bits, equivalent to bit shift right
+ * by 2 * fraction_bits
+ */
+ const gint recovery_bits = 2 * fraction_bits;
+
+ /*
+ * example: suppose fraction_bits = 9
+ * a 9-bit mask looks like this: 0001 1111 1111
+ * and is given by: 2^fraction_bits - 1
+ * demonstration:
+ * 2^0 = 0000 0000 0001
+ * 2^1 = 0000 0000 0010
+ * :
+ * 2^8 = 0001 0000 0000
+ * 2^9 = 0010 0000 0000
+ * 2^9 - 1 = 0001 1111 1111
+ */
+ const guint fraction_bitmask = pow(2, fraction_bits) - 1 ;
+
+ gimp_brush_transform_get_scale (scale, aspect_ratio,
+ &scale_x, &scale_y);
+
+ source = gimp_brush_mipmap_get_mask (brush, &scale_x, &scale_y);
+
+ src_width = gimp_temp_buf_get_width (source);
+ src_height = gimp_temp_buf_get_height (source);
+
+ gimp_brush_transform_matrix (src_width, src_height,
+ scale_x, scale_y, angle, reflect, &matrix);
+
+ if (gimp_matrix3_is_identity (&matrix) && hardness == 1.0)
+ return gimp_temp_buf_copy (source);
+
+ src_width_minus_one = src_width - 1;
+ src_height_minus_one = src_height - 1;
+
+ gimp_brush_transform_bounding_box (source, &matrix,
+ &x, &y, &dest_width, &dest_height);
+
+ blur_radius = 0;
+
+ if (hardness < 1.0)
+ {
+ GimpMatrix3 unrotated_matrix;
+ gint unrotated_x;
+ gint unrotated_y;
+ gint unrotated_dest_width;
+ gint unrotated_dest_height;
+
+ gimp_brush_transform_matrix (src_width, src_height,
+ scale_x, scale_y, 0.0, FALSE,
+ &unrotated_matrix);
+
+ gimp_brush_transform_bounding_box (source, &unrotated_matrix,
+ &unrotated_x, &unrotated_y,
+ &unrotated_dest_width,
+ &unrotated_dest_height);
+
+ blur_radius = gimp_brush_transform_blur_radius (unrotated_dest_width,
+ unrotated_dest_height,
+ hardness);
+
+ gimp_brush_transform_adjust_hardness_matrix (dest_width, dest_height,
+ blur_radius, &matrix);
+ }
+
+ gimp_matrix3_translate (&matrix, -x, -y);
+ gimp_matrix3_invert (&matrix);
+ gimp_matrix3_translate (&matrix, -0.5, -0.5);
+
+ result = gimp_temp_buf_new (dest_width, dest_height,
+ gimp_temp_buf_get_format (source));
+
+ src = gimp_temp_buf_get_data (source);
+
+ /* prevent disappearance of 1x1 pixel brush at some rotations when
+ scaling < 1 */
+ /*
+ if (src_width == 1 && src_height == 1 && scale_x < 1 && scale_y < 1 )
+ {
+ *dest = src[0];
+ return result;
+ }*/
+
+ gimp_matrix3_transform_point (&matrix,
+ 0.5, 0.5,
+ &t_lx, &t_ly);
+ gimp_matrix3_transform_point (&matrix,
+ dest_width - 0.5, 0.5,
+ &t_rx, &t_ry);
+ gimp_matrix3_transform_point (&matrix,
+ 0.5, dest_height - 0.5,
+ &b_lx, &b_ly);
+ gimp_matrix3_transform_point (&matrix,
+ dest_width - 0.5, dest_height - 0.5,
+ &b_rx, &b_ry);
+
+ /* in image space, calc U (what was horizontal originally)
+ * note: double precision
+ */
+ src_tl_to_tr_delta_x = t_rx - t_lx;
+ src_tl_to_tr_delta_y = t_ry - t_ly;
+
+ /* in image space, calc V (what was vertical originally)
+ * note: double precision
+ */
+ src_tl_to_bl_delta_x = b_lx - t_lx;
+ src_tl_to_bl_delta_y = b_ly - t_ly;
+
+ /* speed optimized, note conversion to int precision */
+ src_walk_ux_i = (gint) ((src_tl_to_tr_delta_x / MAX (dest_width - 1, 1)) *
+ int_multiple);
+ src_walk_uy_i = (gint) ((src_tl_to_tr_delta_y / MAX (dest_width - 1, 1)) *
+ int_multiple);
+ src_walk_vx_i = (gint) ((src_tl_to_bl_delta_x / MAX (dest_height - 1, 1)) *
+ int_multiple);
+ src_walk_vy_i = (gint) ((src_tl_to_bl_delta_y / MAX (dest_height - 1, 1)) *
+ int_multiple);
+
+ src_x_min_i = -int_multiple / 2;
+ src_y_min_i = -int_multiple / 2;
+
+ src_x_max_i = src_width * int_multiple - int_multiple / 2;
+ src_y_max_i = src_height * int_multiple - int_multiple / 2;
+
+ gegl_parallel_distribute_area (
+ GEGL_RECTANGLE (0, 0, dest_width, dest_height), PIXELS_PER_THREAD,
+ [=] (const GeglRectangle *area)
+ {
+ guchar *dest;
+ gint src_space_cur_pos_x;
+ gint src_space_cur_pos_y;
+ gint src_space_cur_pos_x_i;
+ gint src_space_cur_pos_y_i;
+ gint src_space_row_start_x_i;
+ gint src_space_row_start_y_i;
+ const guchar *src_walker;
+ const guchar *pixel_next;
+ const guchar *pixel_below;
+ const guchar *pixel_below_next;
+ gint opposite_x, distance_from_true_x;
+ gint opposite_y, distance_from_true_y;
+ gint u, v;
+
+ dest = gimp_temp_buf_get_data (result) +
+ dest_width * area->y + area->x;
+
+ /* initialize current position in source space to the start position (tl)
+ * speed optimized, note conversion to int precision
+ */
+ src_space_row_start_x_i = (gint) (t_lx * int_multiple) +
+ src_walk_vx_i * area->y +
+ src_walk_ux_i * area->x;
+ src_space_row_start_y_i = (gint) (t_ly * int_multiple) +
+ src_walk_vy_i * area->y +
+ src_walk_uy_i * area->x;
+
+ for (v = 0; v < area->height; v++)
+ {
+ src_space_cur_pos_x_i = src_space_row_start_x_i;
+ src_space_cur_pos_y_i = src_space_row_start_y_i;
+
+ for (u = 0; u < area->width; u++)
+ {
+ if (src_space_cur_pos_x_i < src_x_min_i ||
+ src_space_cur_pos_x_i >= src_x_max_i ||
+ src_space_cur_pos_y_i < src_y_min_i ||
+ src_space_cur_pos_y_i >= src_y_max_i)
+ /* no corresponding pixel in source space */
+ {
+ *dest = 0;
+ }
+ else /* reverse transformed point hits source pixel */
+ {
+ src_space_cur_pos_x = src_space_cur_pos_x_i >> fraction_bits;
+ src_space_cur_pos_y = src_space_cur_pos_y_i >> fraction_bits;
+
+ src_walker = src +
+ src_space_cur_pos_y * src_width +
+ src_space_cur_pos_x;
+
+ pixel_next = src_walker + 1;
+ pixel_below = src_walker + src_width;
+ pixel_below_next = pixel_below + 1;
+
+ if (src_space_cur_pos_x < 0)
+ {
+ src_walker = pixel_next;
+ pixel_below = pixel_below_next;
+ }
+ else if (src_space_cur_pos_x >= src_width_minus_one)
+ {
+ pixel_next = src_walker;
+ pixel_below_next = pixel_below;
+ }
+
+ if (src_space_cur_pos_y < 0)
+ {
+ src_walker = pixel_below;
+ pixel_next = pixel_below_next;
+ }
+ else if (src_space_cur_pos_y >= src_height_minus_one)
+ {
+ pixel_below = src_walker;
+ pixel_below_next = pixel_next;
+ }
+
+ distance_from_true_x = src_space_cur_pos_x_i & fraction_bitmask;
+ distance_from_true_y = src_space_cur_pos_y_i & fraction_bitmask;
+ opposite_x = int_multiple - distance_from_true_x;
+ opposite_y = int_multiple - distance_from_true_y;
+
+ *dest = ((src_walker[0] * opposite_x +
+ pixel_next[0] * distance_from_true_x) * opposite_y +
+ (pixel_below[0] * opposite_x +
+ pixel_below_next[0] *distance_from_true_x) * distance_from_true_y
+ ) >> recovery_bits;
+ }
+
+ src_space_cur_pos_x_i += src_walk_ux_i;
+ src_space_cur_pos_y_i += src_walk_uy_i;
+
+ dest++;
+ } /* end for x */
+
+ src_space_row_start_x_i += src_walk_vx_i;
+ src_space_row_start_y_i += src_walk_vy_i;
+
+ dest += dest_width - area->width;
+ } /* end for y */
+ });
+
+ gimp_brush_transform_blur (result, blur_radius);
+
+ return result;
+}
+
+/*
+ * Transforms the brush pixmap with bilinear interpolation.
+ *
+ * The algorithm used is exactly the same as for the brush mask
+ * (gimp_brush_real_transform_mask) except it accounts for 3 color channels
+ * instead of 1 grayscale channel.
+ *
+ * Rather than calculating the inverse transform for each point in the
+ * transformed image, this algorithm uses the inverse transformed
+ * corner points of the destination image to work out the starting
+ * position in the source image and the U and V deltas in the source
+ * image space. It then uses a scan-line approach, looping through
+ * rows and columns in the transformed (destination) image while
+ * walking along the corresponding rows and columns (named U and V) in
+ * the source image.
+ *
+ * The horizontal in destination space (transform result) is reverse
+ * transformed into source image space to get U. The vertical in
+ * destination space (transform result) is reverse transformed into
+ * source image space to get V.
+ *
+ * The strength of this particular algorithm is that calculation work
+ * should depend more upon the final transformed brush size rather
+ * than the input brush size.
+ *
+ * There are no floating point calculations in the inner loop for speed.
+ *
+ * Some variables end with the suffix _i to indicate they have been
+ * premultiplied by int_multiple
+ */
+GimpTempBuf *
+gimp_brush_real_transform_pixmap (GimpBrush *brush,
+ gdouble scale,
+ gdouble aspect_ratio,
+ gdouble angle,
+ gboolean reflect,
+ gdouble hardness)
+{
+ GimpTempBuf *result;
+ const GimpTempBuf *source;
+ const guchar *src;
+ GimpMatrix3 matrix;
+ gdouble scale_x, scale_y;
+ gint src_width;
+ gint src_height;
+ gint src_width_minus_one;
+ gint src_height_minus_one;
+ gint dest_width;
+ gint dest_height;
+ gint blur_radius;
+ gint x, y;
+ gdouble b_lx, b_rx, t_lx, t_rx;
+ gdouble b_ly, b_ry, t_ly, t_ry;
+ gdouble src_tl_to_tr_delta_x;
+ gdouble src_tl_to_tr_delta_y;
+ gdouble src_tl_to_bl_delta_x;
+ gdouble src_tl_to_bl_delta_y;
+ gint src_walk_ux_i;
+ gint src_walk_uy_i;
+ gint src_walk_vx_i;
+ gint src_walk_vy_i;
+ gint src_x_min_i;
+ gint src_y_min_i;
+ gint src_x_max_i;
+ gint src_y_max_i;
+
+ /*
+ * tl, tr etc are used because it is easier to visualize top left,
+ * top right etc corners of the forward transformed source image
+ * rectangle.
+ */
+ const gint fraction_bits = 12;
+ const gint int_multiple = pow (2, fraction_bits);
+
+ /* In inner loop's bilinear calculation, two numbers that were each
+ * previously multiplied by int_multiple are multiplied together.
+ * To get back the right result, the multiplication result must be
+ * divided *twice* by 2^fraction_bits, equivalent to bit shift right
+ * by 2 * fraction_bits
+ */
+ const gint recovery_bits = 2 * fraction_bits;
+
+ /*
+ * example: suppose fraction_bits = 9
+ * a 9-bit mask looks like this: 0001 1111 1111
+ * and is given by: 2^fraction_bits - 1
+ * demonstration:
+ * 2^0 = 0000 0000 0001
+ * 2^1 = 0000 0000 0010
+ * :
+ * 2^8 = 0001 0000 0000
+ * 2^9 = 0010 0000 0000
+ * 2^9 - 1 = 0001 1111 1111
+ */
+ const guint fraction_bitmask = pow(2, fraction_bits) - 1 ;
+
+ gimp_brush_transform_get_scale (scale, aspect_ratio,
+ &scale_x, &scale_y);
+
+ source = gimp_brush_mipmap_get_pixmap (brush, &scale_x, &scale_y);
+
+ src_width = gimp_temp_buf_get_width (source);
+ src_height = gimp_temp_buf_get_height (source);
+
+ gimp_brush_transform_matrix (src_width, src_height,
+ scale_x, scale_y, angle, reflect, &matrix);
+
+ if (gimp_matrix3_is_identity (&matrix) && hardness == 1.0)
+ return gimp_temp_buf_copy (source);
+
+ src_width_minus_one = src_width - 1;
+ src_height_minus_one = src_height - 1;
+
+ gimp_brush_transform_bounding_box (source, &matrix,
+ &x, &y, &dest_width, &dest_height);
+
+ blur_radius = 0;
+
+ if (hardness < 1.0)
+ {
+ GimpMatrix3 unrotated_matrix;
+ gint unrotated_x;
+ gint unrotated_y;
+ gint unrotated_dest_width;
+ gint unrotated_dest_height;
+
+ gimp_brush_transform_matrix (src_width, src_height,
+ scale_x, scale_y, 0.0, FALSE,
+ &unrotated_matrix);
+
+ gimp_brush_transform_bounding_box (source, &unrotated_matrix,
+ &unrotated_x, &unrotated_y,
+ &unrotated_dest_width,
+ &unrotated_dest_height);
+
+ blur_radius = gimp_brush_transform_blur_radius (unrotated_dest_width,
+ unrotated_dest_height,
+ hardness);
+
+ gimp_brush_transform_adjust_hardness_matrix (dest_width, dest_height,
+ blur_radius, &matrix);
+ }
+
+ gimp_matrix3_translate (&matrix, -x, -y);
+ gimp_matrix3_invert (&matrix);
+ gimp_matrix3_translate (&matrix, -0.5, -0.5);
+
+ result = gimp_temp_buf_new (dest_width, dest_height,
+ gimp_temp_buf_get_format (source));
+
+ src = gimp_temp_buf_get_data (source);
+
+ /* prevent disappearance of 1x1 pixel brush at some rotations when
+ scaling < 1 */
+ /*
+ if (src_width == 1 && src_height == 1 && scale_x < 1 && scale_y < 1 )
+ {
+ *dest = src[0];
+ return result;
+ }*/
+
+ gimp_matrix3_transform_point (&matrix,
+ 0.5, 0.5,
+ &t_lx, &t_ly);
+ gimp_matrix3_transform_point (&matrix,
+ dest_width - 0.5, 0.5,
+ &t_rx, &t_ry);
+ gimp_matrix3_transform_point (&matrix,
+ 0.5, dest_height - 0.5,
+ &b_lx, &b_ly);
+ gimp_matrix3_transform_point (&matrix,
+ dest_width - 0.5, dest_height - 0.5,
+ &b_rx, &b_ry);
+
+ /* in image space, calc U (what was horizontal originally)
+ * note: double precision
+ */
+ src_tl_to_tr_delta_x = t_rx - t_lx;
+ src_tl_to_tr_delta_y = t_ry - t_ly;
+
+ /* in image space, calc V (what was vertical originally)
+ * note: double precision
+ */
+ src_tl_to_bl_delta_x = b_lx - t_lx;
+ src_tl_to_bl_delta_y = b_ly - t_ly;
+
+ /* speed optimized, note conversion to int precision */
+ src_walk_ux_i = (gint) ((src_tl_to_tr_delta_x / MAX (dest_width - 1, 1)) *
+ int_multiple);
+ src_walk_uy_i = (gint) ((src_tl_to_tr_delta_y / MAX (dest_width - 1, 1)) *
+ int_multiple);
+ src_walk_vx_i = (gint) ((src_tl_to_bl_delta_x / MAX (dest_height - 1, 1)) *
+ int_multiple);
+ src_walk_vy_i = (gint) ((src_tl_to_bl_delta_y / MAX (dest_height - 1, 1)) *
+ int_multiple);
+
+ src_x_min_i = -int_multiple / 2;
+ src_y_min_i = -int_multiple / 2;
+
+ src_x_max_i = src_width * int_multiple - int_multiple / 2;
+ src_y_max_i = src_height * int_multiple - int_multiple / 2;
+
+ gegl_parallel_distribute_area (
+ GEGL_RECTANGLE (0, 0, dest_width, dest_height), PIXELS_PER_THREAD,
+ [=] (const GeglRectangle *area)
+ {
+ guchar *dest;
+ gint src_space_cur_pos_x;
+ gint src_space_cur_pos_y;
+ gint src_space_cur_pos_x_i;
+ gint src_space_cur_pos_y_i;
+ gint src_space_row_start_x_i;
+ gint src_space_row_start_y_i;
+ const guchar *src_walker;
+ const guchar *pixel_next;
+ const guchar *pixel_below;
+ const guchar *pixel_below_next;
+ gint opposite_x, distance_from_true_x;
+ gint opposite_y, distance_from_true_y;
+ gint u, v;
+
+ dest = gimp_temp_buf_get_data (result) +
+ 3 * (dest_width * area->y + area->x);
+
+ /* initialize current position in source space to the start position (tl)
+ * speed optimized, note conversion to int precision
+ */
+ src_space_row_start_x_i = (gint) (t_lx * int_multiple) +
+ src_walk_vx_i * area->y +
+ src_walk_ux_i * area->x;
+ src_space_row_start_y_i = (gint) (t_ly * int_multiple) +
+ src_walk_vy_i * area->y +
+ src_walk_uy_i * area->x;
+
+ for (v = 0; v < area->height; v++)
+ {
+ src_space_cur_pos_x_i = src_space_row_start_x_i;
+ src_space_cur_pos_y_i = src_space_row_start_y_i;
+
+ for (u = 0; u < area->width; u++)
+ {
+ if (src_space_cur_pos_x_i < src_x_min_i ||
+ src_space_cur_pos_x_i >= src_x_max_i ||
+ src_space_cur_pos_y_i < src_y_min_i ||
+ src_space_cur_pos_y_i >= src_y_max_i)
+ /* no corresponding pixel in source space */
+ {
+ dest[0] = 0;
+ dest[1] = 0;
+ dest[2] = 0;
+ }
+ else /* reverse transformed point hits source pixel */
+ {
+ src_space_cur_pos_x = src_space_cur_pos_x_i >> fraction_bits;
+ src_space_cur_pos_y = src_space_cur_pos_y_i >> fraction_bits;
+
+ src_walker = src +
+ 3 * (src_space_cur_pos_y * src_width +
+ src_space_cur_pos_x);
+
+ pixel_next = src_walker + 3;
+ pixel_below = src_walker + 3 * src_width;
+ pixel_below_next = pixel_below + 3;
+
+ if (src_space_cur_pos_x < 0)
+ {
+ src_walker = pixel_next;
+ pixel_below = pixel_below_next;
+ }
+ else if (src_space_cur_pos_x >= src_width_minus_one)
+ {
+ pixel_next = src_walker;
+ pixel_below_next = pixel_below;
+ }
+
+ if (src_space_cur_pos_y < 0)
+ {
+ src_walker = pixel_below;
+ pixel_next = pixel_below_next;
+ }
+ else if (src_space_cur_pos_y >= src_height_minus_one)
+ {
+ pixel_below = src_walker;
+ pixel_below_next = pixel_next;
+ }
+
+ distance_from_true_x = src_space_cur_pos_x_i & fraction_bitmask;
+ distance_from_true_y = src_space_cur_pos_y_i & fraction_bitmask;
+ opposite_x = int_multiple - distance_from_true_x;
+ opposite_y = int_multiple - distance_from_true_y;
+
+ dest[0] = ((src_walker[0] * opposite_x +
+ pixel_next[0] * distance_from_true_x) * opposite_y +
+ (pixel_below[0] * opposite_x +
+ pixel_below_next[0] *distance_from_true_x) * distance_from_true_y
+ ) >> recovery_bits;
+
+ dest[1] = ((src_walker[1] * opposite_x +
+ pixel_next[1] * distance_from_true_x) * opposite_y +
+ (pixel_below[1] * opposite_x +
+ pixel_below_next[1] *distance_from_true_x) * distance_from_true_y
+ ) >> recovery_bits;
+
+ dest[2] = ((src_walker[2] * opposite_x +
+ pixel_next[2] * distance_from_true_x) * opposite_y +
+ (pixel_below[2] * opposite_x +
+ pixel_below_next[2] *distance_from_true_x) * distance_from_true_y
+ ) >> recovery_bits;
+ }
+
+ src_space_cur_pos_x_i += src_walk_ux_i;
+ src_space_cur_pos_y_i += src_walk_uy_i;
+
+ dest += 3;
+ } /* end for x */
+
+ src_space_row_start_x_i += src_walk_vx_i;
+ src_space_row_start_y_i += src_walk_vy_i;
+
+ dest += 3 * (dest_width - area->width);
+ } /* end for y */
+ });
+
+ gimp_brush_transform_blur (result, blur_radius);
+
+ return result;
+}
+
+void
+gimp_brush_transform_get_scale (gdouble scale,
+ gdouble aspect_ratio,
+ gdouble *scale_x,
+ gdouble *scale_y)
+{
+ if (aspect_ratio < 0.0)
+ {
+ *scale_x = scale * (1.0 + (aspect_ratio / 20.0));
+ *scale_y = scale;
+ }
+ else
+ {
+ *scale_x = scale;
+ *scale_y = scale * (1.0 - (aspect_ratio / 20.0));
+ }
+}
+
+void
+gimp_brush_transform_matrix (gdouble width,
+ gdouble height,
+ gdouble scale_x,
+ gdouble scale_y,
+ gdouble angle,
+ gboolean reflect,
+ GimpMatrix3 *matrix)
+{
+ const gdouble center_x = width / 2;
+ const gdouble center_y = height / 2;
+
+ gimp_matrix3_identity (matrix);
+ gimp_matrix3_scale (matrix, scale_x, scale_y);
+ gimp_matrix3_translate (matrix, - center_x * scale_x, - center_y * scale_y);
+ gimp_matrix3_rotate (matrix, -2 * G_PI * angle);
+ if (reflect)
+ gimp_matrix3_scale (matrix, -1.0, 1.0);
+ gimp_matrix3_translate (matrix, center_x * scale_x, center_y * scale_y);
+}
+
+/* private functions */
+
+static void
+gimp_brush_transform_bounding_box (const GimpTempBuf *temp_buf,
+ const GimpMatrix3 *matrix,
+ gint *x,
+ gint *y,
+ gint *width,
+ gint *height)
+{
+ const gdouble w = gimp_temp_buf_get_width (temp_buf);
+ const gdouble h = gimp_temp_buf_get_height (temp_buf);
+ gdouble x1, x2, x3, x4;
+ gdouble y1, y2, y3, y4;
+
+ gimp_matrix3_transform_point (matrix, 0, 0, &x1, &y1);
+ gimp_matrix3_transform_point (matrix, w, 0, &x2, &y2);
+ gimp_matrix3_transform_point (matrix, 0, h, &x3, &y3);
+ gimp_matrix3_transform_point (matrix, w, h, &x4, &y4);
+
+ *x = (gint) ceil (MIN (MIN (x1, x2), MIN (x3, x4)) - 0.5);
+ *y = (gint) ceil (MIN (MIN (y1, y2), MIN (y3, y4)) - 0.5);
+
+ *width = (gint) ceil (MAX (MAX (x1, x2), MAX (x3, x4)) - 0.5) - *x;
+ *height = (gint) ceil (MAX (MAX (y1, y2), MAX (y3, y4)) - 0.5) - *y;
+
+ /* Transform size can not be less than 1 px */
+ *width = MAX (1, *width);
+ *height = MAX (1, *height);
+}
+
+/* Blurs the brush mask/pixmap, in place, using a convolution of the form:
+ *
+ * 12 11 10 9 8
+ * 7 6 5 4 3
+ * 2 1 0 1 2
+ * 3 4 5 6 7
+ * 8 9 10 11 12
+ *
+ * (i.e., an array, wrapped into a matrix, whose i-th element is
+ * `abs (i - a / 2)`, where `a` is the length of the array.) `r` specifies the
+ * convolution kernel's radius.
+ */
+static void
+gimp_brush_transform_blur (GimpTempBuf *buf,
+ gint r)
+{
+ typedef struct
+ {
+ gint sum;
+ gint weighted_sum;
+ gint middle_sum;
+ } Sums;
+
+ const Babl *format = gimp_temp_buf_get_format (buf);
+ gint components = babl_format_get_n_components (format);
+ gint components_r = components * r;
+ gint width = gimp_temp_buf_get_width (buf);
+ gint height = gimp_temp_buf_get_height (buf);
+ gint stride = components * width;
+ gint stride_r = stride * r;
+ guchar *data = gimp_temp_buf_get_data (buf);
+ gint rw = MIN (r, width - 1);
+ gint rh = MIN (r, height - 1);
+ gfloat n = 2 * r + 1;
+ gfloat n_r = n * r;
+ gfloat weight = floor (n * n / 2) * (floor (n * n / 2) + 1);
+ gfloat weight_inv = 1 / weight;
+ Sums *sums;
+
+ if (rw <= 0 || rh <= 0)
+ return;
+
+ sums = g_new (Sums, width * height * components);
+
+ gegl_parallel_distribute_range (
+ height, PIXELS_PER_THREAD / width,
+ [=] (gint y0, gint height)
+ {
+ gint x;
+ gint y;
+ gint c;
+ const guchar *d;
+ Sums *s;
+
+ d = data + y0 * stride;
+ s = sums + y0 * stride;
+
+ for (y = 0; y < height; y++)
+ {
+ const guchar *p;
+
+ struct
+ {
+ gint sum;
+ gint weighted_sum;
+ gint leading_sum;
+ gint leading_weighted_sum;
+ } acc[components];
+
+ memset (acc, 0, sizeof (acc));
+
+ p = d;
+
+ for (x = 0; x <= rw; x++)
+ {
+ for (c = 0; c < components; c++)
+ {
+ acc[c].sum += *p;
+ acc[c].weighted_sum += -x * *p;
+
+ p++;
+ }
+ }
+
+ for (x = 0; x < width; x++)
+ {
+ for (c = 0; c < components; c++)
+ {
+ if (x > 0)
+ {
+ acc[c].weighted_sum += acc[c].sum;
+ acc[c].leading_weighted_sum += acc[c].leading_sum;
+
+ if (x < width - r)
+ {
+ acc[c].sum += d[components_r];
+ acc[c].weighted_sum += -r * d[components_r];
+ }
+ }
+
+ acc[c].leading_sum += d[0];
+
+ s->sum = acc[c].sum;
+ s->weighted_sum = acc[c].weighted_sum;
+ s->middle_sum = 2 * acc[c].leading_weighted_sum -
+ acc[c].weighted_sum;
+
+ if (x >= r)
+ {
+ acc[c].sum -= d[-components_r];
+ acc[c].weighted_sum -= r * d[-components_r];
+ acc[c].leading_sum -= d[-components_r];
+ acc[c].leading_weighted_sum -= r * d[-components_r];
+ }
+
+ d++;
+ s++;
+ }
+ }
+ }
+ });
+
+ gegl_parallel_distribute_range (
+ width, PIXELS_PER_THREAD / height,
+ [=] (gint x0, gint width)
+ {
+ gint x;
+ gint y;
+ gint c;
+ guchar *d0;
+ const Sums *s0;
+ guchar *d;
+ const Sums *s;
+
+ d0 = data + x0 * components;
+ s0 = sums + x0 * components;
+
+ for (x = 0; x < width; x++)
+ {
+ const Sums *p;
+ gfloat n_y;
+
+ struct
+ {
+ gfloat weighted_sum;
+ gint leading_sum;
+ gint trailing_sum;
+ } acc[components];
+
+ memset (acc, 0, sizeof (acc));
+
+ d = d0 + components * x;
+ s = s0 + components * x;
+
+ p = s + stride;
+
+ for (y = 1, n_y = n; y <= rh; y++, n_y += n)
+ {
+ for (c = 0; c < components; c++)
+ {
+ acc[c].weighted_sum += n_y * p->sum - p->weighted_sum;
+ acc[c].trailing_sum += p->sum;
+
+ p++;
+ }
+
+ p += stride - components;
+ }
+
+ for (y = 0; y < height; y++)
+ {
+ for (c = 0; c < components; c++)
+ {
+ if (y > 0)
+ {
+ acc[c].weighted_sum += s->weighted_sum +
+ n * (acc[c].leading_sum -
+ acc[c].trailing_sum);
+ acc[c].trailing_sum -= s->sum;
+
+ if (y < height - r)
+ {
+ acc[c].weighted_sum += n_r * s[stride_r].sum -
+ s[stride_r].weighted_sum;
+ acc[c].trailing_sum += s[stride_r].sum;
+ }
+ }
+
+ acc[c].leading_sum += s->sum;
+
+ *d = (acc[c].weighted_sum + s->middle_sum) * weight_inv + 0.5f;
+
+ acc[c].weighted_sum += s->weighted_sum;
+
+ if (y >= r)
+ {
+ acc[c].weighted_sum -= n_r * s[-stride_r].sum +
+ s[-stride_r].weighted_sum;
+ acc[c].leading_sum -= s[-stride_r].sum;
+ }
+
+ d++;
+ s++;
+ }
+
+ d += stride - components;
+ s += stride - components;
+ }
+ }
+ });
+
+ g_free (sums);
+}
+
+static gint
+gimp_brush_transform_blur_radius (gint height,
+ gint width,
+ gdouble hardness)
+{
+ return floor ((1.0 - hardness) * (sqrt (0.5) - 0.5) * MIN (width, height));
+}
+
+static void
+gimp_brush_transform_adjust_hardness_matrix (gdouble width,
+ gdouble height,
+ gdouble blur_radius,
+ GimpMatrix3 *matrix)
+{
+ gdouble scale;
+
+ if (blur_radius == 0.0)
+ return;
+
+ scale = (MIN (width, height) - 2.0 * blur_radius) / MIN (width, height);
+
+ gimp_matrix3_scale (matrix, scale, scale);
+ gimp_matrix3_translate (matrix,
+ (1.0 - scale) * width / 2.0,
+ (1.0 - scale) * height / 2.0);
+}
+
+} /* extern "C" */
diff --git a/app/core/gimpbrush-transform.h b/app/core/gimpbrush-transform.h
new file mode 100644
index 0000000..5f7cbcb
--- /dev/null
+++ b/app/core/gimpbrush-transform.h
@@ -0,0 +1,59 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpbrush-transform.h
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_BRUSH_TRANSFORM_H__
+#define __GIMP_BRUSH_TRANSFORM_H__
+
+
+/* virtual functions of GimpBrush, don't call directly */
+
+void gimp_brush_real_transform_size (GimpBrush *brush,
+ gdouble scale,
+ gdouble aspect_ratio,
+ gdouble angle,
+ gboolean reflect,
+ gint *scaled_width,
+ gint *scaled_height);
+GimpTempBuf * gimp_brush_real_transform_mask (GimpBrush *brush,
+ gdouble scale,
+ gdouble aspect_ratio,
+ gdouble angle,
+ gboolean reflect,
+ gdouble hardness);
+GimpTempBuf * gimp_brush_real_transform_pixmap (GimpBrush *brush,
+ gdouble scale,
+ gdouble aspect_ratio,
+ gdouble angle,
+ gboolean reflect,
+ gdouble hardness);
+
+void gimp_brush_transform_get_scale (gdouble scale,
+ gdouble aspect_ratio,
+ gdouble *scale_x,
+ gdouble *scale_y);
+void gimp_brush_transform_matrix (gdouble width,
+ gdouble height,
+ gdouble scale_x,
+ gdouble scale_y,
+ gdouble angle,
+ gboolean reflect,
+ GimpMatrix3 *matrix);
+
+
+#endif /* __GIMP_BRUSH_TRANSFORM_H__ */
diff --git a/app/core/gimpbrush.c b/app/core/gimpbrush.c
new file mode 100644
index 0000000..8e15115
--- /dev/null
+++ b/app/core/gimpbrush.c
@@ -0,0 +1,942 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <cairo.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpbase/gimpbase.h"
+#include "libgimpmath/gimpmath.h"
+
+#include "core-types.h"
+
+#include "gimpbezierdesc.h"
+#include "gimpbrush.h"
+#include "gimpbrush-boundary.h"
+#include "gimpbrush-load.h"
+#include "gimpbrush-mipmap.h"
+#include "gimpbrush-private.h"
+#include "gimpbrush-save.h"
+#include "gimpbrush-transform.h"
+#include "gimpbrushcache.h"
+#include "gimpbrushgenerated.h"
+#include "gimpbrushpipe.h"
+#include "gimpmarshal.h"
+#include "gimptagged.h"
+#include "gimptempbuf.h"
+
+#include "gimp-intl.h"
+
+
+enum
+{
+ SPACING_CHANGED,
+ LAST_SIGNAL
+};
+
+enum
+{
+ PROP_0,
+ PROP_SPACING
+};
+
+
+static void gimp_brush_tagged_iface_init (GimpTaggedInterface *iface);
+
+static void gimp_brush_finalize (GObject *object);
+static void gimp_brush_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_brush_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static gint64 gimp_brush_get_memsize (GimpObject *object,
+ gint64 *gui_size);
+
+static gboolean gimp_brush_get_size (GimpViewable *viewable,
+ gint *width,
+ gint *height);
+static GimpTempBuf * gimp_brush_get_new_preview (GimpViewable *viewable,
+ GimpContext *context,
+ gint width,
+ gint height);
+static gchar * gimp_brush_get_description (GimpViewable *viewable,
+ gchar **tooltip);
+
+static void gimp_brush_dirty (GimpData *data);
+static const gchar * gimp_brush_get_extension (GimpData *data);
+static void gimp_brush_copy (GimpData *data,
+ GimpData *src_data);
+
+static void gimp_brush_real_begin_use (GimpBrush *brush);
+static void gimp_brush_real_end_use (GimpBrush *brush);
+static GimpBrush * gimp_brush_real_select_brush (GimpBrush *brush,
+ const GimpCoords *last_coords,
+ const GimpCoords *current_coords);
+static gboolean gimp_brush_real_want_null_motion (GimpBrush *brush,
+ const GimpCoords *last_coords,
+ const GimpCoords *current_coords);
+
+static gchar * gimp_brush_get_checksum (GimpTagged *tagged);
+
+
+G_DEFINE_TYPE_WITH_CODE (GimpBrush, gimp_brush, GIMP_TYPE_DATA,
+ G_ADD_PRIVATE (GimpBrush)
+ G_IMPLEMENT_INTERFACE (GIMP_TYPE_TAGGED,
+ gimp_brush_tagged_iface_init))
+
+#define parent_class gimp_brush_parent_class
+
+static guint brush_signals[LAST_SIGNAL] = { 0 };
+
+
+static void
+gimp_brush_class_init (GimpBrushClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpObjectClass *gimp_object_class = GIMP_OBJECT_CLASS (klass);
+ GimpViewableClass *viewable_class = GIMP_VIEWABLE_CLASS (klass);
+ GimpDataClass *data_class = GIMP_DATA_CLASS (klass);
+
+ brush_signals[SPACING_CHANGED] =
+ g_signal_new ("spacing-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpBrushClass, spacing_changed),
+ NULL, NULL,
+ gimp_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ object_class->finalize = gimp_brush_finalize;
+ object_class->get_property = gimp_brush_get_property;
+ object_class->set_property = gimp_brush_set_property;
+
+ gimp_object_class->get_memsize = gimp_brush_get_memsize;
+
+ viewable_class->default_icon_name = "gimp-tool-paintbrush";
+ viewable_class->get_size = gimp_brush_get_size;
+ viewable_class->get_new_preview = gimp_brush_get_new_preview;
+ viewable_class->get_description = gimp_brush_get_description;
+
+ data_class->dirty = gimp_brush_dirty;
+ data_class->save = gimp_brush_save;
+ data_class->get_extension = gimp_brush_get_extension;
+ data_class->copy = gimp_brush_copy;
+
+ klass->begin_use = gimp_brush_real_begin_use;
+ klass->end_use = gimp_brush_real_end_use;
+ klass->select_brush = gimp_brush_real_select_brush;
+ klass->want_null_motion = gimp_brush_real_want_null_motion;
+ klass->transform_size = gimp_brush_real_transform_size;
+ klass->transform_mask = gimp_brush_real_transform_mask;
+ klass->transform_pixmap = gimp_brush_real_transform_pixmap;
+ klass->transform_boundary = gimp_brush_real_transform_boundary;
+ klass->spacing_changed = NULL;
+
+ g_object_class_install_property (object_class, PROP_SPACING,
+ g_param_spec_double ("spacing", NULL,
+ _("Brush Spacing"),
+ 1.0, 5000.0, 20.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+}
+
+static void
+gimp_brush_tagged_iface_init (GimpTaggedInterface *iface)
+{
+ iface->get_checksum = gimp_brush_get_checksum;
+}
+
+static void
+gimp_brush_init (GimpBrush *brush)
+{
+ brush->priv = gimp_brush_get_instance_private (brush);
+
+ brush->priv->spacing = 20;
+ brush->priv->x_axis.x = 15.0;
+ brush->priv->x_axis.y = 0.0;
+ brush->priv->y_axis.x = 0.0;
+ brush->priv->y_axis.y = 15.0;
+
+ brush->priv->blur_hardness = 1.0;
+}
+
+static void
+gimp_brush_finalize (GObject *object)
+{
+ GimpBrush *brush = GIMP_BRUSH (object);
+
+ g_clear_pointer (&brush->priv->mask, gimp_temp_buf_unref);
+ g_clear_pointer (&brush->priv->pixmap, gimp_temp_buf_unref);
+ g_clear_pointer (&brush->priv->blurred_mask, gimp_temp_buf_unref);
+ g_clear_pointer (&brush->priv->blurred_pixmap, gimp_temp_buf_unref);
+
+ gimp_brush_mipmap_clear (brush);
+
+ g_clear_object (&brush->priv->mask_cache);
+ g_clear_object (&brush->priv->pixmap_cache);
+ g_clear_object (&brush->priv->boundary_cache);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gimp_brush_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpBrush *brush = GIMP_BRUSH (object);
+
+ switch (property_id)
+ {
+ case PROP_SPACING:
+ gimp_brush_set_spacing (brush, g_value_get_double (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_brush_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpBrush *brush = GIMP_BRUSH (object);
+
+ switch (property_id)
+ {
+ case PROP_SPACING:
+ g_value_set_double (value, gimp_brush_get_spacing (brush));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static gint64
+gimp_brush_get_memsize (GimpObject *object,
+ gint64 *gui_size)
+{
+ GimpBrush *brush = GIMP_BRUSH (object);
+ gint64 memsize = 0;
+
+ memsize += gimp_temp_buf_get_memsize (brush->priv->mask);
+ memsize += gimp_temp_buf_get_memsize (brush->priv->pixmap);
+
+ memsize += gimp_brush_mipmap_get_memsize (brush);
+
+ return memsize + GIMP_OBJECT_CLASS (parent_class)->get_memsize (object,
+ gui_size);
+}
+
+static gboolean
+gimp_brush_get_size (GimpViewable *viewable,
+ gint *width,
+ gint *height)
+{
+ GimpBrush *brush = GIMP_BRUSH (viewable);
+
+ *width = gimp_temp_buf_get_width (brush->priv->mask);
+ *height = gimp_temp_buf_get_height (brush->priv->mask);
+
+ return TRUE;
+}
+
+static GimpTempBuf *
+gimp_brush_get_new_preview (GimpViewable *viewable,
+ GimpContext *context,
+ gint width,
+ gint height)
+{
+ GimpBrush *brush = GIMP_BRUSH (viewable);
+ const GimpTempBuf *mask_buf = brush->priv->mask;
+ const GimpTempBuf *pixmap_buf = brush->priv->pixmap;
+ GimpTempBuf *return_buf = NULL;
+ gint mask_width;
+ gint mask_height;
+ guchar *mask_data;
+ guchar *mask;
+ guchar *buf;
+ gint x, y;
+ gboolean scaled = FALSE;
+
+ mask_width = gimp_temp_buf_get_width (mask_buf);
+ mask_height = gimp_temp_buf_get_height (mask_buf);
+
+ if (mask_width > width || mask_height > height)
+ {
+ gdouble ratio_x = (gdouble) width / (gdouble) mask_width;
+ gdouble ratio_y = (gdouble) height / (gdouble) mask_height;
+ gdouble scale = MIN (ratio_x, ratio_y);
+
+ if (scale != 1.0)
+ {
+ gimp_brush_begin_use (brush);
+
+ if (GIMP_IS_BRUSH_GENERATED (brush))
+ {
+ GimpBrushGenerated *gen_brush = GIMP_BRUSH_GENERATED (brush);
+
+ mask_buf = gimp_brush_transform_mask (brush, scale,
+ (gimp_brush_generated_get_aspect_ratio (gen_brush) - 1.0) * 20.0 / 19.0,
+ gimp_brush_generated_get_angle (gen_brush) / 360.0,
+ FALSE,
+ gimp_brush_generated_get_hardness (gen_brush));
+ }
+ else
+ mask_buf = gimp_brush_transform_mask (brush, scale,
+ 0.0, 0.0, FALSE, 1.0);
+
+ if (! mask_buf)
+ {
+ mask_buf = gimp_temp_buf_new (1, 1, babl_format ("Y u8"));
+ gimp_temp_buf_data_clear ((GimpTempBuf *) mask_buf);
+ }
+ else
+ {
+ gimp_temp_buf_ref ((GimpTempBuf *) mask_buf);
+ }
+
+ if (pixmap_buf)
+ pixmap_buf = gimp_brush_transform_pixmap (brush, scale,
+ 0.0, 0.0, FALSE, 1.0);
+
+ mask_width = gimp_temp_buf_get_width (mask_buf);
+ mask_height = gimp_temp_buf_get_height (mask_buf);
+
+ scaled = TRUE;
+ }
+ }
+
+ return_buf = gimp_temp_buf_new (mask_width, mask_height,
+ babl_format ("R'G'B'A u8"));
+
+ mask = mask_data = gimp_temp_buf_lock (mask_buf, babl_format ("Y u8"),
+ GEGL_ACCESS_READ);
+ buf = gimp_temp_buf_get_data (return_buf);
+
+ if (pixmap_buf)
+ {
+ guchar *pixmap_data;
+ guchar *pixmap;
+
+ pixmap = pixmap_data = gimp_temp_buf_lock (pixmap_buf,
+ babl_format ("R'G'B' u8"),
+ GEGL_ACCESS_READ);
+
+ for (y = 0; y < mask_height; y++)
+ {
+ for (x = 0; x < mask_width ; x++)
+ {
+ *buf++ = *pixmap++;
+ *buf++ = *pixmap++;
+ *buf++ = *pixmap++;
+ *buf++ = *mask++;
+ }
+ }
+
+ gimp_temp_buf_unlock (pixmap_buf, pixmap_data);
+ }
+ else
+ {
+ for (y = 0; y < mask_height; y++)
+ {
+ for (x = 0; x < mask_width ; x++)
+ {
+ *buf++ = 0;
+ *buf++ = 0;
+ *buf++ = 0;
+ *buf++ = *mask++;
+ }
+ }
+ }
+
+ gimp_temp_buf_unlock (mask_buf, mask_data);
+
+ if (scaled)
+ {
+ gimp_temp_buf_unref ((GimpTempBuf *) mask_buf);
+
+ gimp_brush_end_use (brush);
+ }
+
+ return return_buf;
+}
+
+static gchar *
+gimp_brush_get_description (GimpViewable *viewable,
+ gchar **tooltip)
+{
+ GimpBrush *brush = GIMP_BRUSH (viewable);
+
+ return g_strdup_printf ("%s (%d × %d)",
+ gimp_object_get_name (brush),
+ gimp_temp_buf_get_width (brush->priv->mask),
+ gimp_temp_buf_get_height (brush->priv->mask));
+}
+
+static void
+gimp_brush_dirty (GimpData *data)
+{
+ GimpBrush *brush = GIMP_BRUSH (data);
+
+ if (brush->priv->mask_cache)
+ gimp_brush_cache_clear (brush->priv->mask_cache);
+
+ if (brush->priv->pixmap_cache)
+ gimp_brush_cache_clear (brush->priv->pixmap_cache);
+
+ if (brush->priv->boundary_cache)
+ gimp_brush_cache_clear (brush->priv->boundary_cache);
+
+ gimp_brush_mipmap_clear (brush);
+
+ g_clear_pointer (&brush->priv->blurred_mask, gimp_temp_buf_unref);
+ g_clear_pointer (&brush->priv->blurred_pixmap, gimp_temp_buf_unref);
+
+ GIMP_DATA_CLASS (parent_class)->dirty (data);
+}
+
+static const gchar *
+gimp_brush_get_extension (GimpData *data)
+{
+ return GIMP_BRUSH_FILE_EXTENSION;
+}
+
+static void
+gimp_brush_copy (GimpData *data,
+ GimpData *src_data)
+{
+ GimpBrush *brush = GIMP_BRUSH (data);
+ GimpBrush *src_brush = GIMP_BRUSH (src_data);
+
+ g_clear_pointer (&brush->priv->mask, gimp_temp_buf_unref);
+ if (src_brush->priv->mask)
+ brush->priv->mask = gimp_temp_buf_copy (src_brush->priv->mask);
+
+ g_clear_pointer (&brush->priv->pixmap, gimp_temp_buf_unref);
+ if (src_brush->priv->pixmap)
+ brush->priv->pixmap = gimp_temp_buf_copy (src_brush->priv->pixmap);
+
+ brush->priv->spacing = src_brush->priv->spacing;
+ brush->priv->x_axis = src_brush->priv->x_axis;
+ brush->priv->y_axis = src_brush->priv->y_axis;
+
+ gimp_data_dirty (data);
+}
+
+static void
+gimp_brush_real_begin_use (GimpBrush *brush)
+{
+ brush->priv->mask_cache =
+ gimp_brush_cache_new ((GDestroyNotify) gimp_temp_buf_unref, 'M', 'm');
+
+ brush->priv->pixmap_cache =
+ gimp_brush_cache_new ((GDestroyNotify) gimp_temp_buf_unref, 'P', 'p');
+
+ brush->priv->boundary_cache =
+ gimp_brush_cache_new ((GDestroyNotify) gimp_bezier_desc_free, 'B', 'b');
+}
+
+static void
+gimp_brush_real_end_use (GimpBrush *brush)
+{
+ g_clear_object (&brush->priv->mask_cache);
+ g_clear_object (&brush->priv->pixmap_cache);
+ g_clear_object (&brush->priv->boundary_cache);
+
+ g_clear_pointer (&brush->priv->blurred_mask, gimp_temp_buf_unref);
+ g_clear_pointer (&brush->priv->blurred_pixmap, gimp_temp_buf_unref);
+}
+
+static GimpBrush *
+gimp_brush_real_select_brush (GimpBrush *brush,
+ const GimpCoords *last_coords,
+ const GimpCoords *current_coords)
+{
+ return brush;
+}
+
+static gboolean
+gimp_brush_real_want_null_motion (GimpBrush *brush,
+ const GimpCoords *last_coords,
+ const GimpCoords *current_coords)
+{
+ return TRUE;
+}
+
+static gchar *
+gimp_brush_get_checksum (GimpTagged *tagged)
+{
+ GimpBrush *brush = GIMP_BRUSH (tagged);
+ gchar *checksum_string = NULL;
+
+ if (brush->priv->mask)
+ {
+ GChecksum *checksum = g_checksum_new (G_CHECKSUM_MD5);
+
+ g_checksum_update (checksum,
+ gimp_temp_buf_get_data (brush->priv->mask),
+ gimp_temp_buf_get_data_size (brush->priv->mask));
+ if (brush->priv->pixmap)
+ g_checksum_update (checksum,
+ gimp_temp_buf_get_data (brush->priv->pixmap),
+ gimp_temp_buf_get_data_size (brush->priv->pixmap));
+ g_checksum_update (checksum,
+ (const guchar *) &brush->priv->spacing,
+ sizeof (brush->priv->spacing));
+ g_checksum_update (checksum,
+ (const guchar *) &brush->priv->x_axis,
+ sizeof (brush->priv->x_axis));
+ g_checksum_update (checksum,
+ (const guchar *) &brush->priv->y_axis,
+ sizeof (brush->priv->y_axis));
+
+ checksum_string = g_strdup (g_checksum_get_string (checksum));
+
+ g_checksum_free (checksum);
+ }
+
+ return checksum_string;
+}
+
+/* public functions */
+
+GimpData *
+gimp_brush_new (GimpContext *context,
+ const gchar *name)
+{
+ g_return_val_if_fail (name != NULL, NULL);
+
+ return gimp_brush_generated_new (name,
+ GIMP_BRUSH_GENERATED_CIRCLE,
+ 5.0, 2, 0.5, 1.0, 0.0);
+}
+
+GimpData *
+gimp_brush_get_standard (GimpContext *context)
+{
+ static GimpData *standard_brush = NULL;
+
+ if (! standard_brush)
+ {
+ standard_brush = gimp_brush_new (context, "Standard");
+
+ gimp_data_clean (standard_brush);
+ gimp_data_make_internal (standard_brush, "gimp-brush-standard");
+
+ g_object_add_weak_pointer (G_OBJECT (standard_brush),
+ (gpointer *) &standard_brush);
+ }
+
+ return standard_brush;
+}
+
+void
+gimp_brush_begin_use (GimpBrush *brush)
+{
+ g_return_if_fail (GIMP_IS_BRUSH (brush));
+
+ brush->priv->use_count++;
+
+ if (brush->priv->use_count == 1)
+ GIMP_BRUSH_GET_CLASS (brush)->begin_use (brush);
+}
+
+void
+gimp_brush_end_use (GimpBrush *brush)
+{
+ g_return_if_fail (GIMP_IS_BRUSH (brush));
+ g_return_if_fail (brush->priv->use_count > 0);
+
+ brush->priv->use_count--;
+
+ if (brush->priv->use_count == 0)
+ GIMP_BRUSH_GET_CLASS (brush)->end_use (brush);
+}
+
+GimpBrush *
+gimp_brush_select_brush (GimpBrush *brush,
+ const GimpCoords *last_coords,
+ const GimpCoords *current_coords)
+{
+ g_return_val_if_fail (GIMP_IS_BRUSH (brush), NULL);
+ g_return_val_if_fail (last_coords != NULL, NULL);
+ g_return_val_if_fail (current_coords != NULL, NULL);
+
+ return GIMP_BRUSH_GET_CLASS (brush)->select_brush (brush,
+ last_coords,
+ current_coords);
+}
+
+gboolean
+gimp_brush_want_null_motion (GimpBrush *brush,
+ const GimpCoords *last_coords,
+ const GimpCoords *current_coords)
+{
+ g_return_val_if_fail (GIMP_IS_BRUSH (brush), FALSE);
+ g_return_val_if_fail (last_coords != NULL, FALSE);
+ g_return_val_if_fail (current_coords != NULL, FALSE);
+
+ return GIMP_BRUSH_GET_CLASS (brush)->want_null_motion (brush,
+ last_coords,
+ current_coords);
+}
+
+void
+gimp_brush_transform_size (GimpBrush *brush,
+ gdouble scale,
+ gdouble aspect_ratio,
+ gdouble angle,
+ gboolean reflect,
+ gint *width,
+ gint *height)
+{
+ g_return_if_fail (GIMP_IS_BRUSH (brush));
+ g_return_if_fail (scale > 0.0);
+ g_return_if_fail (width != NULL);
+ g_return_if_fail (height != NULL);
+
+ if (scale == 1.0 &&
+ aspect_ratio == 0.0 &&
+ fmod (angle, 0.5) == 0.0)
+ {
+ *width = gimp_temp_buf_get_width (brush->priv->mask);
+ *height = gimp_temp_buf_get_height (brush->priv->mask);
+
+ return;
+ }
+
+ GIMP_BRUSH_GET_CLASS (brush)->transform_size (brush,
+ scale, aspect_ratio, angle, reflect,
+ width, height);
+}
+
+const GimpTempBuf *
+gimp_brush_transform_mask (GimpBrush *brush,
+ gdouble scale,
+ gdouble aspect_ratio,
+ gdouble angle,
+ gboolean reflect,
+ gdouble hardness)
+{
+ const GimpTempBuf *mask;
+ gint width;
+ gint height;
+ gdouble effective_hardness = hardness;
+
+ g_return_val_if_fail (GIMP_IS_BRUSH (brush), NULL);
+ g_return_val_if_fail (scale > 0.0, NULL);
+
+ gimp_brush_transform_size (brush,
+ scale, aspect_ratio, angle, reflect,
+ &width, &height);
+
+ mask = gimp_brush_cache_get (brush->priv->mask_cache,
+ width, height,
+ scale, aspect_ratio, angle, reflect, hardness);
+
+ if (! mask)
+ {
+#if 0
+ /* This code makes sure that brushes using blur for hardness
+ * (all of them but generated) are blurred once and no more.
+ * It also makes hardnes dynamics not work for these brushes.
+ * This is intentional. Confoliving for each stamp is too expensive.*/
+ if (! brush->priv->blurred_mask &&
+ ! GIMP_IS_BRUSH_GENERATED(brush) &&
+ ! GIMP_IS_BRUSH_PIPE(brush) && /*Can't cache pipes. Sanely anyway*/
+ hardness < 1.0)
+ {
+ brush->priv->blurred_mask = GIMP_BRUSH_GET_CLASS (brush)->transform_mask (brush,
+ 1.0,
+ 0.0,
+ 0.0,
+ FALSE,
+ hardness);
+ brush->priv->blur_hardness = hardness;
+ }
+
+ if (brush->priv->blurred_mask)
+ {
+ effective_hardness = 1.0; /*Hardness has already been applied*/
+ }
+#endif
+
+ mask = GIMP_BRUSH_GET_CLASS (brush)->transform_mask (brush,
+ scale,
+ aspect_ratio,
+ angle,
+ reflect,
+ effective_hardness);
+
+ gimp_brush_cache_add (brush->priv->mask_cache,
+ (gpointer) mask,
+ width, height,
+ scale, aspect_ratio, angle, reflect, effective_hardness);
+ }
+
+ return mask;
+}
+
+const GimpTempBuf *
+gimp_brush_transform_pixmap (GimpBrush *brush,
+ gdouble scale,
+ gdouble aspect_ratio,
+ gdouble angle,
+ gboolean reflect,
+ gdouble hardness)
+{
+ const GimpTempBuf *pixmap;
+ gint width;
+ gint height;
+ gdouble effective_hardness = hardness;
+
+ g_return_val_if_fail (GIMP_IS_BRUSH (brush), NULL);
+ g_return_val_if_fail (brush->priv->pixmap != NULL, NULL);
+ g_return_val_if_fail (scale > 0.0, NULL);
+
+ gimp_brush_transform_size (brush,
+ scale, aspect_ratio, angle, reflect,
+ &width, &height);
+
+ pixmap = gimp_brush_cache_get (brush->priv->pixmap_cache,
+ width, height,
+ scale, aspect_ratio, angle, reflect, hardness);
+
+ if (! pixmap)
+ {
+#if 0
+ if (! brush->priv->blurred_pixmap &&
+ ! GIMP_IS_BRUSH_GENERATED(brush) &&
+ ! GIMP_IS_BRUSH_PIPE(brush) /*Can't cache pipes. Sanely anyway*/
+ && hardness < 1.0)
+ {
+ brush->priv->blurred_pixmap = GIMP_BRUSH_GET_CLASS (brush)->transform_pixmap (brush,
+ 1.0,
+ 0.0,
+ 0.0,
+ FALSE,
+ hardness);
+ brush->priv->blur_hardness = hardness;
+ }
+
+ if (brush->priv->blurred_pixmap) {
+ effective_hardness = 1.0; /*Hardness has already been applied*/
+ }
+#endif
+
+ pixmap = GIMP_BRUSH_GET_CLASS (brush)->transform_pixmap (brush,
+ scale,
+ aspect_ratio,
+ angle,
+ reflect,
+ effective_hardness);
+
+ gimp_brush_cache_add (brush->priv->pixmap_cache,
+ (gpointer) pixmap,
+ width, height,
+ scale, aspect_ratio, angle, reflect, effective_hardness);
+ }
+
+ return pixmap;
+}
+
+const GimpBezierDesc *
+gimp_brush_transform_boundary (GimpBrush *brush,
+ gdouble scale,
+ gdouble aspect_ratio,
+ gdouble angle,
+ gboolean reflect,
+ gdouble hardness,
+ gint *width,
+ gint *height)
+{
+ const GimpBezierDesc *boundary;
+
+ g_return_val_if_fail (GIMP_IS_BRUSH (brush), NULL);
+ g_return_val_if_fail (scale > 0.0, NULL);
+ g_return_val_if_fail (width != NULL, NULL);
+ g_return_val_if_fail (height != NULL, NULL);
+
+ gimp_brush_transform_size (brush,
+ scale, aspect_ratio, angle, reflect,
+ width, height);
+
+ boundary = gimp_brush_cache_get (brush->priv->boundary_cache,
+ *width, *height,
+ scale, aspect_ratio, angle, reflect, hardness);
+
+ if (! boundary)
+ {
+ boundary = GIMP_BRUSH_GET_CLASS (brush)->transform_boundary (brush,
+ scale,
+ aspect_ratio,
+ angle,
+ reflect,
+ hardness,
+ width,
+ height);
+
+ /* while the brush mask is always at least 1x1 pixels, its
+ * outline can correctly be NULL
+ *
+ * FIXME: make the cache handle NULL things when it is
+ * properly implemented
+ */
+ if (boundary)
+ gimp_brush_cache_add (brush->priv->boundary_cache,
+ (gpointer) boundary,
+ *width, *height,
+ scale, aspect_ratio, angle, reflect, hardness);
+ }
+
+ return boundary;
+}
+
+GimpTempBuf *
+gimp_brush_get_mask (GimpBrush *brush)
+{
+ g_return_val_if_fail (brush != NULL, NULL);
+ g_return_val_if_fail (GIMP_IS_BRUSH (brush), NULL);
+
+ if (brush->priv->blurred_mask)
+ {
+ return brush->priv->blurred_mask;
+ }
+ return brush->priv->mask;
+}
+
+GimpTempBuf *
+gimp_brush_get_pixmap (GimpBrush *brush)
+{
+ g_return_val_if_fail (brush != NULL, NULL);
+ g_return_val_if_fail (GIMP_IS_BRUSH (brush), NULL);
+
+ if(brush->priv->blurred_pixmap)
+ {
+ return brush->priv->blurred_pixmap;
+ }
+ return brush->priv->pixmap;
+}
+
+void
+gimp_brush_flush_blur_caches (GimpBrush *brush)
+{
+#if 0
+ g_clear_pointer (&brush->priv->blurred_mask, gimp_temp_buf_unref);
+ g_clear_pointer (&brush->priv->blurred_pixmap, gimp_temp_buf_unref);
+
+ if (brush->priv->mask_cache)
+ gimp_brush_cache_clear (brush->priv->mask_cache);
+
+ if (brush->priv->pixmap_cache)
+ gimp_brush_cache_clear (brush->priv->pixmap_cache);
+
+ if (brush->priv->boundary_cache)
+ gimp_brush_cache_clear (brush->priv->boundary_cache);
+#endif
+}
+
+gdouble
+gimp_brush_get_blur_hardness (GimpBrush *brush)
+{
+ g_return_val_if_fail (GIMP_IS_BRUSH (brush), 0);
+
+ return brush->priv->blur_hardness;
+}
+
+gint
+gimp_brush_get_width (GimpBrush *brush)
+{
+ g_return_val_if_fail (GIMP_IS_BRUSH (brush), 0);
+
+ if (brush->priv->blurred_mask)
+ return gimp_temp_buf_get_width (brush->priv->blurred_mask);
+
+ if (brush->priv->blurred_pixmap)
+ return gimp_temp_buf_get_width (brush->priv->blurred_pixmap);
+
+ return gimp_temp_buf_get_width (brush->priv->mask);
+}
+
+gint
+gimp_brush_get_height (GimpBrush *brush)
+{
+ g_return_val_if_fail (GIMP_IS_BRUSH (brush), 0);
+
+ if (brush->priv->blurred_mask)
+ return gimp_temp_buf_get_height (brush->priv->blurred_mask);
+
+ if (brush->priv->blurred_pixmap)
+ return gimp_temp_buf_get_height (brush->priv->blurred_pixmap);
+
+ return gimp_temp_buf_get_height (brush->priv->mask);
+}
+
+gint
+gimp_brush_get_spacing (GimpBrush *brush)
+{
+ g_return_val_if_fail (GIMP_IS_BRUSH (brush), 0);
+
+ return brush->priv->spacing;
+}
+
+void
+gimp_brush_set_spacing (GimpBrush *brush,
+ gint spacing)
+{
+ g_return_if_fail (GIMP_IS_BRUSH (brush));
+
+ if (brush->priv->spacing != spacing)
+ {
+ brush->priv->spacing = spacing;
+
+ g_signal_emit (brush, brush_signals[SPACING_CHANGED], 0);
+ g_object_notify (G_OBJECT (brush), "spacing");
+ }
+}
+
+static const GimpVector2 fail = { 0.0, 0.0 };
+
+GimpVector2
+gimp_brush_get_x_axis (GimpBrush *brush)
+{
+ g_return_val_if_fail (GIMP_IS_BRUSH (brush), fail);
+
+ return brush->priv->x_axis;
+}
+
+GimpVector2
+gimp_brush_get_y_axis (GimpBrush *brush)
+{
+ g_return_val_if_fail (GIMP_IS_BRUSH (brush), fail);
+
+ return brush->priv->y_axis;
+}
diff --git a/app/core/gimpbrush.h b/app/core/gimpbrush.h
new file mode 100644
index 0000000..b5735f0
--- /dev/null
+++ b/app/core/gimpbrush.h
@@ -0,0 +1,152 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_BRUSH_H__
+#define __GIMP_BRUSH_H__
+
+
+#include "gimpdata.h"
+
+
+#define GIMP_TYPE_BRUSH (gimp_brush_get_type ())
+#define GIMP_BRUSH(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_BRUSH, GimpBrush))
+#define GIMP_BRUSH_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_BRUSH, GimpBrushClass))
+#define GIMP_IS_BRUSH(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_BRUSH))
+#define GIMP_IS_BRUSH_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_BRUSH))
+#define GIMP_BRUSH_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_BRUSH, GimpBrushClass))
+
+
+typedef struct _GimpBrushPrivate GimpBrushPrivate;
+typedef struct _GimpBrushClass GimpBrushClass;
+
+struct _GimpBrush
+{
+ GimpData parent_instance;
+
+ GimpBrushPrivate *priv;
+};
+
+struct _GimpBrushClass
+{
+ GimpDataClass parent_class;
+
+ /* virtual functions */
+ void (* begin_use) (GimpBrush *brush);
+ void (* end_use) (GimpBrush *brush);
+ GimpBrush * (* select_brush) (GimpBrush *brush,
+ const GimpCoords *last_coords,
+ const GimpCoords *current_coords);
+ gboolean (* want_null_motion) (GimpBrush *brush,
+ const GimpCoords *last_coords,
+ const GimpCoords *current_coords);
+ void (* transform_size) (GimpBrush *brush,
+ gdouble scale,
+ gdouble aspect_ratio,
+ gdouble angle,
+ gboolean reflect,
+ gint *width,
+ gint *height);
+ GimpTempBuf * (* transform_mask) (GimpBrush *brush,
+ gdouble scale,
+ gdouble aspect_ratio,
+ gdouble angle,
+ gboolean reflect,
+ gdouble hardness);
+ GimpTempBuf * (* transform_pixmap) (GimpBrush *brush,
+ gdouble scale,
+ gdouble aspect_ratio,
+ gdouble angle,
+ gboolean reflect,
+ gdouble hardness);
+ GimpBezierDesc * (* transform_boundary) (GimpBrush *brush,
+ gdouble scale,
+ gdouble aspect_ratio,
+ gdouble angle,
+ gboolean reflect,
+ gdouble hardness,
+ gint *width,
+ gint *height);
+
+ /* signals */
+ void (* spacing_changed) (GimpBrush *brush);
+};
+
+
+GType gimp_brush_get_type (void) G_GNUC_CONST;
+
+GimpData * gimp_brush_new (GimpContext *context,
+ const gchar *name);
+GimpData * gimp_brush_get_standard (GimpContext *context);
+
+void gimp_brush_begin_use (GimpBrush *brush);
+void gimp_brush_end_use (GimpBrush *brush);
+
+GimpBrush * gimp_brush_select_brush (GimpBrush *brush,
+ const GimpCoords *last_coords,
+ const GimpCoords *current_coords);
+gboolean gimp_brush_want_null_motion (GimpBrush *brush,
+ const GimpCoords *last_coords,
+ const GimpCoords *current_coords);
+
+/* Gets width and height of a transformed mask of the brush, for
+ * provided parameters.
+ */
+void gimp_brush_transform_size (GimpBrush *brush,
+ gdouble scale,
+ gdouble aspect_ratio,
+ gdouble angle,
+ gboolean reflect,
+ gint *width,
+ gint *height);
+const GimpTempBuf * gimp_brush_transform_mask (GimpBrush *brush,
+ gdouble scale,
+ gdouble aspect_ratio,
+ gdouble angle,
+ gboolean reflect,
+ gdouble hardness);
+const GimpTempBuf * gimp_brush_transform_pixmap (GimpBrush *brush,
+ gdouble scale,
+ gdouble aspect_ratio,
+ gdouble angle,
+ gboolean reflect,
+ gdouble hardness);
+const GimpBezierDesc * gimp_brush_transform_boundary (GimpBrush *brush,
+ gdouble scale,
+ gdouble aspect_ratio,
+ gdouble angle,
+ gboolean reflect,
+ gdouble hardness,
+ gint *width,
+ gint *height);
+
+GimpTempBuf * gimp_brush_get_mask (GimpBrush *brush);
+GimpTempBuf * gimp_brush_get_pixmap (GimpBrush *brush);
+
+gint gimp_brush_get_width (GimpBrush *brush);
+gint gimp_brush_get_height (GimpBrush *brush);
+
+gint gimp_brush_get_spacing (GimpBrush *brush);
+void gimp_brush_set_spacing (GimpBrush *brush,
+ gint spacing);
+
+GimpVector2 gimp_brush_get_x_axis (GimpBrush *brush);
+GimpVector2 gimp_brush_get_y_axis (GimpBrush *brush);
+
+void gimp_brush_flush_blur_caches (GimpBrush *brush);
+gdouble gimp_brush_get_blur_hardness (GimpBrush *brush);
+
+#endif /* __GIMP_BRUSH_H__ */
diff --git a/app/core/gimpbrushcache.c b/app/core/gimpbrushcache.c
new file mode 100644
index 0000000..07ad008
--- /dev/null
+++ b/app/core/gimpbrushcache.c
@@ -0,0 +1,299 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpbrushcache.c
+ * Copyright (C) 2011 Michael Natterer <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+
+#include "core-types.h"
+
+#include "gimpbrushcache.h"
+
+#include "gimp-log.h"
+#include "gimp-intl.h"
+
+
+#define MAX_CACHED_DATA 20
+
+
+enum
+{
+ PROP_0,
+ PROP_DATA_DESTROY
+};
+
+
+typedef struct _GimpBrushCacheUnit GimpBrushCacheUnit;
+
+struct _GimpBrushCacheUnit
+{
+ gpointer data;
+
+ gint width;
+ gint height;
+ gdouble scale;
+ gdouble aspect_ratio;
+ gdouble angle;
+ gboolean reflect;
+ gdouble hardness;
+};
+
+
+static void gimp_brush_cache_constructed (GObject *object);
+static void gimp_brush_cache_finalize (GObject *object);
+static void gimp_brush_cache_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_brush_cache_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+
+G_DEFINE_TYPE (GimpBrushCache, gimp_brush_cache, GIMP_TYPE_OBJECT)
+
+#define parent_class gimp_brush_cache_parent_class
+
+
+static void
+gimp_brush_cache_class_init (GimpBrushCacheClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->constructed = gimp_brush_cache_constructed;
+ object_class->finalize = gimp_brush_cache_finalize;
+ object_class->set_property = gimp_brush_cache_set_property;
+ object_class->get_property = gimp_brush_cache_get_property;
+
+ g_object_class_install_property (object_class, PROP_DATA_DESTROY,
+ g_param_spec_pointer ("data-destroy",
+ NULL, NULL,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+}
+
+static void
+gimp_brush_cache_init (GimpBrushCache *brush)
+{
+}
+
+static void
+gimp_brush_cache_constructed (GObject *object)
+{
+ GimpBrushCache *cache = GIMP_BRUSH_CACHE (object);
+
+ G_OBJECT_CLASS (parent_class)->constructed (object);
+
+ gimp_assert (cache->data_destroy != NULL);
+}
+
+static void
+gimp_brush_cache_finalize (GObject *object)
+{
+ GimpBrushCache *cache = GIMP_BRUSH_CACHE (object);
+
+ gimp_brush_cache_clear (cache);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gimp_brush_cache_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpBrushCache *cache = GIMP_BRUSH_CACHE (object);
+
+ switch (property_id)
+ {
+ case PROP_DATA_DESTROY:
+ cache->data_destroy = g_value_get_pointer (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_brush_cache_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpBrushCache *cache = GIMP_BRUSH_CACHE (object);
+
+ switch (property_id)
+ {
+ case PROP_DATA_DESTROY:
+ g_value_set_pointer (value, cache->data_destroy);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+
+/* public functions */
+
+GimpBrushCache *
+gimp_brush_cache_new (GDestroyNotify data_destroy,
+ gchar debug_hit,
+ gchar debug_miss)
+{
+ GimpBrushCache *cache;
+
+ g_return_val_if_fail (data_destroy != NULL, NULL);
+
+ cache = g_object_new (GIMP_TYPE_BRUSH_CACHE,
+ "data-destroy", data_destroy,
+ NULL);
+
+ cache->debug_hit = debug_hit;
+ cache->debug_miss = debug_miss;
+
+ return cache;
+}
+
+void
+gimp_brush_cache_clear (GimpBrushCache *cache)
+{
+ g_return_if_fail (GIMP_IS_BRUSH_CACHE (cache));
+
+ if (cache->cached_units)
+ {
+ GList *iter;
+
+ for (iter = cache->cached_units; iter; iter = g_list_next (iter))
+ {
+ GimpBrushCacheUnit *unit = iter->data;
+
+ cache->data_destroy (unit->data);
+ }
+
+ g_list_free_full (cache->cached_units, g_free);
+ cache->cached_units = NULL;
+ }
+}
+
+gconstpointer
+gimp_brush_cache_get (GimpBrushCache *cache,
+ gint width,
+ gint height,
+ gdouble scale,
+ gdouble aspect_ratio,
+ gdouble angle,
+ gboolean reflect,
+ gdouble hardness)
+{
+ GList *iter;
+
+ g_return_val_if_fail (GIMP_IS_BRUSH_CACHE (cache), NULL);
+
+ for (iter = cache->cached_units; iter; iter = g_list_next (iter))
+ {
+ GimpBrushCacheUnit *unit = iter->data;
+
+ if (unit->data &&
+ unit->width == width &&
+ unit->height == height &&
+ unit->scale == scale &&
+ unit->aspect_ratio == aspect_ratio &&
+ unit->angle == angle &&
+ unit->reflect == reflect &&
+ unit->hardness == hardness)
+ {
+ if (gimp_log_flags & GIMP_LOG_BRUSH_CACHE)
+ g_printerr ("%c", cache->debug_hit);
+
+ /* Make the returned cached brush first in the list. */
+ cache->cached_units = g_list_remove_link (cache->cached_units, iter);
+ iter->next = cache->cached_units;
+ if (cache->cached_units)
+ cache->cached_units->prev = iter;
+ cache->cached_units = iter;
+
+ return (gconstpointer) unit->data;
+ }
+ }
+
+ if (gimp_log_flags & GIMP_LOG_BRUSH_CACHE)
+ g_printerr ("%c", cache->debug_miss);
+
+ return NULL;
+}
+
+void
+gimp_brush_cache_add (GimpBrushCache *cache,
+ gpointer data,
+ gint width,
+ gint height,
+ gdouble scale,
+ gdouble aspect_ratio,
+ gdouble angle,
+ gboolean reflect,
+ gdouble hardness)
+{
+ GList *iter;
+ GimpBrushCacheUnit *unit;
+ GList *last = NULL;
+ gint length = 0;
+
+ g_return_if_fail (GIMP_IS_BRUSH_CACHE (cache));
+ g_return_if_fail (data != NULL);
+
+ for (iter = cache->cached_units; iter; iter = g_list_next (iter))
+ {
+ unit = iter->data;
+
+ if (data == unit->data)
+ return;
+
+ length++;
+ last = iter;
+ }
+
+ if (length > MAX_CACHED_DATA)
+ {
+ unit = last->data;
+
+ cache->data_destroy (unit->data);
+ cache->cached_units = g_list_delete_link (cache->cached_units, last);
+ g_free (unit);
+ }
+
+ unit = g_new0 (GimpBrushCacheUnit, 1);
+
+ unit->data = data;
+ unit->width = width;
+ unit->height = height;
+ unit->scale = scale;
+ unit->aspect_ratio = aspect_ratio;
+ unit->angle = angle;
+ unit->reflect = reflect;
+ unit->hardness = hardness;
+
+ cache->cached_units = g_list_prepend (cache->cached_units, unit);
+}
diff --git a/app/core/gimpbrushcache.h b/app/core/gimpbrushcache.h
new file mode 100644
index 0000000..7722117
--- /dev/null
+++ b/app/core/gimpbrushcache.h
@@ -0,0 +1,83 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpbrushcache.h
+ * Copyright (C) 2011 Michael Natterer <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_BRUSH_CACHE_H__
+#define __GIMP_BRUSH_CACHE_H__
+
+
+#include "gimpobject.h"
+
+
+#define GIMP_TYPE_BRUSH_CACHE (gimp_brush_cache_get_type ())
+#define GIMP_BRUSH_CACHE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_BRUSH_CACHE, GimpBrushCache))
+#define GIMP_BRUSH_CACHE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_BRUSH_CACHE, GimpBrushCacheClass))
+#define GIMP_IS_BRUSH_CACHE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_BRUSH_CACHE))
+#define GIMP_IS_BRUSH_CACHE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_BRUSH_CACHE))
+#define GIMP_BRUSH_CACHE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_BRUSH_CACHE, GimpBrushCacheClass))
+
+
+typedef struct _GimpBrushCacheClass GimpBrushCacheClass;
+
+struct _GimpBrushCache
+{
+ GimpObject parent_instance;
+
+ GDestroyNotify data_destroy;
+
+ GList *cached_units;
+
+ gchar debug_hit;
+ gchar debug_miss;
+};
+
+struct _GimpBrushCacheClass
+{
+ GimpObjectClass parent_class;
+};
+
+
+GType gimp_brush_cache_get_type (void) G_GNUC_CONST;
+
+GimpBrushCache * gimp_brush_cache_new (GDestroyNotify data_destory,
+ gchar debug_hit,
+ gchar debug_miss);
+
+void gimp_brush_cache_clear (GimpBrushCache *cache);
+
+gconstpointer gimp_brush_cache_get (GimpBrushCache *cache,
+ gint width,
+ gint height,
+ gdouble scale,
+ gdouble aspect_ratio,
+ gdouble angle,
+ gboolean reflect,
+ gdouble hardness);
+void gimp_brush_cache_add (GimpBrushCache *cache,
+ gpointer data,
+ gint width,
+ gint height,
+ gdouble scale,
+ gdouble aspect_ratio,
+ gdouble angle,
+ gboolean reflect,
+ gdouble hardness);
+
+
+#endif /* __GIMP_BRUSH_CACHE_H__ */
diff --git a/app/core/gimpbrushclipboard.c b/app/core/gimpbrushclipboard.c
new file mode 100644
index 0000000..2d4a5e1
--- /dev/null
+++ b/app/core/gimpbrushclipboard.c
@@ -0,0 +1,298 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpbrushclipboard.c
+ * Copyright (C) 2006 Michael Natterer <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "core-types.h"
+
+#include "gimp.h"
+#include "gimpbuffer.h"
+#include "gimpbrush-private.h"
+#include "gimpbrushclipboard.h"
+#include "gimpimage.h"
+#include "gimppickable.h"
+#include "gimptempbuf.h"
+
+#include "gimp-intl.h"
+
+
+#define BRUSH_MAX_SIZE 1024
+
+enum
+{
+ PROP_0,
+ PROP_GIMP,
+ PROP_MASK_ONLY
+};
+
+
+/* local function prototypes */
+
+static void gimp_brush_clipboard_constructed (GObject *object);
+static void gimp_brush_clipboard_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_brush_clipboard_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static GimpData * gimp_brush_clipboard_duplicate (GimpData *data);
+
+static void gimp_brush_clipboard_changed (Gimp *gimp,
+ GimpBrush *brush);
+
+
+G_DEFINE_TYPE (GimpBrushClipboard, gimp_brush_clipboard, GIMP_TYPE_BRUSH)
+
+#define parent_class gimp_brush_clipboard_parent_class
+
+
+static void
+gimp_brush_clipboard_class_init (GimpBrushClipboardClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpDataClass *data_class = GIMP_DATA_CLASS (klass);
+
+ object_class->constructed = gimp_brush_clipboard_constructed;
+ object_class->set_property = gimp_brush_clipboard_set_property;
+ object_class->get_property = gimp_brush_clipboard_get_property;
+
+ data_class->duplicate = gimp_brush_clipboard_duplicate;
+
+ g_object_class_install_property (object_class, PROP_GIMP,
+ g_param_spec_object ("gimp", NULL, NULL,
+ GIMP_TYPE_GIMP,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+
+ g_object_class_install_property (object_class, PROP_MASK_ONLY,
+ g_param_spec_boolean ("mask-only", NULL, NULL,
+ FALSE,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+}
+
+static void
+gimp_brush_clipboard_init (GimpBrushClipboard *brush)
+{
+}
+
+static void
+gimp_brush_clipboard_constructed (GObject *object)
+{
+ GimpBrushClipboard *brush = GIMP_BRUSH_CLIPBOARD (object);
+
+ G_OBJECT_CLASS (parent_class)->constructed (object);
+
+ gimp_assert (GIMP_IS_GIMP (brush->gimp));
+
+ g_signal_connect_object (brush->gimp, "clipboard-changed",
+ G_CALLBACK (gimp_brush_clipboard_changed),
+ brush, 0);
+
+ gimp_brush_clipboard_changed (brush->gimp, GIMP_BRUSH (brush));
+}
+
+static void
+gimp_brush_clipboard_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpBrushClipboard *brush = GIMP_BRUSH_CLIPBOARD (object);
+
+ switch (property_id)
+ {
+ case PROP_GIMP:
+ brush->gimp = g_value_get_object (value);
+ break;
+
+ case PROP_MASK_ONLY:
+ brush->mask_only = g_value_get_boolean (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_brush_clipboard_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpBrushClipboard *brush = GIMP_BRUSH_CLIPBOARD (object);
+
+ switch (property_id)
+ {
+ case PROP_GIMP:
+ g_value_set_object (value, brush->gimp);
+ break;
+
+ case PROP_MASK_ONLY:
+ g_value_set_boolean (value, brush->mask_only);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static GimpData *
+gimp_brush_clipboard_duplicate (GimpData *data)
+{
+ GimpData *new = g_object_new (GIMP_TYPE_BRUSH, NULL);
+
+ gimp_data_copy (new, data);
+
+ return new;
+}
+
+GimpData *
+gimp_brush_clipboard_new (Gimp *gimp,
+ gboolean mask_only)
+{
+ const gchar *name;
+
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
+
+ if (mask_only)
+ name = _("Clipboard Mask");
+ else
+ name = _("Clipboard Image");
+
+ return g_object_new (GIMP_TYPE_BRUSH_CLIPBOARD,
+ "name", name,
+ "gimp", gimp,
+ "mask-only", mask_only,
+ NULL);
+}
+
+
+/* private functions */
+
+static void
+gimp_brush_clipboard_changed (Gimp *gimp,
+ GimpBrush *brush)
+{
+ GimpObject *paste;
+ GeglBuffer *buffer = NULL;
+ gint width;
+ gint height;
+
+ g_clear_pointer (&brush->priv->mask, gimp_temp_buf_unref);
+ g_clear_pointer (&brush->priv->pixmap, gimp_temp_buf_unref);
+
+ paste = gimp_get_clipboard_object (gimp);
+
+ if (GIMP_IS_IMAGE (paste))
+ {
+ gimp_pickable_flush (GIMP_PICKABLE (paste));
+ buffer = gimp_pickable_get_buffer (GIMP_PICKABLE (paste));
+ }
+ else if (GIMP_IS_BUFFER (paste))
+ {
+ buffer = gimp_buffer_get_buffer (GIMP_BUFFER (paste));
+ }
+
+ if (buffer)
+ {
+ const Babl *format = gegl_buffer_get_format (buffer);
+
+ width = MIN (gegl_buffer_get_width (buffer), BRUSH_MAX_SIZE);
+ height = MIN (gegl_buffer_get_height (buffer), BRUSH_MAX_SIZE);
+
+ brush->priv->mask = gimp_temp_buf_new (width, height,
+ babl_format ("Y u8"));
+
+ if (GIMP_BRUSH_CLIPBOARD (brush)->mask_only)
+ {
+ guchar *p;
+ gint i;
+
+ gegl_buffer_get (buffer,
+ GEGL_RECTANGLE (0, 0, width, height), 1.0,
+ babl_format ("Y u8"),
+ gimp_temp_buf_get_data (brush->priv->mask),
+ GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
+
+ /* invert the mask, it's more intuitive to think
+ * "black on white" than the other way around
+ */
+ for (i = 0, p = gimp_temp_buf_get_data (brush->priv->mask);
+ i < width * height;
+ i++, p++)
+ {
+ *p = 255 - *p;
+ }
+ }
+ else
+ {
+ brush->priv->pixmap = gimp_temp_buf_new (width, height,
+ babl_format ("R'G'B' u8"));
+
+ /* copy the alpha channel into the brush's mask */
+ if (babl_format_has_alpha (format))
+ {
+ gegl_buffer_get (buffer,
+ GEGL_RECTANGLE (0, 0, width, height), 1.0,
+ babl_format ("A u8"),
+ gimp_temp_buf_get_data (brush->priv->mask),
+ GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
+ }
+ else
+ {
+ memset (gimp_temp_buf_get_data (brush->priv->mask), 255,
+ width * height);
+ }
+
+ /* copy the color channels into the brush's pixmap */
+ gegl_buffer_get (buffer,
+ GEGL_RECTANGLE (0, 0, width, height), 1.0,
+ babl_format ("R'G'B' u8"),
+ gimp_temp_buf_get_data (brush->priv->pixmap),
+ GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
+ }
+ }
+ else
+ {
+ width = 17;
+ height = 17;
+
+ brush->priv->mask = gimp_temp_buf_new (width, height,
+ babl_format ("Y u8"));
+ gimp_temp_buf_data_clear (brush->priv->mask);
+ }
+
+ brush->priv->x_axis.x = width / 2;
+ brush->priv->x_axis.y = 0;
+ brush->priv->y_axis.x = 0;
+ brush->priv->y_axis.y = height / 2;
+
+ gimp_data_dirty (GIMP_DATA (brush));
+}
diff --git a/app/core/gimpbrushclipboard.h b/app/core/gimpbrushclipboard.h
new file mode 100644
index 0000000..95af7e1
--- /dev/null
+++ b/app/core/gimpbrushclipboard.h
@@ -0,0 +1,58 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpbrushclipboard.h
+ * Copyright (C) 2006 Michael Natterer <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_BRUSH_CLIPBOARD_H__
+#define __GIMP_BRUSH_CLIPBOARD_H__
+
+
+#include "gimpbrush.h"
+
+
+#define GIMP_TYPE_BRUSH_CLIPBOARD (gimp_brush_clipboard_get_type ())
+#define GIMP_BRUSH_CLIPBOARD(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_BRUSH_CLIPBOARD, GimpBrushClipboard))
+#define GIMP_BRUSH_CLIPBOARD_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_BRUSH_CLIPBOARD, GimpBrushClipboardClass))
+#define GIMP_IS_BRUSH_CLIPBOARD(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_BRUSH_CLIPBOARD))
+#define GIMP_IS_BRUSH_CLIPBOARD_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_BRUSH_CLIPBOARD))
+#define GIMP_BRUSH_CLIPBOARD_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_BRUSH_CLIPBOARD, GimpBrushClipboardClass))
+
+
+typedef struct _GimpBrushClipboardClass GimpBrushClipboardClass;
+
+struct _GimpBrushClipboard
+{
+ GimpBrush parent_instance;
+
+ Gimp *gimp;
+ gboolean mask_only;
+};
+
+struct _GimpBrushClipboardClass
+{
+ GimpBrushClass parent_class;
+};
+
+
+GType gimp_brush_clipboard_get_type (void) G_GNUC_CONST;
+
+GimpData * gimp_brush_clipboard_new (Gimp *gimp,
+ gboolean mask_only);
+
+
+#endif /* __GIMP_BRUSH_CLIPBOARD_H__ */
diff --git a/app/core/gimpbrushgenerated-load.c b/app/core/gimpbrushgenerated-load.c
new file mode 100644
index 0000000..a526ae6
--- /dev/null
+++ b/app/core/gimpbrushgenerated-load.c
@@ -0,0 +1,284 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimp_brush_generated module Copyright 1998 Jay Cox <jaycox@earthlink.net>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpbase/gimpbase.h"
+
+#include "core-types.h"
+
+#include "gimp-utils.h"
+#include "gimpbrushgenerated.h"
+#include "gimpbrushgenerated-load.h"
+
+#include "gimp-intl.h"
+
+
+GList *
+gimp_brush_generated_load (GimpContext *context,
+ GFile *file,
+ GInputStream *input,
+ GError **error)
+{
+ GimpBrush *brush;
+ GDataInputStream *data_input;
+ gchar *string;
+ gsize string_len;
+ gint linenum;
+ gchar *name = NULL;
+ GimpBrushGeneratedShape shape = GIMP_BRUSH_GENERATED_CIRCLE;
+ gboolean have_shape = FALSE;
+ gint spikes = 2;
+ gdouble spacing;
+ gdouble radius;
+ gdouble hardness;
+ gdouble aspect_ratio;
+ gdouble angle;
+
+ g_return_val_if_fail (G_IS_FILE (file), NULL);
+ g_return_val_if_fail (G_IS_INPUT_STREAM (input), NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ data_input = g_data_input_stream_new (input);
+
+ /* make sure the file we are reading is the right type */
+ linenum = 1;
+ string_len = 256;
+ string = gimp_data_input_stream_read_line_always (data_input, &string_len,
+ NULL, error);
+ if (! string)
+ goto failed;
+
+ if (! g_str_has_prefix (string, "GIMP-VBR"))
+ {
+ g_set_error (error, GIMP_DATA_ERROR, GIMP_DATA_ERROR_READ,
+ _("Not a GIMP brush file."));
+ g_free (string);
+ goto failed;
+ }
+
+ g_free (string);
+
+ /* make sure we are reading a compatible version */
+ linenum++;
+ string_len = 256;
+ string = gimp_data_input_stream_read_line_always (data_input, &string_len,
+ NULL, error);
+ if (! string)
+ goto failed;
+
+ if (! g_str_has_prefix (string, "1.0"))
+ {
+ if (! g_str_has_prefix (string, "1.5"))
+ {
+ g_set_error (error, GIMP_DATA_ERROR, GIMP_DATA_ERROR_READ,
+ _("Unknown GIMP brush version."));
+ g_free (string);
+ goto failed;
+ }
+ else
+ {
+ have_shape = TRUE;
+ }
+ }
+
+ g_free (string);
+
+ /* read name */
+ linenum++;
+ string_len = 256;
+ string = gimp_data_input_stream_read_line_always (data_input, &string_len,
+ NULL, error);
+ if (! string)
+ goto failed;
+
+ g_strstrip (string);
+
+ /* the empty string is not an allowed name */
+ if (strlen (string) < 1)
+ {
+ name = g_strdup (_("Untitled"));
+ }
+ else
+ {
+ name = gimp_any_to_utf8 (string, -1,
+ _("Invalid UTF-8 string in brush file '%s'."),
+ gimp_file_get_utf8_name (file));
+ }
+
+ g_free (string);
+
+ if (have_shape)
+ {
+ GEnumClass *enum_class;
+ GEnumValue *shape_val;
+
+ enum_class = g_type_class_peek (GIMP_TYPE_BRUSH_GENERATED_SHAPE);
+
+ /* read shape */
+ linenum++;
+ string_len = 256;
+ string = gimp_data_input_stream_read_line_always (data_input, &string_len,
+ NULL, error);
+ if (! string)
+ goto failed;
+
+ g_strstrip (string);
+ shape_val = g_enum_get_value_by_nick (enum_class, string);
+
+ if (! shape_val)
+ {
+ g_set_error (error, GIMP_DATA_ERROR, GIMP_DATA_ERROR_READ,
+ _("Unknown GIMP brush shape."));
+ g_free (string);
+ goto failed;
+ }
+
+ g_free (string);
+
+ shape = shape_val->value;
+ }
+
+ /* read brush spacing */
+ linenum++;
+ string_len = 256;
+ string = gimp_data_input_stream_read_line_always (data_input, &string_len,
+ NULL, error);
+ if (! string)
+ goto failed;
+ if (! gimp_ascii_strtod (string, NULL, &spacing))
+ {
+ g_set_error (error, GIMP_DATA_ERROR, GIMP_DATA_ERROR_READ,
+ _("Invalid brush spacing."));
+ g_free (string);
+ goto failed;
+ }
+ g_free (string);
+
+
+ /* read brush radius */
+ linenum++;
+ string_len = 256;
+ string = gimp_data_input_stream_read_line_always (data_input, &string_len,
+ NULL, error);
+ if (! string)
+ goto failed;
+ if (! gimp_ascii_strtod (string, NULL, &radius))
+ {
+ g_set_error (error, GIMP_DATA_ERROR, GIMP_DATA_ERROR_READ,
+ _("Invalid brush radius."));
+ g_free (string);
+ goto failed;
+ }
+ g_free (string);
+
+ if (have_shape)
+ {
+ /* read number of spikes */
+ linenum++;
+ string_len = 256;
+ string = gimp_data_input_stream_read_line_always (data_input, &string_len,
+ NULL, error);
+ if (! string)
+ goto failed;
+ if (! gimp_ascii_strtoi (string, NULL, 10, &spikes) ||
+ spikes < 2 || spikes > 20)
+ {
+ g_set_error (error, GIMP_DATA_ERROR, GIMP_DATA_ERROR_READ,
+ _("Invalid brush spike count."));
+ g_free (string);
+ goto failed;
+ }
+ g_free (string);
+ }
+
+ /* read brush hardness */
+ linenum++;
+ string_len = 256;
+ string = gimp_data_input_stream_read_line_always (data_input, &string_len,
+ NULL, error);
+ if (! string)
+ goto failed;
+ if (! gimp_ascii_strtod (string, NULL, &hardness))
+ {
+ g_set_error (error, GIMP_DATA_ERROR, GIMP_DATA_ERROR_READ,
+ _("Invalid brush hardness."));
+ g_free (string);
+ goto failed;
+ }
+ g_free (string);
+
+ /* read brush aspect_ratio */
+ linenum++;
+ string_len = 256;
+ string = gimp_data_input_stream_read_line_always (data_input, &string_len,
+ NULL, error);
+ if (! string)
+ goto failed;
+ if (! gimp_ascii_strtod (string, NULL, &aspect_ratio))
+ {
+ g_set_error (error, GIMP_DATA_ERROR, GIMP_DATA_ERROR_READ,
+ _("Invalid brush aspect ratio."));
+ g_free (string);
+ goto failed;
+ }
+ g_free (string);
+
+ /* read brush angle */
+ linenum++;
+ string_len = 256;
+ string = gimp_data_input_stream_read_line_always (data_input, &string_len,
+ NULL, error);
+ if (! string)
+ goto failed;
+ if (! gimp_ascii_strtod (string, NULL, &angle))
+ {
+ g_set_error (error, GIMP_DATA_ERROR, GIMP_DATA_ERROR_READ,
+ _("Invalid brush angle."));
+ g_free (string);
+ goto failed;
+ }
+ g_free (string);
+
+ g_object_unref (data_input);
+
+ brush = GIMP_BRUSH (gimp_brush_generated_new (name, shape, radius, spikes,
+ hardness, aspect_ratio, angle));
+ g_free (name);
+
+ gimp_brush_set_spacing (brush, spacing);
+
+ return g_list_prepend (NULL, brush);
+
+ failed:
+
+ g_object_unref (data_input);
+
+ if (name)
+ g_free (name);
+
+ g_prefix_error (error, _("In line %d of brush file: "), linenum);
+
+ return NULL;
+}
diff --git a/app/core/gimpbrushgenerated-load.h b/app/core/gimpbrushgenerated-load.h
new file mode 100644
index 0000000..afecb3b
--- /dev/null
+++ b/app/core/gimpbrushgenerated-load.h
@@ -0,0 +1,33 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * brush_generated module Copyright 1998 Jay Cox <jaycox@earthlink.net>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_BRUSH_GENERATED_LOAD_H__
+#define __GIMP_BRUSH_GENERATED_LOAD_H__
+
+
+#define GIMP_BRUSH_GENERATED_FILE_EXTENSION ".vbr"
+
+
+GList * gimp_brush_generated_load (GimpContext *context,
+ GFile *file,
+ GInputStream *input,
+ GError **error);
+
+
+#endif /* __GIMP_BRUSH_GENERATED_LOAD_H__ */
diff --git a/app/core/gimpbrushgenerated-save.c b/app/core/gimpbrushgenerated-save.c
new file mode 100644
index 0000000..2b9f8a0
--- /dev/null
+++ b/app/core/gimpbrushgenerated-save.c
@@ -0,0 +1,119 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimp_brush_generated module Copyright 1998 Jay Cox <jaycox@earthlink.net>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpbase/gimpbase.h"
+
+#include "core-types.h"
+
+#include "gimpbrushgenerated.h"
+#include "gimpbrushgenerated-save.h"
+
+#include "gimp-intl.h"
+
+
+gboolean
+gimp_brush_generated_save (GimpData *data,
+ GOutputStream *output,
+ GError **error)
+{
+ GimpBrushGenerated *brush = GIMP_BRUSH_GENERATED (data);
+ const gchar *name = gimp_object_get_name (data);
+ GString *string;
+ gchar buf[G_ASCII_DTOSTR_BUF_SIZE];
+ gboolean have_shape = FALSE;
+
+ g_return_val_if_fail (name != NULL && *name != '\0', FALSE);
+
+ /* write magic header */
+ string = g_string_new ("GIMP-VBR\n");
+
+ /* write version */
+ if (brush->shape != GIMP_BRUSH_GENERATED_CIRCLE || brush->spikes > 2)
+ {
+ g_string_append (string, "1.5\n");
+ have_shape = TRUE;
+ }
+ else
+ {
+ g_string_append (string, "1.0\n");
+ }
+
+ /* write name */
+ g_string_append_printf (string, "%.255s\n", name);
+
+ if (have_shape)
+ {
+ GEnumClass *enum_class;
+ GEnumValue *shape_val;
+
+ enum_class = g_type_class_peek (GIMP_TYPE_BRUSH_GENERATED_SHAPE);
+
+ /* write shape */
+ shape_val = g_enum_get_value (enum_class, brush->shape);
+ g_string_append_printf (string, "%s\n", shape_val->value_nick);
+ }
+
+ /* write brush spacing */
+ g_string_append_printf (string, "%s\n",
+ g_ascii_dtostr (buf, G_ASCII_DTOSTR_BUF_SIZE,
+ gimp_brush_get_spacing (GIMP_BRUSH (brush))));
+
+ /* write brush radius */
+ g_string_append_printf (string, "%s\n",
+ g_ascii_dtostr (buf, G_ASCII_DTOSTR_BUF_SIZE,
+ brush->radius));
+
+ if (have_shape)
+ {
+ /* write brush spikes */
+ g_string_append_printf (string, "%d\n", brush->spikes);
+ }
+
+ /* write brush hardness */
+ g_string_append_printf (string, "%s\n",
+ g_ascii_dtostr (buf, G_ASCII_DTOSTR_BUF_SIZE,
+ brush->hardness));
+
+ /* write brush aspect_ratio */
+ g_string_append_printf (string, "%s\n",
+ g_ascii_dtostr (buf, G_ASCII_DTOSTR_BUF_SIZE,
+ brush->aspect_ratio));
+
+ /* write brush angle */
+ g_string_append_printf (string, "%s\n",
+ g_ascii_dtostr (buf, G_ASCII_DTOSTR_BUF_SIZE,
+ brush->angle));
+
+ if (! g_output_stream_write_all (output, string->str, string->len,
+ NULL, NULL, error))
+ {
+ g_string_free (string, TRUE);
+
+ return FALSE;
+ }
+
+ g_string_free (string, TRUE);
+
+ return TRUE;
+}
diff --git a/app/core/gimpbrushgenerated-save.h b/app/core/gimpbrushgenerated-save.h
new file mode 100644
index 0000000..b33d82e
--- /dev/null
+++ b/app/core/gimpbrushgenerated-save.h
@@ -0,0 +1,30 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * brush_generated module Copyright 1998 Jay Cox <jaycox@earthlink.net>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_BRUSH_GENERATED_SAVE_H__
+#define __GIMP_BRUSH_GENERATED_SAVE_H__
+
+
+/* don't call this function directly, use gimp_data_save() instead */
+gboolean gimp_brush_generated_save (GimpData *data,
+ GOutputStream *output,
+ GError **error);
+
+
+#endif /* __GIMP_BRUSH_GENERATED_SAVE_H__ */
diff --git a/app/core/gimpbrushgenerated.c b/app/core/gimpbrushgenerated.c
new file mode 100644
index 0000000..dacdaa8
--- /dev/null
+++ b/app/core/gimpbrushgenerated.c
@@ -0,0 +1,875 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimp_brush_generated module Copyright 1998 Jay Cox <jaycox@earthlink.net>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpbase/gimpbase.h"
+#include "libgimpmath/gimpmath.h"
+
+#include "core-types.h"
+
+#include "gimpbrush-private.h"
+#include "gimpbrushgenerated.h"
+#include "gimpbrushgenerated-load.h"
+#include "gimpbrushgenerated-save.h"
+#include "gimptempbuf.h"
+
+#include "gimp-intl.h"
+
+
+#define OVERSAMPLING 4
+
+
+enum
+{
+ PROP_0,
+ PROP_SHAPE,
+ PROP_RADIUS,
+ PROP_SPIKES,
+ PROP_HARDNESS,
+ PROP_ASPECT_RATIO,
+ PROP_ANGLE
+};
+
+
+/* local function prototypes */
+
+static void gimp_brush_generated_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_brush_generated_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static void gimp_brush_generated_dirty (GimpData *data);
+static const gchar * gimp_brush_generated_get_extension (GimpData *data);
+static void gimp_brush_generated_copy (GimpData *data,
+ GimpData *src_data);
+
+static void gimp_brush_generated_transform_size(GimpBrush *gbrush,
+ gdouble scale,
+ gdouble aspect_ratio,
+ gdouble angle,
+ gboolean reflect,
+ gint *width,
+ gint *height);
+static GimpTempBuf * gimp_brush_generated_transform_mask(GimpBrush *gbrush,
+ gdouble scale,
+ gdouble aspect_ratio,
+ gdouble angle,
+ gboolean reflect,
+ gdouble hardness);
+
+static GimpTempBuf * gimp_brush_generated_calc (GimpBrushGenerated *brush,
+ GimpBrushGeneratedShape shape,
+ gfloat radius,
+ gint spikes,
+ gfloat hardness,
+ gfloat aspect_ratio,
+ gfloat angle,
+ gboolean reflect,
+ GimpVector2 *xaxis,
+ GimpVector2 *yaxis);
+static void gimp_brush_generated_get_size (GimpBrushGenerated *gbrush,
+ GimpBrushGeneratedShape shape,
+ gfloat radius,
+ gint spikes,
+ gfloat hardness,
+ gfloat aspect_ratio,
+ gdouble angle_in_degrees,
+ gboolean reflect,
+ gint *width,
+ gint *height,
+ gdouble *_s,
+ gdouble *_c,
+ GimpVector2 *_x_axis,
+ GimpVector2 *_y_axis);
+
+
+G_DEFINE_TYPE (GimpBrushGenerated, gimp_brush_generated, GIMP_TYPE_BRUSH)
+
+#define parent_class gimp_brush_generated_parent_class
+
+
+static void
+gimp_brush_generated_class_init (GimpBrushGeneratedClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpDataClass *data_class = GIMP_DATA_CLASS (klass);
+ GimpBrushClass *brush_class = GIMP_BRUSH_CLASS (klass);
+
+ object_class->set_property = gimp_brush_generated_set_property;
+ object_class->get_property = gimp_brush_generated_get_property;
+
+ data_class->save = gimp_brush_generated_save;
+ data_class->dirty = gimp_brush_generated_dirty;
+ data_class->get_extension = gimp_brush_generated_get_extension;
+ data_class->copy = gimp_brush_generated_copy;
+
+ brush_class->transform_size = gimp_brush_generated_transform_size;
+ brush_class->transform_mask = gimp_brush_generated_transform_mask;
+
+ g_object_class_install_property (object_class, PROP_SHAPE,
+ g_param_spec_enum ("shape", NULL,
+ _("Brush Shape"),
+ GIMP_TYPE_BRUSH_GENERATED_SHAPE,
+ GIMP_BRUSH_GENERATED_CIRCLE,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_RADIUS,
+ g_param_spec_double ("radius", NULL,
+ _("Brush Radius"),
+ 0.1, 4000.0, 5.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_SPIKES,
+ g_param_spec_int ("spikes", NULL,
+ _("Brush Spikes"),
+ 2, 20, 2,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_HARDNESS,
+ g_param_spec_double ("hardness", NULL,
+ _("Brush Hardness"),
+ 0.0, 1.0, 0.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_ASPECT_RATIO,
+ g_param_spec_double ("aspect-ratio",
+ NULL,
+ _("Brush Aspect Ratio"),
+ 1.0, 20.0, 1.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_ANGLE,
+ g_param_spec_double ("angle", NULL,
+ _("Brush Angle"),
+ 0.0, 180.0, 0.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+}
+
+static void
+gimp_brush_generated_init (GimpBrushGenerated *brush)
+{
+ brush->shape = GIMP_BRUSH_GENERATED_CIRCLE;
+ brush->radius = 5.0;
+ brush->hardness = 0.0;
+ brush->aspect_ratio = 1.0;
+ brush->angle = 0.0;
+}
+
+static void
+gimp_brush_generated_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpBrushGenerated *brush = GIMP_BRUSH_GENERATED (object);
+
+ switch (property_id)
+ {
+ case PROP_SHAPE:
+ gimp_brush_generated_set_shape (brush, g_value_get_enum (value));
+ break;
+ case PROP_RADIUS:
+ gimp_brush_generated_set_radius (brush, g_value_get_double (value));
+ break;
+ case PROP_SPIKES:
+ gimp_brush_generated_set_spikes (brush, g_value_get_int (value));
+ break;
+ case PROP_HARDNESS:
+ gimp_brush_generated_set_hardness (brush, g_value_get_double (value));
+ break;
+ case PROP_ASPECT_RATIO:
+ gimp_brush_generated_set_aspect_ratio (brush, g_value_get_double (value));
+ break;
+ case PROP_ANGLE:
+ gimp_brush_generated_set_angle (brush, g_value_get_double (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_brush_generated_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpBrushGenerated *brush = GIMP_BRUSH_GENERATED (object);
+
+ switch (property_id)
+ {
+ case PROP_SHAPE:
+ g_value_set_enum (value, brush->shape);
+ break;
+ case PROP_RADIUS:
+ g_value_set_double (value, brush->radius);
+ break;
+ case PROP_SPIKES:
+ g_value_set_int (value, brush->spikes);
+ break;
+ case PROP_HARDNESS:
+ g_value_set_double (value, brush->hardness);
+ break;
+ case PROP_ASPECT_RATIO:
+ g_value_set_double (value, brush->aspect_ratio);
+ break;
+ case PROP_ANGLE:
+ g_value_set_double (value, brush->angle);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_brush_generated_dirty (GimpData *data)
+{
+ GimpBrushGenerated *brush = GIMP_BRUSH_GENERATED (data);
+ GimpBrush *gbrush = GIMP_BRUSH (brush);
+
+ g_clear_pointer (&gbrush->priv->mask, gimp_temp_buf_unref);
+
+ gbrush->priv->mask = gimp_brush_generated_calc (brush,
+ brush->shape,
+ brush->radius,
+ brush->spikes,
+ brush->hardness,
+ brush->aspect_ratio,
+ brush->angle,
+ FALSE,
+ &gbrush->priv->x_axis,
+ &gbrush->priv->y_axis);
+
+ GIMP_DATA_CLASS (parent_class)->dirty (data);
+}
+
+static const gchar *
+gimp_brush_generated_get_extension (GimpData *data)
+{
+ return GIMP_BRUSH_GENERATED_FILE_EXTENSION;
+}
+
+static void
+gimp_brush_generated_copy (GimpData *data,
+ GimpData *src_data)
+{
+ GimpBrushGenerated *brush = GIMP_BRUSH_GENERATED (data);
+ GimpBrushGenerated *src_brush = GIMP_BRUSH_GENERATED (src_data);
+
+ gimp_data_freeze (data);
+
+ gimp_brush_set_spacing (GIMP_BRUSH (brush),
+ gimp_brush_get_spacing (GIMP_BRUSH (src_brush)));
+
+ brush->shape = src_brush->shape;
+ brush->radius = src_brush->radius;
+ brush->spikes = src_brush->spikes;
+ brush->hardness = src_brush->hardness;
+ brush->aspect_ratio = src_brush->aspect_ratio;
+ brush->angle = src_brush->angle;
+
+ gimp_data_thaw (data);
+}
+
+static void
+gimp_brush_generated_transform_size (GimpBrush *gbrush,
+ gdouble scale,
+ gdouble aspect_ratio,
+ gdouble angle,
+ gboolean reflect,
+ gint *width,
+ gint *height)
+{
+ GimpBrushGenerated *brush = GIMP_BRUSH_GENERATED (gbrush);
+ gdouble ratio;
+
+ ratio = fabs (aspect_ratio) * 19.0 / 20.0 + 1.0;
+ ratio = MIN (ratio, 20);
+
+ /* Since generated brushes are symmetric they don't have aspect
+ * ratios < 1.0. it's the same as rotating by 90 degrees and 1 /
+ * ratio, so we fix the input for this case.
+ */
+ if (aspect_ratio < 0.0)
+ angle = angle + 0.25;
+
+ angle = fmod (fmod (angle, 1.0) + 1.0, 1.0);
+ angle *= 360;
+
+ gimp_brush_generated_get_size (brush,
+ brush->shape,
+ brush->radius * scale,
+ brush->spikes,
+ brush->hardness,
+ ratio,
+ angle,
+ reflect,
+ width, height,
+ NULL, NULL, NULL, NULL);
+}
+
+static GimpTempBuf *
+gimp_brush_generated_transform_mask (GimpBrush *gbrush,
+ gdouble scale,
+ gdouble aspect_ratio,
+ gdouble angle,
+ gboolean reflect,
+ gdouble hardness)
+{
+ GimpBrushGenerated *brush = GIMP_BRUSH_GENERATED (gbrush);
+ gdouble ratio;
+
+ ratio = fabs (aspect_ratio) * 19.0 / 20.0 + 1.0;
+ ratio = MIN (ratio, 20);
+
+ /* Since generated brushes are symmetric they don't have aspect
+ * ratios < 1.0. it's the same as rotating by 90 degrees and 1 /
+ * ratio, so we fix the input for this case.
+ */
+ if (aspect_ratio < 0.0)
+ angle = angle + 0.25;
+
+ angle = fmod (fmod (angle, 1.0) + 1.0, 1.0);
+ angle *= 360;
+
+ if (scale == 1.0 &&
+ ratio == brush->aspect_ratio &&
+ angle == brush->angle &&
+ reflect == FALSE &&
+ hardness == brush->hardness)
+ {
+ return gimp_temp_buf_copy (gimp_brush_get_mask (gbrush));
+ }
+
+ return gimp_brush_generated_calc (brush,
+ brush->shape,
+ brush->radius * scale,
+ brush->spikes,
+ hardness,
+ ratio,
+ angle,
+ reflect,
+ NULL, NULL);
+}
+
+
+/* the actual brush rendering functions */
+
+static gdouble
+gauss (gdouble f)
+{
+ /* this aint' a real gauss function */
+ if (f < -0.5)
+ {
+ f = -1.0 - f;
+ return (2.0 * f*f);
+ }
+
+ if (f < 0.5)
+ return (1.0 - 2.0 * f*f);
+
+ f = 1.0 - f;
+ return (2.0 * f*f);
+}
+
+/* set up lookup table */
+static gfloat *
+gimp_brush_generated_calc_lut (gdouble radius,
+ gdouble hardness)
+{
+ gfloat *lookup;
+ gint length;
+ gint x;
+ gdouble d;
+ gdouble sum;
+ gdouble exponent;
+ gdouble buffer[OVERSAMPLING];
+
+ length = OVERSAMPLING * ceil (1 + sqrt (2 * SQR (ceil (radius + 1.0))));
+
+ lookup = gegl_scratch_new (gfloat, length);
+ sum = 0.0;
+
+ if ((1.0 - hardness) < 0.0000004)
+ exponent = 1000000.0;
+ else
+ exponent = 0.4 / (1.0 - hardness);
+
+ for (x = 0; x < OVERSAMPLING; x++)
+ {
+ d = fabs ((x + 0.5) / OVERSAMPLING - 0.5);
+
+ if (d > radius)
+ buffer[x] = 0.0;
+ else
+ buffer[x] = gauss (pow (d / radius, exponent));
+
+ sum += buffer[x];
+ }
+
+ for (x = 0; d < radius || sum > 0.00001; d += 1.0 / OVERSAMPLING)
+ {
+ sum -= buffer[x % OVERSAMPLING];
+
+ if (d > radius)
+ buffer[x % OVERSAMPLING] = 0.0;
+ else
+ buffer[x % OVERSAMPLING] = gauss (pow (d / radius, exponent));
+
+ sum += buffer[x % OVERSAMPLING];
+ lookup[x++] = sum / OVERSAMPLING;
+ }
+
+ while (x < length)
+ {
+ lookup[x++] = 0.0f;
+ }
+
+ return lookup;
+}
+
+static GimpTempBuf *
+gimp_brush_generated_calc (GimpBrushGenerated *brush,
+ GimpBrushGeneratedShape shape,
+ gfloat radius,
+ gint spikes,
+ gfloat hardness,
+ gfloat aspect_ratio,
+ gfloat angle,
+ gboolean reflect,
+ GimpVector2 *xaxis,
+ GimpVector2 *yaxis)
+{
+ gfloat *centerp;
+ gfloat *lookup;
+ gfloat a;
+ gint x, y;
+ gdouble c, s, cs, ss;
+ GimpVector2 x_axis;
+ GimpVector2 y_axis;
+ GimpTempBuf *mask;
+ gint width;
+ gint height;
+ gint half_width;
+ gint half_height;
+
+ gimp_brush_generated_get_size (brush,
+ shape,
+ radius,
+ spikes,
+ hardness,
+ aspect_ratio,
+ angle,
+ reflect,
+ &width, &height,
+ &s, &c, &x_axis, &y_axis);
+
+ mask = gimp_temp_buf_new (width, height,
+ babl_format ("Y float"));
+
+ half_width = width / 2;
+ half_height = height / 2;
+
+ centerp = (gfloat *) gimp_temp_buf_get_data (mask) +
+ half_height * width + half_width;
+
+ lookup = gimp_brush_generated_calc_lut (radius, hardness);
+
+ cs = cos (- 2 * G_PI / spikes);
+ ss = sin (- 2 * G_PI / spikes);
+
+ /* for an even number of spikes compute one half and mirror it */
+ for (y = ((spikes % 2) ? -half_height : 0); y <= half_height; y++)
+ {
+ for (x = -half_width; x <= half_width; x++)
+ {
+ gdouble d = 0;
+ gdouble tx = c * x - s * y;
+ gdouble ty = fabs (s * x + c * y);
+
+ if (spikes > 2)
+ {
+ gdouble angle = atan2 (ty, tx);
+
+ while (angle > G_PI / spikes)
+ {
+ gdouble sx = tx;
+ gdouble sy = ty;
+
+ tx = cs * sx - ss * sy;
+ ty = ss * sx + cs * sy;
+
+ angle -= 2 * G_PI / spikes;
+ }
+ }
+
+ ty *= aspect_ratio;
+
+ switch (shape)
+ {
+ case GIMP_BRUSH_GENERATED_CIRCLE:
+ d = sqrt (SQR (tx) + SQR (ty));
+ break;
+ case GIMP_BRUSH_GENERATED_SQUARE:
+ d = MAX (fabs (tx), fabs (ty));
+ break;
+ case GIMP_BRUSH_GENERATED_DIAMOND:
+ d = fabs (tx) + fabs (ty);
+ break;
+ }
+
+ if (d < radius + 1)
+ a = lookup[(gint) RINT (d * OVERSAMPLING)];
+ else
+ a = 0.0f;
+
+ centerp[y * width + x] = a;
+
+ if (spikes % 2 == 0)
+ centerp[-1 * y * width - x] = a;
+ }
+ }
+
+ gegl_scratch_free (lookup);
+
+ if (xaxis)
+ *xaxis = x_axis;
+
+ if (yaxis)
+ *yaxis = y_axis;
+
+ return mask;
+}
+
+/* This function is shared between gimp_brush_generated_transform_size and
+ * gimp_brush_generated_calc, therefore we provide a bunch of optional
+ * pointers for returnvalues.
+ */
+static void
+gimp_brush_generated_get_size (GimpBrushGenerated *gbrush,
+ GimpBrushGeneratedShape shape,
+ gfloat radius,
+ gint spikes,
+ gfloat hardness,
+ gfloat aspect_ratio,
+ gdouble angle_in_degrees,
+ gboolean reflect,
+ gint *width,
+ gint *height,
+ gdouble *_s,
+ gdouble *_c,
+ GimpVector2 *_x_axis,
+ GimpVector2 *_y_axis)
+{
+ gdouble half_width = 0.0;
+ gdouble half_height = 0.0;
+ gint w, h;
+ gdouble c, s;
+ gdouble short_radius;
+ GimpVector2 x_axis;
+ GimpVector2 y_axis;
+
+ /* Since floatongpoint is not really accurate,
+ * we need to round to limit the errors.
+ * Errors in some border cases resulted in
+ * different height and width reported for
+ * the same input value on calling procedure side.
+ * This became problem at the rise of dynamics that
+ * allows for any angle to turn up.
+ **/
+
+ angle_in_degrees = RINT (angle_in_degrees * 1000.0) / 1000.0;
+
+ s = sin (gimp_deg_to_rad (angle_in_degrees));
+ c = cos (gimp_deg_to_rad (angle_in_degrees));
+
+ if (reflect)
+ c = -c;
+
+ short_radius = radius / aspect_ratio;
+
+ x_axis.x = c * radius;
+ x_axis.y = -1.0 * s * radius;
+ y_axis.x = s * short_radius;
+ y_axis.y = c * short_radius;
+
+ switch (shape)
+ {
+ case GIMP_BRUSH_GENERATED_CIRCLE:
+ half_width = sqrt (x_axis.x * x_axis.x + y_axis.x * y_axis.x);
+ half_height = sqrt (x_axis.y * x_axis.y + y_axis.y * y_axis.y);
+ break;
+
+ case GIMP_BRUSH_GENERATED_SQUARE:
+ half_width = fabs (x_axis.x) + fabs (y_axis.x);
+ half_height = fabs (x_axis.y) + fabs (y_axis.y);
+ break;
+
+ case GIMP_BRUSH_GENERATED_DIAMOND:
+ half_width = MAX (fabs (x_axis.x), fabs (y_axis.x));
+ half_height = MAX (fabs (x_axis.y), fabs (y_axis.y));
+ break;
+ }
+
+ if (spikes > 2)
+ {
+ /* could be optimized by respecting the angle */
+ half_width = half_height = sqrt (radius * radius +
+ short_radius * short_radius);
+ y_axis.x = s * radius;
+ y_axis.y = c * radius;
+ }
+
+ w = MAX (1, ceil (half_width * 2));
+ h = MAX (1, ceil (half_height * 2));
+
+ if (! (w & 0x1)) w++;
+ if (! (h & 0x1)) h++;
+
+ *width = w;
+ *height = h;
+
+ /* These will typically be set then this function is called by
+ * gimp_brush_generated_calc, which needs the values in its algorithms.
+ */
+ if (_s != NULL)
+ *_s = s;
+
+ if (_c != NULL)
+ *_c = c;
+
+ if (_x_axis != NULL)
+ *_x_axis = x_axis;
+
+ if (_y_axis != NULL)
+ *_y_axis = y_axis;
+}
+
+
+/* public functions */
+
+GimpData *
+gimp_brush_generated_new (const gchar *name,
+ GimpBrushGeneratedShape shape,
+ gfloat radius,
+ gint spikes,
+ gfloat hardness,
+ gfloat aspect_ratio,
+ gfloat angle)
+{
+ GimpBrushGenerated *brush;
+
+ g_return_val_if_fail (name != NULL, NULL);
+ g_return_val_if_fail (*name != '\0', NULL);
+
+ brush = g_object_new (GIMP_TYPE_BRUSH_GENERATED,
+ "name", name,
+ "mime-type", "application/x-gimp-brush-generated",
+ "spacing", 20.0,
+ "shape", shape,
+ "radius", radius,
+ "spikes", spikes,
+ "hardness", hardness,
+ "aspect-ratio", aspect_ratio,
+ "angle", angle,
+ NULL);
+
+ return GIMP_DATA (brush);
+}
+
+GimpBrushGeneratedShape
+gimp_brush_generated_set_shape (GimpBrushGenerated *brush,
+ GimpBrushGeneratedShape shape)
+{
+ g_return_val_if_fail (GIMP_IS_BRUSH_GENERATED (brush),
+ GIMP_BRUSH_GENERATED_CIRCLE);
+
+ if (brush->shape != shape)
+ {
+ brush->shape = shape;
+
+ g_object_notify (G_OBJECT (brush), "shape");
+ gimp_data_dirty (GIMP_DATA (brush));
+ }
+
+ return brush->shape;
+}
+
+gfloat
+gimp_brush_generated_set_radius (GimpBrushGenerated *brush,
+ gfloat radius)
+{
+ g_return_val_if_fail (GIMP_IS_BRUSH_GENERATED (brush), -1.0);
+
+ radius = CLAMP (radius, 0.0, 32767.0);
+
+ if (brush->radius != radius)
+ {
+ brush->radius = radius;
+
+ g_object_notify (G_OBJECT (brush), "radius");
+ gimp_data_dirty (GIMP_DATA (brush));
+ }
+
+ return brush->radius;
+}
+
+gint
+gimp_brush_generated_set_spikes (GimpBrushGenerated *brush,
+ gint spikes)
+{
+ g_return_val_if_fail (GIMP_IS_BRUSH_GENERATED (brush), -1);
+
+ spikes = CLAMP (spikes, 2, 20);
+
+ if (brush->spikes != spikes)
+ {
+ brush->spikes = spikes;
+
+ g_object_notify (G_OBJECT (brush), "spikes");
+ gimp_data_dirty (GIMP_DATA (brush));
+ }
+
+ return brush->spikes;
+}
+
+gfloat
+gimp_brush_generated_set_hardness (GimpBrushGenerated *brush,
+ gfloat hardness)
+{
+ g_return_val_if_fail (GIMP_IS_BRUSH_GENERATED (brush), -1.0);
+
+ hardness = CLAMP (hardness, 0.0, 1.0);
+
+ if (brush->hardness != hardness)
+ {
+ brush->hardness = hardness;
+
+ g_object_notify (G_OBJECT (brush), "hardness");
+ gimp_data_dirty (GIMP_DATA (brush));
+ }
+
+ return brush->hardness;
+}
+
+gfloat
+gimp_brush_generated_set_aspect_ratio (GimpBrushGenerated *brush,
+ gfloat ratio)
+{
+ g_return_val_if_fail (GIMP_IS_BRUSH_GENERATED (brush), -1.0);
+
+ ratio = CLAMP (ratio, 1.0, 1000.0);
+
+ if (brush->aspect_ratio != ratio)
+ {
+ brush->aspect_ratio = ratio;
+
+ g_object_notify (G_OBJECT (brush), "aspect-ratio");
+ gimp_data_dirty (GIMP_DATA (brush));
+ }
+
+ return brush->aspect_ratio;
+}
+
+gfloat
+gimp_brush_generated_set_angle (GimpBrushGenerated *brush,
+ gfloat angle)
+{
+ g_return_val_if_fail (GIMP_IS_BRUSH_GENERATED (brush), -1.0);
+
+ if (angle < 0.0)
+ angle = -1.0 * fmod (angle, 180.0);
+ else if (angle > 180.0)
+ angle = fmod (angle, 180.0);
+
+ if (brush->angle != angle)
+ {
+ brush->angle = angle;
+
+ g_object_notify (G_OBJECT (brush), "angle");
+ gimp_data_dirty (GIMP_DATA (brush));
+ }
+
+ return brush->angle;
+}
+
+GimpBrushGeneratedShape
+gimp_brush_generated_get_shape (GimpBrushGenerated *brush)
+{
+ g_return_val_if_fail (GIMP_IS_BRUSH_GENERATED (brush),
+ GIMP_BRUSH_GENERATED_CIRCLE);
+
+ return brush->shape;
+}
+
+gfloat
+gimp_brush_generated_get_radius (GimpBrushGenerated *brush)
+{
+ g_return_val_if_fail (GIMP_IS_BRUSH_GENERATED (brush), -1.0);
+
+ return brush->radius;
+}
+
+gint
+gimp_brush_generated_get_spikes (GimpBrushGenerated *brush)
+{
+ g_return_val_if_fail (GIMP_IS_BRUSH_GENERATED (brush), -1);
+
+ return brush->spikes;
+}
+
+gfloat
+gimp_brush_generated_get_hardness (GimpBrushGenerated *brush)
+{
+ g_return_val_if_fail (GIMP_IS_BRUSH_GENERATED (brush), -1.0);
+
+ return brush->hardness;
+}
+
+gfloat
+gimp_brush_generated_get_aspect_ratio (GimpBrushGenerated *brush)
+{
+ g_return_val_if_fail (GIMP_IS_BRUSH_GENERATED (brush), -1.0);
+
+ return brush->aspect_ratio;
+}
+
+gfloat
+gimp_brush_generated_get_angle (GimpBrushGenerated *brush)
+{
+ g_return_val_if_fail (GIMP_IS_BRUSH_GENERATED (brush), -1.0);
+
+ return brush->angle;
+}
diff --git a/app/core/gimpbrushgenerated.h b/app/core/gimpbrushgenerated.h
new file mode 100644
index 0000000..2df3578
--- /dev/null
+++ b/app/core/gimpbrushgenerated.h
@@ -0,0 +1,88 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * brush_generated module Copyright 1998 Jay Cox <jaycox@earthlink.net>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_BRUSH_GENERATED_H__
+#define __GIMP_BRUSH_GENERATED_H__
+
+
+#include "gimpbrush.h"
+
+
+#define GIMP_TYPE_BRUSH_GENERATED (gimp_brush_generated_get_type ())
+#define GIMP_BRUSH_GENERATED(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_BRUSH_GENERATED, GimpBrushGenerated))
+#define GIMP_BRUSH_GENERATED_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_BRUSH_GENERATED, GimpBrushGeneratedClass))
+#define GIMP_IS_BRUSH_GENERATED(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_BRUSH_GENERATED))
+#define GIMP_IS_BRUSH_GENERATED_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_BRUSH_GENERATED))
+#define GIMP_BRUSH_GENERATED_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_BRUSH_GENERATED, GimpBrushGeneratedClass))
+
+
+typedef struct _GimpBrushGeneratedClass GimpBrushGeneratedClass;
+
+struct _GimpBrushGenerated
+{
+ GimpBrush parent_instance;
+
+ GimpBrushGeneratedShape shape;
+ gfloat radius;
+ gint spikes; /* 2 - 20 */
+ gfloat hardness; /* 0.0 - 1.0 */
+ gfloat aspect_ratio; /* y/x */
+ gfloat angle; /* in degrees */
+};
+
+struct _GimpBrushGeneratedClass
+{
+ GimpBrushClass parent_class;
+};
+
+
+GType gimp_brush_generated_get_type (void) G_GNUC_CONST;
+
+GimpData * gimp_brush_generated_new (const gchar *name,
+ GimpBrushGeneratedShape shape,
+ gfloat radius,
+ gint spikes,
+ gfloat hardness,
+ gfloat aspect_ratio,
+ gfloat angle);
+
+GimpBrushGeneratedShape
+ gimp_brush_generated_set_shape (GimpBrushGenerated *brush,
+ GimpBrushGeneratedShape shape);
+gfloat gimp_brush_generated_set_radius (GimpBrushGenerated *brush,
+ gfloat radius);
+gint gimp_brush_generated_set_spikes (GimpBrushGenerated *brush,
+ gint spikes);
+gfloat gimp_brush_generated_set_hardness (GimpBrushGenerated *brush,
+ gfloat hardness);
+gfloat gimp_brush_generated_set_aspect_ratio (GimpBrushGenerated *brush,
+ gfloat ratio);
+gfloat gimp_brush_generated_set_angle (GimpBrushGenerated *brush,
+ gfloat angle);
+
+GimpBrushGeneratedShape
+ gimp_brush_generated_get_shape (GimpBrushGenerated *brush);
+gfloat gimp_brush_generated_get_radius (GimpBrushGenerated *brush);
+gint gimp_brush_generated_get_spikes (GimpBrushGenerated *brush);
+gfloat gimp_brush_generated_get_hardness (GimpBrushGenerated *brush);
+gfloat gimp_brush_generated_get_aspect_ratio (GimpBrushGenerated *brush);
+gfloat gimp_brush_generated_get_angle (GimpBrushGenerated *brush);
+
+
+#endif /* __GIMP_BRUSH_GENERATED_H__ */
diff --git a/app/core/gimpbrushpipe-load.c b/app/core/gimpbrushpipe-load.c
new file mode 100644
index 0000000..18ad898
--- /dev/null
+++ b/app/core/gimpbrushpipe-load.c
@@ -0,0 +1,163 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ * Copyright (C) 1999 Adrian Likins and Tor Lillqvist
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+#include "libgimpbase/gimpbase.h"
+
+#include "core-types.h"
+
+#include "gimpbrush-load.h"
+#include "gimpbrush-private.h"
+#include "gimpbrushpipe.h"
+#include "gimpbrushpipe-load.h"
+
+#include "gimp-intl.h"
+
+
+GList *
+gimp_brush_pipe_load (GimpContext *context,
+ GFile *file,
+ GInputStream *input,
+ GError **error)
+{
+ GimpBrushPipe *pipe = NULL;
+ gint n_brushes = 0;
+ GString *buffer;
+ gchar *paramstring;
+ gchar c;
+ gsize bytes_read;
+
+ g_return_val_if_fail (G_IS_FILE (file), NULL);
+ g_return_val_if_fail (G_IS_INPUT_STREAM (input), NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ /* The file format starts with a painfully simple text header */
+
+ /* get the name */
+ buffer = g_string_new (NULL);
+ while (g_input_stream_read_all (input, &c, 1, &bytes_read, NULL, NULL) &&
+ bytes_read == 1 &&
+ c != '\n' &&
+ buffer->len < 1024)
+ {
+ g_string_append_c (buffer, c);
+ }
+
+ if (buffer->len > 0 && buffer->len < 1024)
+ {
+ gchar *utf8 =
+ gimp_any_to_utf8 (buffer->str, buffer->len,
+ _("Invalid UTF-8 string in brush file '%s'."),
+ gimp_file_get_utf8_name (file));
+
+ pipe = g_object_new (GIMP_TYPE_BRUSH_PIPE,
+ "name", utf8,
+ "mime-type", "image/x-gimp-gih",
+ NULL);
+
+ g_free (utf8);
+ }
+
+ g_string_free (buffer, TRUE);
+
+ if (! pipe)
+ {
+ g_set_error (error, GIMP_DATA_ERROR, GIMP_DATA_ERROR_READ,
+ _("Fatal parse error in brush file '%s': "
+ "File is corrupt."),
+ gimp_file_get_utf8_name (file));
+ return NULL;
+ }
+
+ /* get the number of brushes */
+ buffer = g_string_new (NULL);
+ while (g_input_stream_read_all (input, &c, 1, &bytes_read, NULL, NULL) &&
+ bytes_read == 1 &&
+ c != '\n' &&
+ buffer->len < 1024)
+ {
+ g_string_append_c (buffer, c);
+ }
+
+ if (buffer->len > 0 && buffer->len < 1024)
+ {
+ n_brushes = strtol (buffer->str, &paramstring, 10);
+ }
+
+ if (n_brushes < 1)
+ {
+ g_set_error (error, GIMP_DATA_ERROR, GIMP_DATA_ERROR_READ,
+ _("Fatal parse error in brush file '%s': "
+ "File is corrupt."),
+ gimp_file_get_utf8_name (file));
+ g_object_unref (pipe);
+ g_string_free (buffer, TRUE);
+ return NULL;
+ }
+
+ while (*paramstring && g_ascii_isspace (*paramstring))
+ paramstring++;
+
+ pipe->brushes = g_new0 (GimpBrush *, n_brushes);
+
+ while (pipe->n_brushes < n_brushes)
+ {
+ pipe->brushes[pipe->n_brushes] = gimp_brush_load_brush (context,
+ file, input,
+ error);
+
+ if (! pipe->brushes[pipe->n_brushes])
+ {
+ g_object_unref (pipe);
+ g_string_free (buffer, TRUE);
+ return NULL;
+ }
+
+ pipe->n_brushes++;
+ }
+
+ if (! gimp_brush_pipe_set_params (pipe, paramstring))
+ {
+ g_set_error (error, GIMP_DATA_ERROR, GIMP_DATA_ERROR_READ,
+ _("Fatal parse error in brush file '%s': "
+ "Inconsistent parameters."),
+ gimp_file_get_utf8_name (file));
+ g_object_unref (pipe);
+ g_string_free (buffer, TRUE);
+ return NULL;
+ }
+
+ g_string_free (buffer, TRUE);
+
+ /* Current brush is the first one. */
+ pipe->current = pipe->brushes[0];
+
+ /* just to satisfy the code that relies on this crap */
+ GIMP_BRUSH (pipe)->priv->spacing = pipe->current->priv->spacing;
+ GIMP_BRUSH (pipe)->priv->x_axis = pipe->current->priv->x_axis;
+ GIMP_BRUSH (pipe)->priv->y_axis = pipe->current->priv->y_axis;
+ GIMP_BRUSH (pipe)->priv->mask = pipe->current->priv->mask;
+ GIMP_BRUSH (pipe)->priv->pixmap = pipe->current->priv->pixmap;
+
+ return g_list_prepend (NULL, pipe);
+}
diff --git a/app/core/gimpbrushpipe-load.h b/app/core/gimpbrushpipe-load.h
new file mode 100644
index 0000000..7426fcb
--- /dev/null
+++ b/app/core/gimpbrushpipe-load.h
@@ -0,0 +1,32 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ * Copyright (C) 1999 Adrian Likins and Tor Lillqvist
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_BRUSH_PIPE_LOAD_H__
+#define __GIMP_BRUSH_PIPE_LOAD_H__
+
+
+#define GIMP_BRUSH_PIPE_FILE_EXTENSION ".gih"
+
+
+GList * gimp_brush_pipe_load (GimpContext *context,
+ GFile *file,
+ GInputStream *input,
+ GError **error);
+
+
+#endif /* __GIMP_BRUSH_PIPE_LOAD_H__ */
diff --git a/app/core/gimpbrushpipe-save.c b/app/core/gimpbrushpipe-save.c
new file mode 100644
index 0000000..1b8e7fb
--- /dev/null
+++ b/app/core/gimpbrushpipe-save.c
@@ -0,0 +1,59 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+#include "core-types.h"
+
+#include "gimpbrushpipe.h"
+#include "gimpbrushpipe-save.h"
+
+
+gboolean
+gimp_brush_pipe_save (GimpData *data,
+ GOutputStream *output,
+ GError **error)
+{
+ GimpBrushPipe *pipe = GIMP_BRUSH_PIPE (data);
+ const gchar *name;
+ gint i;
+
+ name = gimp_object_get_name (pipe);
+
+ if (! g_output_stream_printf (output, NULL, NULL, error,
+ "%s\n%d %s\n",
+ name, pipe->n_brushes, pipe->params))
+ {
+ return FALSE;
+ }
+
+ for (i = 0; i < pipe->n_brushes; i++)
+ {
+ GimpBrush *brush = pipe->brushes[i];
+
+ if (brush &&
+ ! GIMP_DATA_GET_CLASS (brush)->save (GIMP_DATA (brush),
+ output, error))
+ {
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
diff --git a/app/core/gimpbrushpipe-save.h b/app/core/gimpbrushpipe-save.h
new file mode 100644
index 0000000..df76855
--- /dev/null
+++ b/app/core/gimpbrushpipe-save.h
@@ -0,0 +1,28 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_BRUSH_PIPE_SAVE_H__
+#define __GIMP_BRUSH_PIPE_SAVE_H__
+
+
+/* don't call this function directly, use gimp_data_save() instead */
+gboolean gimp_brush_pipe_save (GimpData *data,
+ GOutputStream *output,
+ GError **error);
+
+
+#endif /* __GIMP_BRUSH_PIPE_SAVE_H__ */
diff --git a/app/core/gimpbrushpipe.c b/app/core/gimpbrushpipe.c
new file mode 100644
index 0000000..cca68ad
--- /dev/null
+++ b/app/core/gimpbrushpipe.c
@@ -0,0 +1,420 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ * Copyright (C) 1999 Adrian Likins and Tor Lillqvist
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpbase/gimpparasiteio.h"
+#include "libgimpmath/gimpmath.h"
+
+#include "core-types.h"
+
+#include "gimpbrush-private.h"
+#include "gimpbrushpipe.h"
+#include "gimpbrushpipe-load.h"
+#include "gimpbrushpipe-save.h"
+#include "gimptempbuf.h"
+
+
+static void gimp_brush_pipe_finalize (GObject *object);
+
+static gint64 gimp_brush_pipe_get_memsize (GimpObject *object,
+ gint64 *gui_size);
+
+static gboolean gimp_brush_pipe_get_popup_size (GimpViewable *viewable,
+ gint width,
+ gint height,
+ gboolean dot_for_dot,
+ gint *popup_width,
+ gint *popup_height);
+
+static const gchar * gimp_brush_pipe_get_extension (GimpData *data);
+static void gimp_brush_pipe_copy (GimpData *data,
+ GimpData *src_data);
+
+static void gimp_brush_pipe_begin_use (GimpBrush *brush);
+static void gimp_brush_pipe_end_use (GimpBrush *brush);
+static GimpBrush * gimp_brush_pipe_select_brush (GimpBrush *brush,
+ const GimpCoords *last_coords,
+ const GimpCoords *current_coords);
+static gboolean gimp_brush_pipe_want_null_motion (GimpBrush *brush,
+ const GimpCoords *last_coords,
+ const GimpCoords *current_coords);
+
+
+G_DEFINE_TYPE (GimpBrushPipe, gimp_brush_pipe, GIMP_TYPE_BRUSH);
+
+#define parent_class gimp_brush_pipe_parent_class
+
+
+static void
+gimp_brush_pipe_class_init (GimpBrushPipeClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpObjectClass *gimp_object_class = GIMP_OBJECT_CLASS (klass);
+ GimpViewableClass *viewable_class = GIMP_VIEWABLE_CLASS (klass);
+ GimpDataClass *data_class = GIMP_DATA_CLASS (klass);
+ GimpBrushClass *brush_class = GIMP_BRUSH_CLASS (klass);
+
+ object_class->finalize = gimp_brush_pipe_finalize;
+
+ gimp_object_class->get_memsize = gimp_brush_pipe_get_memsize;
+
+ viewable_class->get_popup_size = gimp_brush_pipe_get_popup_size;
+
+ data_class->save = gimp_brush_pipe_save;
+ data_class->get_extension = gimp_brush_pipe_get_extension;
+ data_class->copy = gimp_brush_pipe_copy;
+
+ brush_class->begin_use = gimp_brush_pipe_begin_use;
+ brush_class->end_use = gimp_brush_pipe_end_use;
+ brush_class->select_brush = gimp_brush_pipe_select_brush;
+ brush_class->want_null_motion = gimp_brush_pipe_want_null_motion;
+}
+
+static void
+gimp_brush_pipe_init (GimpBrushPipe *pipe)
+{
+ pipe->current = NULL;
+ pipe->dimension = 0;
+ pipe->rank = NULL;
+ pipe->stride = NULL;
+ pipe->n_brushes = 0;
+ pipe->brushes = NULL;
+ pipe->select = NULL;
+ pipe->index = NULL;
+}
+
+static void
+gimp_brush_pipe_finalize (GObject *object)
+{
+ GimpBrushPipe *pipe = GIMP_BRUSH_PIPE (object);
+
+ g_clear_pointer (&pipe->rank, g_free);
+ g_clear_pointer (&pipe->stride, g_free);
+ g_clear_pointer (&pipe->select, g_free);
+ g_clear_pointer (&pipe->index, g_free);
+ g_clear_pointer (&pipe->params, g_free);
+
+ if (pipe->brushes)
+ {
+ gint i;
+
+ for (i = 0; i < pipe->n_brushes; i++)
+ if (pipe->brushes[i])
+ g_object_unref (pipe->brushes[i]);
+
+ g_clear_pointer (&pipe->brushes, g_free);
+ }
+
+ GIMP_BRUSH (pipe)->priv->mask = NULL;
+ GIMP_BRUSH (pipe)->priv->pixmap = NULL;
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static gint64
+gimp_brush_pipe_get_memsize (GimpObject *object,
+ gint64 *gui_size)
+{
+ GimpBrushPipe *pipe = GIMP_BRUSH_PIPE (object);
+ gint64 memsize = 0;
+ gint i;
+
+ memsize += pipe->dimension * (sizeof (gint) /* rank */ +
+ sizeof (gint) /* stride */ +
+ sizeof (PipeSelectModes));
+
+ for (i = 0; i < pipe->n_brushes; i++)
+ memsize += gimp_object_get_memsize (GIMP_OBJECT (pipe->brushes[i]),
+ gui_size);
+
+ return memsize + GIMP_OBJECT_CLASS (parent_class)->get_memsize (object,
+ gui_size);
+}
+
+static gboolean
+gimp_brush_pipe_get_popup_size (GimpViewable *viewable,
+ gint width,
+ gint height,
+ gboolean dot_for_dot,
+ gint *popup_width,
+ gint *popup_height)
+{
+ return gimp_viewable_get_size (viewable, popup_width, popup_height);
+}
+
+static const gchar *
+gimp_brush_pipe_get_extension (GimpData *data)
+{
+ return GIMP_BRUSH_PIPE_FILE_EXTENSION;
+}
+
+static void
+gimp_brush_pipe_copy (GimpData *data,
+ GimpData *src_data)
+{
+ GimpBrushPipe *pipe = GIMP_BRUSH_PIPE (data);
+ GimpBrushPipe *src_pipe = GIMP_BRUSH_PIPE (src_data);
+ gint i;
+
+ pipe->dimension = src_pipe->dimension;
+
+ g_clear_pointer (&pipe->rank, g_free);
+ pipe->rank = g_memdup (src_pipe->rank,
+ pipe->dimension * sizeof (gint));
+
+ g_clear_pointer (&pipe->stride, g_free);
+ pipe->stride = g_memdup (src_pipe->stride,
+ pipe->dimension * sizeof (gint));
+
+ g_clear_pointer (&pipe->select, g_free);
+ pipe->select = g_memdup (src_pipe->select,
+ pipe->dimension * sizeof (PipeSelectModes));
+
+ g_clear_pointer (&pipe->index, g_free);
+ pipe->index = g_memdup (src_pipe->index,
+ pipe->dimension * sizeof (gint));
+
+ for (i = 0; i < pipe->n_brushes; i++)
+ if (pipe->brushes[i])
+ g_object_unref (pipe->brushes[i]);
+ g_clear_pointer (&pipe->brushes, g_free);
+
+ pipe->n_brushes = src_pipe->n_brushes;
+
+ pipe->brushes = g_new0 (GimpBrush *, pipe->n_brushes);
+ for (i = 0; i < pipe->n_brushes; i++)
+ if (src_pipe->brushes[i])
+ {
+ pipe->brushes[i] =
+ GIMP_BRUSH (gimp_data_duplicate (GIMP_DATA (src_pipe->brushes[i])));
+ gimp_object_set_name (GIMP_OBJECT (pipe->brushes[i]),
+ gimp_object_get_name (src_pipe->brushes[i]));
+ }
+
+ g_clear_pointer (&pipe->params, g_free);
+ pipe->params = g_strdup (src_pipe->params);
+
+ pipe->current = pipe->brushes[0];
+
+ GIMP_BRUSH (pipe)->priv->spacing = pipe->current->priv->spacing;
+ GIMP_BRUSH (pipe)->priv->x_axis = pipe->current->priv->x_axis;
+ GIMP_BRUSH (pipe)->priv->y_axis = pipe->current->priv->y_axis;
+ GIMP_BRUSH (pipe)->priv->mask = pipe->current->priv->mask;
+ GIMP_BRUSH (pipe)->priv->pixmap = pipe->current->priv->pixmap;
+
+ gimp_data_dirty (data);
+}
+
+static void
+gimp_brush_pipe_begin_use (GimpBrush *brush)
+{
+ GimpBrushPipe *pipe = GIMP_BRUSH_PIPE (brush);
+ gint i;
+
+ GIMP_BRUSH_CLASS (parent_class)->begin_use (brush);
+
+ for (i = 0; i < pipe->n_brushes; i++)
+ if (pipe->brushes[i])
+ gimp_brush_begin_use (pipe->brushes[i]);
+}
+
+static void
+gimp_brush_pipe_end_use (GimpBrush *brush)
+{
+ GimpBrushPipe *pipe = GIMP_BRUSH_PIPE (brush);
+ gint i;
+
+ GIMP_BRUSH_CLASS (parent_class)->end_use (brush);
+
+ for (i = 0; i < pipe->n_brushes; i++)
+ if (pipe->brushes[i])
+ gimp_brush_end_use (pipe->brushes[i]);
+}
+
+static GimpBrush *
+gimp_brush_pipe_select_brush (GimpBrush *brush,
+ const GimpCoords *last_coords,
+ const GimpCoords *current_coords)
+{
+ GimpBrushPipe *pipe = GIMP_BRUSH_PIPE (brush);
+ gint i, brushix, ix;
+
+ if (pipe->n_brushes == 1)
+ return GIMP_BRUSH (pipe->current);
+
+ brushix = 0;
+ for (i = 0; i < pipe->dimension; i++)
+ {
+ switch (pipe->select[i])
+ {
+ case PIPE_SELECT_INCREMENTAL:
+ ix = (pipe->index[i] + 1) % pipe->rank[i];
+ break;
+
+ case PIPE_SELECT_ANGULAR:
+ /* Coords angle is already nomalized,
+ * offset by 90 degrees is still needed
+ * because hoses were made PS compatible*/
+ ix = (gint) RINT ((1.0 - current_coords->direction + 0.25) * pipe->rank[i]) % pipe->rank[i];
+ break;
+
+ case PIPE_SELECT_VELOCITY:
+ ix = ROUND (current_coords->velocity * pipe->rank[i]);
+ break;
+
+ case PIPE_SELECT_RANDOM:
+ /* This probably isn't the right way */
+ ix = g_random_int_range (0, pipe->rank[i]);
+ break;
+
+ case PIPE_SELECT_PRESSURE:
+ ix = RINT (current_coords->pressure * (pipe->rank[i] - 1));
+ break;
+
+ case PIPE_SELECT_TILT_X:
+ ix = RINT (current_coords->xtilt / 2.0 * pipe->rank[i]) + pipe->rank[i] / 2;
+ break;
+
+ case PIPE_SELECT_TILT_Y:
+ ix = RINT (current_coords->ytilt / 2.0 * pipe->rank[i]) + pipe->rank[i] / 2;
+ break;
+
+ case PIPE_SELECT_CONSTANT:
+ default:
+ ix = pipe->index[i];
+ break;
+ }
+
+ pipe->index[i] = CLAMP (ix, 0, pipe->rank[i] - 1);
+ brushix += pipe->stride[i] * pipe->index[i];
+ }
+
+ /* Make sure is inside bounds */
+ brushix = CLAMP (brushix, 0, pipe->n_brushes - 1);
+
+ pipe->current = pipe->brushes[brushix];
+
+ return GIMP_BRUSH (pipe->current);
+}
+
+static gboolean
+gimp_brush_pipe_want_null_motion (GimpBrush *brush,
+ const GimpCoords *last_coords,
+ const GimpCoords *current_coords)
+{
+ GimpBrushPipe *pipe = GIMP_BRUSH_PIPE (brush);
+ gint i;
+
+ if (pipe->n_brushes == 1)
+ return TRUE;
+
+ for (i = 0; i < pipe->dimension; i++)
+ if (pipe->select[i] == PIPE_SELECT_ANGULAR)
+ return FALSE;
+
+ return TRUE;
+}
+
+
+/* public functions */
+
+gboolean
+gimp_brush_pipe_set_params (GimpBrushPipe *pipe,
+ const gchar *paramstring)
+{
+ gint totalcells;
+ gint i;
+
+ g_return_val_if_fail (GIMP_IS_BRUSH_PIPE (pipe), FALSE);
+ g_return_val_if_fail (pipe->dimension == 0, FALSE); /* only on a new pipe! */
+
+ if (paramstring && *paramstring)
+ {
+ GimpPixPipeParams params;
+
+ gimp_pixpipe_params_init (&params);
+ gimp_pixpipe_params_parse (paramstring, &params);
+
+ pipe->dimension = params.dim;
+ pipe->rank = g_new0 (gint, pipe->dimension);
+ pipe->select = g_new0 (PipeSelectModes, pipe->dimension);
+ pipe->index = g_new0 (gint, pipe->dimension);
+ /* placement is not used at all ?? */
+
+ for (i = 0; i < pipe->dimension; i++)
+ {
+ pipe->rank[i] = MAX (1, params.rank[i]);
+
+ if (strcmp (params.selection[i], "incremental") == 0)
+ pipe->select[i] = PIPE_SELECT_INCREMENTAL;
+ else if (strcmp (params.selection[i], "angular") == 0)
+ pipe->select[i] = PIPE_SELECT_ANGULAR;
+ else if (strcmp (params.selection[i], "velocity") == 0)
+ pipe->select[i] = PIPE_SELECT_VELOCITY;
+ else if (strcmp (params.selection[i], "random") == 0)
+ pipe->select[i] = PIPE_SELECT_RANDOM;
+ else if (strcmp (params.selection[i], "pressure") == 0)
+ pipe->select[i] = PIPE_SELECT_PRESSURE;
+ else if (strcmp (params.selection[i], "xtilt") == 0)
+ pipe->select[i] = PIPE_SELECT_TILT_X;
+ else if (strcmp (params.selection[i], "ytilt") == 0)
+ pipe->select[i] = PIPE_SELECT_TILT_Y;
+ else
+ pipe->select[i] = PIPE_SELECT_CONSTANT;
+
+ pipe->index[i] = 0;
+ }
+
+ gimp_pixpipe_params_free (&params);
+
+ pipe->params = g_strdup (paramstring);
+ }
+ else
+ {
+ pipe->dimension = 1;
+ pipe->rank = g_new (gint, 1);
+ pipe->rank[0] = pipe->n_brushes;
+ pipe->select = g_new (PipeSelectModes, 1);
+ pipe->select[0] = PIPE_SELECT_INCREMENTAL;
+ pipe->index = g_new (gint, 1);
+ pipe->index[0] = 0;
+ }
+
+ totalcells = 1; /* Not all necessarily present, maybe */
+ for (i = 0; i < pipe->dimension; i++)
+ totalcells *= pipe->rank[i];
+
+ pipe->stride = g_new0 (gint, pipe->dimension);
+
+ for (i = 0; i < pipe->dimension; i++)
+ {
+ if (i == 0)
+ pipe->stride[i] = totalcells / pipe->rank[i];
+ else
+ pipe->stride[i] = pipe->stride[i-1] / pipe->rank[i];
+ }
+
+ if (pipe->stride[pipe->dimension - 1] != 1)
+ return FALSE;
+
+ return TRUE;
+}
diff --git a/app/core/gimpbrushpipe.h b/app/core/gimpbrushpipe.h
new file mode 100644
index 0000000..9615210
--- /dev/null
+++ b/app/core/gimpbrushpipe.h
@@ -0,0 +1,80 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ * Copyright (C) 1999 Adrian Likins and Tor Lillqvist
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_BRUSH_PIPE_H__
+#define __GIMP_BRUSH_PIPE_H__
+
+
+#include "gimpbrush.h"
+
+
+#define GIMP_TYPE_BRUSH_PIPE (gimp_brush_pipe_get_type ())
+#define GIMP_BRUSH_PIPE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_BRUSH_PIPE, GimpBrushPipe))
+#define GIMP_BRUSH_PIPE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_BRUSH_PIPE, GimpBrushPipeClass))
+#define GIMP_IS_BRUSH_PIPE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_BRUSH_PIPE))
+#define GIMP_IS_BRUSH_PIPE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_BRUSH_PIPE))
+#define GIMP_BRUSH_PIPE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_BRUSH_PIPE, GimpBrushPipeClass))
+
+
+typedef enum
+{
+ PIPE_SELECT_CONSTANT,
+ PIPE_SELECT_INCREMENTAL,
+ PIPE_SELECT_ANGULAR,
+ PIPE_SELECT_VELOCITY,
+ PIPE_SELECT_RANDOM,
+ PIPE_SELECT_PRESSURE,
+ PIPE_SELECT_TILT_X,
+ PIPE_SELECT_TILT_Y
+} PipeSelectModes;
+
+
+typedef struct _GimpBrushPipeClass GimpBrushPipeClass;
+
+struct _GimpBrushPipe
+{
+ GimpBrush parent_instance;
+
+ gint dimension;
+ gint *rank; /* Size in each dimension */
+ gint *stride; /* Aux for indexing */
+ PipeSelectModes *select; /* One mode per dimension */
+
+ gint *index; /* Current index for incremental dimensions */
+
+ gint n_brushes; /* Might be less than the product of the
+ * ranks in some odd special case */
+ GimpBrush **brushes;
+ GimpBrush *current; /* Currently selected brush */
+
+ gchar *params; /* For pipe <-> image conversion */
+};
+
+struct _GimpBrushPipeClass
+{
+ GimpBrushClass parent_class;
+};
+
+
+GType gimp_brush_pipe_get_type (void) G_GNUC_CONST;
+
+gboolean gimp_brush_pipe_set_params (GimpBrushPipe *pipe,
+ const gchar *paramstring);
+
+
+#endif /* __GIMP_BRUSH_PIPE_H__ */
diff --git a/app/core/gimpbuffer.c b/app/core/gimpbuffer.c
new file mode 100644
index 0000000..c713ed8
--- /dev/null
+++ b/app/core/gimpbuffer.c
@@ -0,0 +1,541 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <cairo.h>
+#include <gegl.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+#include "libgimpbase/gimpbase.h"
+#include "libgimpcolor/gimpcolor.h"
+
+#include "core-types.h"
+
+#include "gegl/gimp-babl.h"
+#include "gegl/gimp-gegl-loops.h"
+#include "gegl/gimp-gegl-utils.h"
+
+#include "gimp-memsize.h"
+#include "gimpbuffer.h"
+#include "gimpimage.h"
+#include "gimptempbuf.h"
+
+
+static void gimp_color_managed_iface_init (GimpColorManagedInterface *iface);
+
+static void gimp_buffer_finalize (GObject *object);
+
+static gint64 gimp_buffer_get_memsize (GimpObject *object,
+ gint64 *gui_size);
+
+static gboolean gimp_buffer_get_size (GimpViewable *viewable,
+ gint *width,
+ gint *height);
+static void gimp_buffer_get_preview_size (GimpViewable *viewable,
+ gint size,
+ gboolean is_popup,
+ gboolean dot_for_dot,
+ gint *popup_width,
+ gint *popup_height);
+static gboolean gimp_buffer_get_popup_size (GimpViewable *viewable,
+ gint width,
+ gint height,
+ gboolean dot_for_dot,
+ gint *popup_width,
+ gint *popup_height);
+static GimpTempBuf * gimp_buffer_get_new_preview (GimpViewable *viewable,
+ GimpContext *context,
+ gint width,
+ gint height);
+static GdkPixbuf * gimp_buffer_get_new_pixbuf (GimpViewable *viewable,
+ GimpContext *context,
+ gint width,
+ gint height);
+static gchar * gimp_buffer_get_description (GimpViewable *viewable,
+ gchar **tooltip);
+
+static const guint8 *
+ gimp_buffer_color_managed_get_icc_profile (GimpColorManaged *managed,
+ gsize *len);
+static GimpColorProfile *
+ gimp_buffer_color_managed_get_color_profile (GimpColorManaged *managed);
+static void
+ gimp_buffer_color_managed_profile_changed (GimpColorManaged *managed);
+
+
+G_DEFINE_TYPE_WITH_CODE (GimpBuffer, gimp_buffer, GIMP_TYPE_VIEWABLE,
+ G_IMPLEMENT_INTERFACE (GIMP_TYPE_COLOR_MANAGED,
+ gimp_color_managed_iface_init))
+
+#define parent_class gimp_buffer_parent_class
+
+
+static void
+gimp_buffer_class_init (GimpBufferClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpObjectClass *gimp_object_class = GIMP_OBJECT_CLASS (klass);
+ GimpViewableClass *viewable_class = GIMP_VIEWABLE_CLASS (klass);
+
+ object_class->finalize = gimp_buffer_finalize;
+
+ gimp_object_class->get_memsize = gimp_buffer_get_memsize;
+
+ viewable_class->default_icon_name = "edit-paste";
+ viewable_class->name_editable = TRUE;
+ viewable_class->get_size = gimp_buffer_get_size;
+ viewable_class->get_preview_size = gimp_buffer_get_preview_size;
+ viewable_class->get_popup_size = gimp_buffer_get_popup_size;
+ viewable_class->get_new_preview = gimp_buffer_get_new_preview;
+ viewable_class->get_new_pixbuf = gimp_buffer_get_new_pixbuf;
+ viewable_class->get_description = gimp_buffer_get_description;
+}
+
+static void
+gimp_color_managed_iface_init (GimpColorManagedInterface *iface)
+{
+ iface->get_icc_profile = gimp_buffer_color_managed_get_icc_profile;
+ iface->get_color_profile = gimp_buffer_color_managed_get_color_profile;
+ iface->profile_changed = gimp_buffer_color_managed_profile_changed;
+}
+
+static void
+gimp_buffer_init (GimpBuffer *buffer)
+{
+}
+
+static void
+gimp_buffer_finalize (GObject *object)
+{
+ GimpBuffer *buffer = GIMP_BUFFER (object);
+
+ g_clear_object (&buffer->buffer);
+
+ gimp_buffer_set_color_profile (buffer, NULL);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static gint64
+gimp_buffer_get_memsize (GimpObject *object,
+ gint64 *gui_size)
+{
+ GimpBuffer *buffer = GIMP_BUFFER (object);
+ gint64 memsize = 0;
+
+ memsize += gimp_gegl_buffer_get_memsize (buffer->buffer);
+ memsize += gimp_g_object_get_memsize (G_OBJECT (buffer->color_profile));
+
+ return memsize + GIMP_OBJECT_CLASS (parent_class)->get_memsize (object,
+ gui_size);
+}
+
+static gboolean
+gimp_buffer_get_size (GimpViewable *viewable,
+ gint *width,
+ gint *height)
+{
+ GimpBuffer *buffer = GIMP_BUFFER (viewable);
+
+ *width = gimp_buffer_get_width (buffer);
+ *height = gimp_buffer_get_height (buffer);
+
+ return TRUE;
+}
+
+static void
+gimp_buffer_get_preview_size (GimpViewable *viewable,
+ gint size,
+ gboolean is_popup,
+ gboolean dot_for_dot,
+ gint *width,
+ gint *height)
+{
+ GimpBuffer *buffer = GIMP_BUFFER (viewable);
+
+ gimp_viewable_calc_preview_size (gimp_buffer_get_width (buffer),
+ gimp_buffer_get_height (buffer),
+ size,
+ size,
+ dot_for_dot, 1.0, 1.0,
+ width,
+ height,
+ NULL);
+}
+
+static gboolean
+gimp_buffer_get_popup_size (GimpViewable *viewable,
+ gint width,
+ gint height,
+ gboolean dot_for_dot,
+ gint *popup_width,
+ gint *popup_height)
+{
+ GimpBuffer *buffer;
+ gint buffer_width;
+ gint buffer_height;
+
+ buffer = GIMP_BUFFER (viewable);
+ buffer_width = gimp_buffer_get_width (buffer);
+ buffer_height = gimp_buffer_get_height (buffer);
+
+ if (buffer_width > width || buffer_height > height)
+ {
+ gboolean scaling_up;
+
+ gimp_viewable_calc_preview_size (buffer_width,
+ buffer_height,
+ width * 2,
+ height * 2,
+ dot_for_dot, 1.0, 1.0,
+ popup_width,
+ popup_height,
+ &scaling_up);
+
+ if (scaling_up)
+ {
+ *popup_width = buffer_width;
+ *popup_height = buffer_height;
+ }
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static GimpTempBuf *
+gimp_buffer_get_new_preview (GimpViewable *viewable,
+ GimpContext *context,
+ gint width,
+ gint height)
+{
+ GimpBuffer *buffer = GIMP_BUFFER (viewable);
+ const Babl *format = gimp_buffer_get_format (buffer);
+ GimpTempBuf *preview;
+
+ if (babl_format_is_palette (format))
+ format = gimp_babl_format (GIMP_RGB, GIMP_PRECISION_U8_GAMMA,
+ babl_format_has_alpha (format));
+ else
+ format = gimp_babl_format (gimp_babl_format_get_base_type (format),
+ gimp_babl_precision (GIMP_COMPONENT_TYPE_U8,
+ gimp_babl_format_get_linear (format)),
+ babl_format_has_alpha (format));
+
+ preview = gimp_temp_buf_new (width, height, format);
+
+ gegl_buffer_get (buffer->buffer, GEGL_RECTANGLE (0, 0, width, height),
+ MIN ((gdouble) width / (gdouble) gimp_buffer_get_width (buffer),
+ (gdouble) height / (gdouble) gimp_buffer_get_height (buffer)),
+ format,
+ gimp_temp_buf_get_data (preview),
+ GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
+
+ return preview;
+}
+
+static GdkPixbuf *
+gimp_buffer_get_new_pixbuf (GimpViewable *viewable,
+ GimpContext *context,
+ gint width,
+ gint height)
+{
+ GimpBuffer *buffer = GIMP_BUFFER (viewable);
+ GdkPixbuf *pixbuf;
+ gdouble scale;
+
+ pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8,
+ width, height);
+
+ scale = MIN ((gdouble) width / (gdouble) gimp_buffer_get_width (buffer),
+ (gdouble) height / (gdouble) gimp_buffer_get_height (buffer));
+
+ if (buffer->color_profile)
+ {
+ GimpColorProfile *srgb_profile;
+ GimpTempBuf *temp_buf;
+ GeglBuffer *src_buf;
+ GeglBuffer *dest_buf;
+
+ srgb_profile = gimp_color_profile_new_rgb_srgb ();
+
+ temp_buf = gimp_temp_buf_new (width, height,
+ gimp_buffer_get_format (buffer));
+
+ gegl_buffer_get (buffer->buffer,
+ GEGL_RECTANGLE (0, 0, width, height),
+ scale,
+ gimp_temp_buf_get_format (temp_buf),
+ gimp_temp_buf_get_data (temp_buf),
+ GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_CLAMP);
+
+ src_buf = gimp_temp_buf_create_buffer (temp_buf);
+ dest_buf = gimp_pixbuf_create_buffer (pixbuf);
+
+ gimp_temp_buf_unref (temp_buf);
+
+ gimp_gegl_convert_color_profile (src_buf,
+ GEGL_RECTANGLE (0, 0, width, height),
+ buffer->color_profile,
+ dest_buf,
+ GEGL_RECTANGLE (0, 0, 0, 0),
+ srgb_profile,
+ GIMP_COLOR_RENDERING_INTENT_PERCEPTUAL,
+ TRUE,
+ NULL);
+
+ g_object_unref (src_buf);
+ g_object_unref (dest_buf);
+
+ g_object_unref (srgb_profile);
+ }
+ else
+ {
+ gegl_buffer_get (buffer->buffer,
+ GEGL_RECTANGLE (0, 0, width, height),
+ scale,
+ gimp_pixbuf_get_format (pixbuf),
+ gdk_pixbuf_get_pixels (pixbuf),
+ gdk_pixbuf_get_rowstride (pixbuf),
+ GEGL_ABYSS_CLAMP);
+ }
+
+ return pixbuf;
+}
+
+static gchar *
+gimp_buffer_get_description (GimpViewable *viewable,
+ gchar **tooltip)
+{
+ GimpBuffer *buffer = GIMP_BUFFER (viewable);
+
+ return g_strdup_printf ("%s (%d × %d)",
+ gimp_object_get_name (buffer),
+ gimp_buffer_get_width (buffer),
+ gimp_buffer_get_height (buffer));
+}
+
+static const guint8 *
+gimp_buffer_color_managed_get_icc_profile (GimpColorManaged *managed,
+ gsize *len)
+{
+ GimpBuffer *buffer = GIMP_BUFFER (managed);
+
+ if (buffer->color_profile)
+ return gimp_color_profile_get_icc_profile (buffer->color_profile, len);
+
+ return NULL;
+}
+
+static GimpColorProfile *
+gimp_buffer_color_managed_get_color_profile (GimpColorManaged *managed)
+{
+ GimpBuffer *buffer = GIMP_BUFFER (managed);
+
+ if (buffer->color_profile)
+ return buffer->color_profile;
+
+ return gimp_babl_format_get_color_profile (gimp_buffer_get_format (buffer));
+}
+
+static void
+gimp_buffer_color_managed_profile_changed (GimpColorManaged *managed)
+{
+ gimp_viewable_invalidate_preview (GIMP_VIEWABLE (managed));
+}
+
+
+/* public functions */
+
+GimpBuffer *
+gimp_buffer_new (GeglBuffer *buffer,
+ const gchar *name,
+ gint offset_x,
+ gint offset_y,
+ gboolean copy_pixels)
+{
+ GimpBuffer *gimp_buffer;
+
+ g_return_val_if_fail (GEGL_IS_BUFFER (buffer), NULL);
+ g_return_val_if_fail (name != NULL, NULL);
+
+ gimp_buffer = g_object_new (GIMP_TYPE_BUFFER,
+ "name", name,
+ NULL);
+
+ if (copy_pixels)
+ gimp_buffer->buffer = gimp_gegl_buffer_dup (buffer);
+ else
+ gimp_buffer->buffer = g_object_ref (buffer);
+
+ gimp_buffer->offset_x = offset_x;
+ gimp_buffer->offset_y = offset_y;
+
+ return gimp_buffer;
+}
+
+GimpBuffer *
+gimp_buffer_new_from_pixbuf (GdkPixbuf *pixbuf,
+ const gchar *name,
+ gint offset_x,
+ gint offset_y)
+{
+ GimpBuffer *gimp_buffer;
+ GeglBuffer *buffer;
+ guint8 *icc_data;
+ gsize icc_len;
+ GimpColorProfile *profile = NULL;
+
+ g_return_val_if_fail (GDK_IS_PIXBUF (pixbuf), NULL);
+ g_return_val_if_fail (name != NULL, NULL);
+
+ buffer = gimp_pixbuf_create_buffer (pixbuf);
+
+ gimp_buffer = gimp_buffer_new (buffer, name,
+ offset_x, offset_y, FALSE);
+
+ icc_data = gimp_pixbuf_get_icc_profile (pixbuf, &icc_len);
+ if (icc_data)
+ {
+ profile = gimp_color_profile_new_from_icc_profile (icc_data, icc_len,
+ NULL);
+ g_free (icc_data);
+ }
+
+ if (! profile && gdk_pixbuf_get_colorspace (pixbuf) == GDK_COLORSPACE_RGB)
+ {
+ profile = gimp_color_profile_new_rgb_srgb ();
+ }
+
+ if (profile)
+ {
+ gimp_buffer_set_color_profile (gimp_buffer, profile);
+ g_object_unref (profile);
+ }
+
+ g_object_unref (buffer);
+
+ return gimp_buffer;
+}
+
+gint
+gimp_buffer_get_width (GimpBuffer *buffer)
+{
+ g_return_val_if_fail (GIMP_IS_BUFFER (buffer), 0);
+
+ return gegl_buffer_get_width (buffer->buffer);
+}
+
+gint
+gimp_buffer_get_height (GimpBuffer *buffer)
+{
+ g_return_val_if_fail (GIMP_IS_BUFFER (buffer), 0);
+
+ return gegl_buffer_get_height (buffer->buffer);
+}
+
+const Babl *
+gimp_buffer_get_format (GimpBuffer *buffer)
+{
+ g_return_val_if_fail (GIMP_IS_BUFFER (buffer), NULL);
+
+ return gegl_buffer_get_format (buffer->buffer);
+}
+
+GeglBuffer *
+gimp_buffer_get_buffer (GimpBuffer *buffer)
+{
+ g_return_val_if_fail (GIMP_IS_BUFFER (buffer), NULL);
+
+ return buffer->buffer;
+}
+
+void
+gimp_buffer_set_resolution (GimpBuffer *buffer,
+ gdouble resolution_x,
+ gdouble resolution_y)
+{
+ g_return_if_fail (GIMP_IS_BUFFER (buffer));
+ g_return_if_fail (resolution_x >= 0.0 && resolution_x <= GIMP_MAX_RESOLUTION);
+ g_return_if_fail (resolution_y >= 0.0 && resolution_y <= GIMP_MAX_RESOLUTION);
+
+ buffer->resolution_x = resolution_x;
+ buffer->resolution_y = resolution_y;
+}
+
+gboolean
+gimp_buffer_get_resolution (GimpBuffer *buffer,
+ gdouble *resolution_x,
+ gdouble *resolution_y)
+{
+ g_return_val_if_fail (GIMP_IS_BUFFER (buffer), FALSE);
+
+ if (buffer->resolution_x > 0.0 &&
+ buffer->resolution_y > 0.0)
+ {
+ if (resolution_x) *resolution_x = buffer->resolution_x;
+ if (resolution_y) *resolution_y = buffer->resolution_y;
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+void
+gimp_buffer_set_unit (GimpBuffer *buffer,
+ GimpUnit unit)
+{
+ g_return_if_fail (GIMP_IS_BUFFER (buffer));
+ g_return_if_fail (unit > GIMP_UNIT_PIXEL);
+
+ buffer->unit = unit;
+}
+
+GimpUnit
+gimp_buffer_get_unit (GimpBuffer *buffer)
+{
+ g_return_val_if_fail (GIMP_IS_BUFFER (buffer), GIMP_UNIT_PIXEL);
+
+ return buffer->unit;
+}
+
+void
+gimp_buffer_set_color_profile (GimpBuffer *buffer,
+ GimpColorProfile *profile)
+{
+ g_return_if_fail (GIMP_IS_BUFFER (buffer));
+ g_return_if_fail (profile == NULL || GIMP_IS_COLOR_PROFILE (profile));
+
+ if (profile != buffer->color_profile)
+ {
+ g_clear_object (&buffer->color_profile);
+
+ if (profile)
+ buffer->color_profile = g_object_ref (profile);
+ }
+}
+
+GimpColorProfile *
+gimp_buffer_get_color_profile (GimpBuffer *buffer)
+{
+ g_return_val_if_fail (GIMP_IS_BUFFER (buffer), NULL);
+
+ return buffer->color_profile;
+}
diff --git a/app/core/gimpbuffer.h b/app/core/gimpbuffer.h
new file mode 100644
index 0000000..2949bae
--- /dev/null
+++ b/app/core/gimpbuffer.h
@@ -0,0 +1,90 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_BUFFER_H__
+#define __GIMP_BUFFER_H__
+
+
+#include "gimpviewable.h"
+
+
+#define GIMP_TYPE_BUFFER (gimp_buffer_get_type ())
+#define GIMP_BUFFER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_BUFFER, GimpBuffer))
+#define GIMP_BUFFER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_BUFFER, GimpBufferClass))
+#define GIMP_IS_BUFFER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_BUFFER))
+#define GIMP_IS_BUFFER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_BUFFER))
+#define GIMP_BUFFER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_BUFFER, GimpBufferClass))
+
+
+typedef struct _GimpBufferClass GimpBufferClass;
+
+struct _GimpBuffer
+{
+ GimpViewable parent_instance;
+
+ GeglBuffer *buffer;
+ gint offset_x;
+ gint offset_y;
+
+ gdouble resolution_x;
+ gdouble resolution_y;
+ GimpUnit unit;
+
+ GimpColorProfile *color_profile;
+};
+
+struct _GimpBufferClass
+{
+ GimpViewableClass parent_class;
+};
+
+
+GType gimp_buffer_get_type (void) G_GNUC_CONST;
+
+GimpBuffer * gimp_buffer_new (GeglBuffer *buffer,
+ const gchar *name,
+ gint offset_x,
+ gint offset_y,
+ gboolean copy_pixels);
+GimpBuffer * gimp_buffer_new_from_pixbuf (GdkPixbuf *pixbuf,
+ const gchar *name,
+ gint offset_x,
+ gint offset_y);
+
+gint gimp_buffer_get_width (GimpBuffer *buffer);
+gint gimp_buffer_get_height (GimpBuffer *buffer);
+const Babl * gimp_buffer_get_format (GimpBuffer *buffer);
+
+GeglBuffer * gimp_buffer_get_buffer (GimpBuffer *buffer);
+
+void gimp_buffer_set_resolution (GimpBuffer *buffer,
+ gdouble resolution_x,
+ gdouble resolution_y);
+gboolean gimp_buffer_get_resolution (GimpBuffer *buffer,
+ gdouble *resolution_x,
+ gdouble *resolution_y);
+
+void gimp_buffer_set_unit (GimpBuffer *buffer,
+ GimpUnit unit);
+GimpUnit gimp_buffer_get_unit (GimpBuffer *buffer);
+
+void gimp_buffer_set_color_profile (GimpBuffer *buffer,
+ GimpColorProfile *profile);
+GimpColorProfile * gimp_buffer_get_color_profile (GimpBuffer *buffer);
+
+
+#endif /* __GIMP_BUFFER_H__ */
diff --git a/app/core/gimpcancelable.c b/app/core/gimpcancelable.c
new file mode 100644
index 0000000..9cb5f12
--- /dev/null
+++ b/app/core/gimpcancelable.c
@@ -0,0 +1,72 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpcancelable.c
+ * Copyright (C) 2018 Ell
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "core-types.h"
+
+#include "gimpcancelable.h"
+#include "gimpmarshal.h"
+
+
+enum
+{
+ CANCEL,
+ LAST_SIGNAL
+};
+
+
+G_DEFINE_INTERFACE (GimpCancelable, gimp_cancelable, G_TYPE_OBJECT)
+
+
+static guint cancelable_signals[LAST_SIGNAL] = { 0 };
+
+
+/* private functions */
+
+
+static void
+gimp_cancelable_default_init (GimpCancelableInterface *iface)
+{
+ cancelable_signals[CANCEL] =
+ g_signal_new ("cancel",
+ G_TYPE_FROM_CLASS (iface),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpCancelableInterface, cancel),
+ NULL, NULL,
+ gimp_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+}
+
+
+/* public functions */
+
+
+void
+gimp_cancelable_cancel (GimpCancelable *cancelable)
+{
+ g_return_if_fail (GIMP_IS_CANCELABLE (cancelable));
+
+ g_signal_emit (cancelable, cancelable_signals[CANCEL], 0);
+}
diff --git a/app/core/gimpcancelable.h b/app/core/gimpcancelable.h
new file mode 100644
index 0000000..dc2a655
--- /dev/null
+++ b/app/core/gimpcancelable.h
@@ -0,0 +1,47 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-1997 Spencer Kimball and Peter Mattis
+ *
+ * gimpcancelable.h
+ * Copyright (C) 2018 Ell
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_CANCELABLE_H__
+#define __GIMP_CANCELABLE_H__
+
+
+#define GIMP_TYPE_CANCELABLE (gimp_cancelable_get_type ())
+#define GIMP_IS_CANCELABLE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CANCELABLE))
+#define GIMP_CANCELABLE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CANCELABLE, GimpCancelable))
+#define GIMP_CANCELABLE_GET_INTERFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), GIMP_TYPE_CANCELABLE, GimpCancelableInterface))
+
+
+typedef struct _GimpCancelableInterface GimpCancelableInterface;
+
+struct _GimpCancelableInterface
+{
+ GTypeInterface base_iface;
+
+ /* signals */
+ void (* cancel) (GimpCancelable *cancelable);
+};
+
+
+GType gimp_cancelable_get_type (void) G_GNUC_CONST;
+
+void gimp_cancelable_cancel (GimpCancelable *cancelable);
+
+
+#endif /* __GIMP_CANCELABLE_H__ */
diff --git a/app/core/gimpchannel-combine.c b/app/core/gimpchannel-combine.c
new file mode 100644
index 0000000..8326f65
--- /dev/null
+++ b/app/core/gimpchannel-combine.c
@@ -0,0 +1,471 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpbase/gimpbase.h"
+#include "libgimpmath/gimpmath.h"
+
+#include "core-types.h"
+
+#include "gegl/gimp-gegl-mask-combine.h"
+
+#include "gimpchannel.h"
+#include "gimpchannel-combine.h"
+
+
+typedef struct
+{
+ GeglRectangle rect;
+
+ gboolean bounds_known;
+ gboolean empty;
+ GeglRectangle bounds;
+} GimpChannelCombineData;
+
+
+/* local function prototypes */
+
+static void gimp_channel_combine_clear (GimpChannel *mask,
+ const GeglRectangle *rect);
+static void gimp_channel_combine_clear_complement (GimpChannel *mask,
+ const GeglRectangle *rect);
+
+static gboolean gimp_channel_combine_start (GimpChannel *mask,
+ GimpChannelOps op,
+ const GeglRectangle *rect,
+ gboolean full_extent,
+ gboolean full_value,
+ GimpChannelCombineData *data);
+static void gimp_channel_combine_end (GimpChannel *mask,
+ GimpChannelCombineData *data);
+
+
+/* private functions */
+
+static void
+gimp_channel_combine_clear (GimpChannel *mask,
+ const GeglRectangle *rect)
+{
+ GeglBuffer *buffer;
+ GeglRectangle area;
+ GeglRectangle update_area;
+
+ if (mask->bounds_known && mask->empty)
+ return;
+
+ buffer = gimp_drawable_get_buffer (GIMP_DRAWABLE (mask));
+
+ if (rect)
+ {
+ if (rect->width <= 0 || rect->height <= 0)
+ return;
+
+ if (mask->bounds_known)
+ {
+ if (! gegl_rectangle_intersect (&area,
+ GEGL_RECTANGLE (mask->x1,
+ mask->y1,
+ mask->x2 - mask->x1,
+ mask->y2 - mask->y1),
+ rect))
+ {
+ return;
+ }
+ }
+ else
+ {
+ area = *rect;
+ }
+
+ update_area = area;
+ }
+ else
+ {
+ if (mask->bounds_known)
+ {
+ area.x = mask->x1;
+ area.y = mask->y1;
+ area.width = mask->x2 - mask->x1;
+ area.height = mask->y2 - mask->y1;
+ }
+ else
+ {
+ area.x = 0;
+ area.y = 0;
+ area.width = gimp_item_get_width (GIMP_ITEM (mask));
+ area.height = gimp_item_get_height (GIMP_ITEM (mask));
+ }
+
+ update_area = area;
+
+ gegl_rectangle_align_to_buffer (&area, &area, buffer,
+ GEGL_RECTANGLE_ALIGNMENT_SUPERSET);
+ }
+
+ gegl_buffer_clear (buffer, &area);
+
+ gimp_drawable_update (GIMP_DRAWABLE (mask),
+ update_area.x, update_area.y,
+ update_area.width, update_area.height);
+}
+
+static void
+gimp_channel_combine_clear_complement (GimpChannel *mask,
+ const GeglRectangle *rect)
+{
+ gint width = gimp_item_get_width (GIMP_ITEM (mask));
+ gint height = gimp_item_get_height (GIMP_ITEM (mask));
+
+ gimp_channel_combine_clear (
+ mask,
+ GEGL_RECTANGLE (0,
+ 0,
+ width,
+ rect->y));
+
+ gimp_channel_combine_clear (
+ mask,
+ GEGL_RECTANGLE (0,
+ rect->y + rect->height,
+ width,
+ height - (rect->y + rect->height)));
+
+ gimp_channel_combine_clear (
+ mask,
+ GEGL_RECTANGLE (0,
+ rect->y,
+ rect->x,
+ rect->height));
+
+ gimp_channel_combine_clear (
+ mask,
+ GEGL_RECTANGLE (rect->x + rect->width,
+ rect->y,
+ width - (rect->x + rect->width),
+ rect->height));
+}
+
+static gboolean
+gimp_channel_combine_start (GimpChannel *mask,
+ GimpChannelOps op,
+ const GeglRectangle *rect,
+ gboolean full_extent,
+ gboolean full_value,
+ GimpChannelCombineData *data)
+{
+ GeglBuffer *buffer = gimp_drawable_get_buffer (GIMP_DRAWABLE (mask));
+ GeglRectangle extent;
+ gboolean intersects;
+
+ extent.x = 0;
+ extent.y = 0;
+ extent.width = gimp_item_get_width (GIMP_ITEM (mask));
+ extent.height = gimp_item_get_height (GIMP_ITEM (mask));
+
+ intersects = gegl_rectangle_intersect (&data->rect, rect, &extent);
+
+ data->bounds_known = mask->bounds_known;
+ data->empty = mask->empty;
+
+ data->bounds.x = mask->x1;
+ data->bounds.y = mask->y1;
+ data->bounds.width = mask->x2 - mask->x1;
+ data->bounds.height = mask->y2 - mask->y1;
+
+ gegl_buffer_freeze_changed (buffer);
+
+ /* Determine new boundary */
+ switch (op)
+ {
+ case GIMP_CHANNEL_OP_REPLACE:
+ gimp_channel_combine_clear (mask, NULL);
+
+ if (! intersects)
+ {
+ data->bounds_known = TRUE;
+ data->empty = TRUE;
+
+ return FALSE;
+ }
+
+ data->bounds_known = FALSE;
+
+ if (full_extent)
+ {
+ data->bounds_known = TRUE;
+ data->empty = FALSE;
+ data->bounds = data->rect;
+ }
+ break;
+
+ case GIMP_CHANNEL_OP_ADD:
+ if (! intersects)
+ return FALSE;
+
+ data->bounds_known = FALSE;
+
+ if (full_extent && (mask->bounds_known ||
+ gegl_rectangle_equal (&data->rect, &extent)))
+ {
+ data->bounds_known = TRUE;
+ data->empty = FALSE;
+
+ if (mask->bounds_known && ! mask->empty)
+ {
+ gegl_rectangle_bounding_box (&data->bounds,
+ &data->bounds, &data->rect);
+ }
+ else
+ {
+ data->bounds = data->rect;
+ }
+ }
+ break;
+
+ case GIMP_CHANNEL_OP_SUBTRACT:
+ if (intersects && mask->bounds_known)
+ {
+ if (mask->empty)
+ {
+ intersects = FALSE;
+ }
+ else
+ {
+ intersects = gegl_rectangle_intersect (&data->rect,
+ &data->rect,
+ &data->bounds);
+ }
+ }
+
+ if (! intersects)
+ return FALSE;
+
+ if (full_value &&
+ gegl_rectangle_contains (&data->rect,
+ mask->bounds_known ? &data->bounds :
+ &extent))
+ {
+ gimp_channel_combine_clear (mask, NULL);
+
+ data->bounds_known = TRUE;
+ data->empty = TRUE;
+
+ return FALSE;
+ }
+
+ data->bounds_known = FALSE;
+
+ gegl_buffer_set_abyss (buffer, &data->rect);
+ break;
+
+ case GIMP_CHANNEL_OP_INTERSECT:
+ if (intersects && mask->bounds_known)
+ {
+ if (mask->empty)
+ {
+ intersects = FALSE;
+ }
+ else
+ {
+ intersects = gegl_rectangle_intersect (&data->rect,
+ &data->rect,
+ &data->bounds);
+ }
+ }
+
+ if (! intersects)
+ {
+ gimp_channel_combine_clear (mask, NULL);
+
+ data->bounds_known = TRUE;
+ data->empty = TRUE;
+
+ return FALSE;
+ }
+
+ if (full_value && mask->bounds_known &&
+ gegl_rectangle_contains (&data->rect, &data->bounds))
+ {
+ return FALSE;
+ }
+
+ data->bounds_known = FALSE;
+
+ gimp_channel_combine_clear_complement (mask, &data->rect);
+
+ gegl_buffer_set_abyss (buffer, &data->rect);
+ break;
+ }
+
+ return TRUE;
+}
+
+static void
+gimp_channel_combine_end (GimpChannel *mask,
+ GimpChannelCombineData *data)
+{
+ GeglBuffer *buffer = gimp_drawable_get_buffer (GIMP_DRAWABLE (mask));
+
+ gegl_buffer_set_abyss (buffer, gegl_buffer_get_extent (buffer));
+
+ gegl_buffer_thaw_changed (buffer);
+
+ mask->bounds_known = data->bounds_known;
+
+ if (data->bounds_known)
+ {
+ mask->empty = data->empty;
+
+ if (data->empty)
+ {
+ mask->x1 = 0;
+ mask->y1 = 0;
+ mask->x2 = gimp_item_get_width (GIMP_ITEM (mask));
+ mask->y2 = gimp_item_get_height (GIMP_ITEM (mask));
+ }
+ else
+ {
+ mask->x1 = data->bounds.x;
+ mask->y1 = data->bounds.y;
+ mask->x2 = data->bounds.x + data->bounds.width;
+ mask->y2 = data->bounds.y + data->bounds.height;
+ }
+ }
+
+ gimp_drawable_update (GIMP_DRAWABLE (mask),
+ data->rect.x, data->rect.y,
+ data->rect.width, data->rect.height);
+}
+
+
+/* public functions */
+
+void
+gimp_channel_combine_rect (GimpChannel *mask,
+ GimpChannelOps op,
+ gint x,
+ gint y,
+ gint w,
+ gint h)
+{
+ GimpChannelCombineData data;
+
+ g_return_if_fail (GIMP_IS_CHANNEL (mask));
+
+ if (gimp_channel_combine_start (mask, op, GEGL_RECTANGLE (x, y, w, h),
+ TRUE, TRUE, &data))
+ {
+ GeglBuffer *buffer = gimp_drawable_get_buffer (GIMP_DRAWABLE (mask));
+
+ gimp_gegl_mask_combine_rect (buffer, op, x, y, w, h);
+ }
+
+ gimp_channel_combine_end (mask, &data);
+}
+
+void
+gimp_channel_combine_ellipse (GimpChannel *mask,
+ GimpChannelOps op,
+ gint x,
+ gint y,
+ gint w,
+ gint h,
+ gboolean antialias)
+{
+ gimp_channel_combine_ellipse_rect (mask, op, x, y, w, h,
+ w / 2.0, h / 2.0, antialias);
+}
+
+void
+gimp_channel_combine_ellipse_rect (GimpChannel *mask,
+ GimpChannelOps op,
+ gint x,
+ gint y,
+ gint w,
+ gint h,
+ gdouble rx,
+ gdouble ry,
+ gboolean antialias)
+{
+ GimpChannelCombineData data;
+
+ g_return_if_fail (GIMP_IS_CHANNEL (mask));
+
+ if (gimp_channel_combine_start (mask, op, GEGL_RECTANGLE (x, y, w, h),
+ TRUE, FALSE, &data))
+ {
+ GeglBuffer *buffer = gimp_drawable_get_buffer (GIMP_DRAWABLE (mask));
+
+ gimp_gegl_mask_combine_ellipse_rect (buffer, op, x, y, w, h,
+ rx, ry, antialias);
+ }
+
+ gimp_channel_combine_end (mask, &data);
+}
+
+void
+gimp_channel_combine_mask (GimpChannel *mask,
+ GimpChannel *add_on,
+ GimpChannelOps op,
+ gint off_x,
+ gint off_y)
+{
+ GeglBuffer *add_on_buffer;
+
+ g_return_if_fail (GIMP_IS_CHANNEL (mask));
+ g_return_if_fail (GIMP_IS_CHANNEL (add_on));
+
+ add_on_buffer = gimp_drawable_get_buffer (GIMP_DRAWABLE (add_on));
+
+ gimp_channel_combine_buffer (mask, add_on_buffer,
+ op, off_x, off_y);
+}
+
+void
+gimp_channel_combine_buffer (GimpChannel *mask,
+ GeglBuffer *add_on_buffer,
+ GimpChannelOps op,
+ gint off_x,
+ gint off_y)
+{
+ GimpChannelCombineData data;
+
+ g_return_if_fail (GIMP_IS_CHANNEL (mask));
+ g_return_if_fail (GEGL_IS_BUFFER (add_on_buffer));
+
+ if (gimp_channel_combine_start (mask, op,
+ GEGL_RECTANGLE (
+ off_x + gegl_buffer_get_x (add_on_buffer),
+ off_y + gegl_buffer_get_y (add_on_buffer),
+ gegl_buffer_get_width (add_on_buffer),
+ gegl_buffer_get_height (add_on_buffer)),
+ FALSE, FALSE, &data))
+ {
+ GeglBuffer *buffer = gimp_drawable_get_buffer (GIMP_DRAWABLE (mask));
+
+ gimp_gegl_mask_combine_buffer (buffer, add_on_buffer, op,
+ off_x, off_y);
+ }
+
+ gimp_channel_combine_end (mask, &data);
+}
diff --git a/app/core/gimpchannel-combine.h b/app/core/gimpchannel-combine.h
new file mode 100644
index 0000000..de1f125
--- /dev/null
+++ b/app/core/gimpchannel-combine.h
@@ -0,0 +1,56 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_CHANNEL_COMBINE_H__
+#define __GIMP_CHANNEL_COMBINE_H__
+
+
+void gimp_channel_combine_rect (GimpChannel *mask,
+ GimpChannelOps op,
+ gint x,
+ gint y,
+ gint w,
+ gint h);
+void gimp_channel_combine_ellipse (GimpChannel *mask,
+ GimpChannelOps op,
+ gint x,
+ gint y,
+ gint w,
+ gint h,
+ gboolean antialias);
+void gimp_channel_combine_ellipse_rect (GimpChannel *mask,
+ GimpChannelOps op,
+ gint x,
+ gint y,
+ gint w,
+ gint h,
+ gdouble rx,
+ gdouble ry,
+ gboolean antialias);
+void gimp_channel_combine_mask (GimpChannel *mask,
+ GimpChannel *add_on,
+ GimpChannelOps op,
+ gint off_x,
+ gint off_y);
+void gimp_channel_combine_buffer (GimpChannel *mask,
+ GeglBuffer *add_on_buffer,
+ GimpChannelOps op,
+ gint off_x,
+ gint off_y);
+
+
+#endif /* __GIMP_CHANNEL_COMBINE_H__ */
diff --git a/app/core/gimpchannel-select.c b/app/core/gimpchannel-select.c
new file mode 100644
index 0000000..b76280e
--- /dev/null
+++ b/app/core/gimpchannel-select.c
@@ -0,0 +1,605 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <cairo.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpbase/gimpbase.h"
+#include "libgimpmath/gimpmath.h"
+
+#include "core-types.h"
+
+#include "gegl/gimp-gegl-apply-operation.h"
+#include "gegl/gimp-gegl-loops.h"
+#include "gegl/gimp-gegl-mask-combine.h"
+
+#include "gimpchannel.h"
+#include "gimpchannel-select.h"
+#include "gimpchannel-combine.h"
+#include "gimppickable.h"
+#include "gimppickable-contiguous-region.h"
+#include "gimpscanconvert.h"
+
+#include "vectors/gimpstroke.h"
+#include "vectors/gimpvectors.h"
+
+#include "gimp-intl.h"
+
+
+/* basic selection functions */
+
+void
+gimp_channel_select_rectangle (GimpChannel *channel,
+ gint x,
+ gint y,
+ gint w,
+ gint h,
+ GimpChannelOps op,
+ gboolean feather,
+ gdouble feather_radius_x,
+ gdouble feather_radius_y,
+ gboolean push_undo)
+{
+ g_return_if_fail (GIMP_IS_CHANNEL (channel));
+ g_return_if_fail (gimp_item_is_attached (GIMP_ITEM (channel)));
+
+ if (push_undo)
+ gimp_channel_push_undo (channel, C_("undo-type", "Rectangle Select"));
+
+ /* if feathering for rect, make a new mask with the
+ * rectangle and feather that with the old mask
+ */
+ if (feather)
+ {
+ GimpItem *item = GIMP_ITEM (channel);
+ GeglBuffer *add_on;
+
+ add_on = gegl_buffer_new (GEGL_RECTANGLE (0, 0,
+ gimp_item_get_width (item),
+ gimp_item_get_height (item)),
+ babl_format ("Y float"));
+
+ gimp_gegl_mask_combine_rect (add_on, GIMP_CHANNEL_OP_REPLACE, x, y, w, h);
+
+ gimp_gegl_apply_feather (add_on, NULL, NULL, add_on, NULL,
+ feather_radius_x,
+ feather_radius_y,
+ TRUE);
+
+ gimp_channel_combine_buffer (channel, add_on, op, 0, 0);
+ g_object_unref (add_on);
+ }
+ else
+ {
+ gimp_channel_combine_rect (channel, op, x, y, w, h);
+ }
+}
+
+void
+gimp_channel_select_ellipse (GimpChannel *channel,
+ gint x,
+ gint y,
+ gint w,
+ gint h,
+ GimpChannelOps op,
+ gboolean antialias,
+ gboolean feather,
+ gdouble feather_radius_x,
+ gdouble feather_radius_y,
+ gboolean push_undo)
+{
+ g_return_if_fail (GIMP_IS_CHANNEL (channel));
+ g_return_if_fail (gimp_item_is_attached (GIMP_ITEM (channel)));
+
+ if (push_undo)
+ gimp_channel_push_undo (channel, C_("undo-type", "Ellipse Select"));
+
+ /* if feathering for rect, make a new mask with the
+ * rectangle and feather that with the old mask
+ */
+ if (feather)
+ {
+ GimpItem *item = GIMP_ITEM (channel);
+ GeglBuffer *add_on;
+
+ add_on = gegl_buffer_new (GEGL_RECTANGLE (0, 0,
+ gimp_item_get_width (item),
+ gimp_item_get_height (item)),
+ babl_format ("Y float"));
+
+ gimp_gegl_mask_combine_ellipse (add_on, GIMP_CHANNEL_OP_REPLACE,
+ x, y, w, h, antialias);
+
+ gimp_gegl_apply_feather (add_on, NULL, NULL, add_on, NULL,
+ feather_radius_x,
+ feather_radius_y,
+ TRUE);
+
+ gimp_channel_combine_buffer (channel, add_on, op, 0, 0);
+ g_object_unref (add_on);
+ }
+ else
+ {
+ gimp_channel_combine_ellipse (channel, op, x, y, w, h, antialias);
+ }
+}
+
+void
+gimp_channel_select_round_rect (GimpChannel *channel,
+ gint x,
+ gint y,
+ gint w,
+ gint h,
+ gdouble corner_radius_x,
+ gdouble corner_radius_y,
+ GimpChannelOps op,
+ gboolean antialias,
+ gboolean feather,
+ gdouble feather_radius_x,
+ gdouble feather_radius_y,
+ gboolean push_undo)
+{
+ g_return_if_fail (GIMP_IS_CHANNEL (channel));
+ g_return_if_fail (gimp_item_is_attached (GIMP_ITEM (channel)));
+
+ if (push_undo)
+ gimp_channel_push_undo (channel, C_("undo-type", "Rounded Rectangle Select"));
+
+ /* if feathering for rect, make a new mask with the
+ * rectangle and feather that with the old mask
+ */
+ if (feather)
+ {
+ GimpItem *item = GIMP_ITEM (channel);
+ GeglBuffer *add_on;
+
+ add_on = gegl_buffer_new (GEGL_RECTANGLE (0, 0,
+ gimp_item_get_width (item),
+ gimp_item_get_height (item)),
+ babl_format ("Y float"));
+
+ gimp_gegl_mask_combine_ellipse_rect (add_on, GIMP_CHANNEL_OP_REPLACE,
+ x, y, w, h,
+ corner_radius_x, corner_radius_y,
+ antialias);
+
+ gimp_gegl_apply_feather (add_on, NULL, NULL, add_on, NULL,
+ feather_radius_x,
+ feather_radius_y,
+ TRUE);
+
+ gimp_channel_combine_buffer (channel, add_on, op, 0, 0);
+ g_object_unref (add_on);
+ }
+ else
+ {
+ gimp_channel_combine_ellipse_rect (channel, op, x, y, w, h,
+ corner_radius_x, corner_radius_y,
+ antialias);
+ }
+}
+
+/* select by GimpScanConvert functions */
+
+void
+gimp_channel_select_scan_convert (GimpChannel *channel,
+ const gchar *undo_desc,
+ GimpScanConvert *scan_convert,
+ gint offset_x,
+ gint offset_y,
+ GimpChannelOps op,
+ gboolean antialias,
+ gboolean feather,
+ gdouble feather_radius_x,
+ gdouble feather_radius_y,
+ gboolean push_undo)
+{
+ GimpItem *item;
+ GeglBuffer *add_on;
+
+ g_return_if_fail (GIMP_IS_CHANNEL (channel));
+ g_return_if_fail (gimp_item_is_attached (GIMP_ITEM (channel)));
+ g_return_if_fail (undo_desc != NULL);
+ g_return_if_fail (scan_convert != NULL);
+
+ if (push_undo)
+ gimp_channel_push_undo (channel, undo_desc);
+
+ item = GIMP_ITEM (channel);
+
+ add_on = gegl_buffer_new (GEGL_RECTANGLE (0, 0,
+ gimp_item_get_width (item),
+ gimp_item_get_height (item)),
+ babl_format ("Y float"));
+
+ gimp_scan_convert_render (scan_convert, add_on,
+ offset_x, offset_y, antialias);
+
+ if (feather)
+ gimp_gegl_apply_feather (add_on, NULL, NULL, add_on, NULL,
+ feather_radius_x,
+ feather_radius_y,
+ TRUE);
+
+ gimp_channel_combine_buffer (channel, add_on, op, 0, 0);
+ g_object_unref (add_on);
+}
+
+void
+gimp_channel_select_polygon (GimpChannel *channel,
+ const gchar *undo_desc,
+ gint n_points,
+ const GimpVector2 *points,
+ GimpChannelOps op,
+ gboolean antialias,
+ gboolean feather,
+ gdouble feather_radius_x,
+ gdouble feather_radius_y,
+ gboolean push_undo)
+{
+ GimpScanConvert *scan_convert;
+
+ g_return_if_fail (GIMP_IS_CHANNEL (channel));
+ g_return_if_fail (gimp_item_is_attached (GIMP_ITEM (channel)));
+ g_return_if_fail (undo_desc != NULL);
+
+ scan_convert = gimp_scan_convert_new ();
+
+ gimp_scan_convert_add_polyline (scan_convert, n_points, points, TRUE);
+
+ gimp_channel_select_scan_convert (channel, undo_desc, scan_convert, 0, 0,
+ op, antialias, feather,
+ feather_radius_x, feather_radius_y,
+ push_undo);
+
+ gimp_scan_convert_free (scan_convert);
+}
+
+void
+gimp_channel_select_vectors (GimpChannel *channel,
+ const gchar *undo_desc,
+ GimpVectors *vectors,
+ GimpChannelOps op,
+ gboolean antialias,
+ gboolean feather,
+ gdouble feather_radius_x,
+ gdouble feather_radius_y,
+ gboolean push_undo)
+{
+ const GimpBezierDesc *bezier;
+
+ g_return_if_fail (GIMP_IS_CHANNEL (channel));
+ g_return_if_fail (gimp_item_is_attached (GIMP_ITEM (channel)));
+ g_return_if_fail (undo_desc != NULL);
+ g_return_if_fail (GIMP_IS_VECTORS (vectors));
+
+ bezier = gimp_vectors_get_bezier (vectors);
+
+ if (bezier && bezier->num_data > 4)
+ {
+ GimpScanConvert *scan_convert;
+
+ scan_convert = gimp_scan_convert_new ();
+ gimp_scan_convert_add_bezier (scan_convert, bezier);
+
+ gimp_channel_select_scan_convert (channel, undo_desc, scan_convert, 0, 0,
+ op, antialias, feather,
+ feather_radius_x, feather_radius_y,
+ push_undo);
+
+ gimp_scan_convert_free (scan_convert);
+ }
+}
+
+
+/* select by GimpChannel functions */
+
+void
+gimp_channel_select_buffer (GimpChannel *channel,
+ const gchar *undo_desc,
+ GeglBuffer *add_on,
+ gint offset_x,
+ gint offset_y,
+ GimpChannelOps op,
+ gboolean feather,
+ gdouble feather_radius_x,
+ gdouble feather_radius_y)
+{
+ g_return_if_fail (GIMP_IS_CHANNEL (channel));
+ g_return_if_fail (gimp_item_is_attached (GIMP_ITEM (channel)));
+ g_return_if_fail (undo_desc != NULL);
+ g_return_if_fail (GEGL_IS_BUFFER (add_on));
+
+ gimp_channel_push_undo (channel, undo_desc);
+
+ if (feather)
+ {
+ GimpItem *item = GIMP_ITEM (channel);
+ GeglBuffer *add_on2;
+
+ add_on2 = gegl_buffer_new (GEGL_RECTANGLE (0, 0,
+ gimp_item_get_width (item),
+ gimp_item_get_height (item)),
+ babl_format ("Y float"));
+
+ gimp_gegl_mask_combine_buffer (add_on2, add_on,
+ GIMP_CHANNEL_OP_REPLACE,
+ offset_x, offset_y);
+
+ gimp_gegl_apply_feather (add_on2, NULL, NULL, add_on2, NULL,
+ feather_radius_x,
+ feather_radius_y,
+ TRUE);
+
+ gimp_channel_combine_buffer (channel, add_on2, op, 0, 0);
+ g_object_unref (add_on2);
+ }
+ else
+ {
+ gimp_channel_combine_buffer (channel, add_on, op, offset_x, offset_y);
+ }
+}
+
+void
+gimp_channel_select_channel (GimpChannel *channel,
+ const gchar *undo_desc,
+ GimpChannel *add_on,
+ gint offset_x,
+ gint offset_y,
+ GimpChannelOps op,
+ gboolean feather,
+ gdouble feather_radius_x,
+ gdouble feather_radius_y)
+{
+ g_return_if_fail (GIMP_IS_CHANNEL (channel));
+ g_return_if_fail (gimp_item_is_attached (GIMP_ITEM (channel)));
+ g_return_if_fail (undo_desc != NULL);
+ g_return_if_fail (GIMP_IS_CHANNEL (add_on));
+
+ gimp_channel_select_buffer (channel, undo_desc,
+ gimp_drawable_get_buffer (GIMP_DRAWABLE (add_on)),
+ offset_x, offset_y, op,
+ feather,
+ feather_radius_x, feather_radius_y);
+}
+
+void
+gimp_channel_select_alpha (GimpChannel *channel,
+ GimpDrawable *drawable,
+ GimpChannelOps op,
+ gboolean feather,
+ gdouble feather_radius_x,
+ gdouble feather_radius_y)
+{
+ GimpItem *item;
+ GimpChannel *add_on;
+ gint off_x, off_y;
+
+ g_return_if_fail (GIMP_IS_CHANNEL (channel));
+ g_return_if_fail (gimp_item_is_attached (GIMP_ITEM (channel)));
+ g_return_if_fail (GIMP_IS_DRAWABLE (drawable));
+
+ item = GIMP_ITEM (channel);
+
+ if (gimp_drawable_has_alpha (drawable))
+ {
+ add_on = gimp_channel_new_from_alpha (gimp_item_get_image (item),
+ drawable, NULL, NULL);
+ }
+ else
+ {
+ /* no alpha is equivalent to completely opaque alpha,
+ * so simply select the whole layer's extents. --mitch
+ */
+ add_on = gimp_channel_new_mask (gimp_item_get_image (item),
+ gimp_item_get_width (GIMP_ITEM (drawable)),
+ gimp_item_get_height (GIMP_ITEM (drawable)));
+ gimp_channel_all (add_on, FALSE);
+ }
+
+ gimp_item_get_offset (GIMP_ITEM (drawable), &off_x, &off_y);
+
+ gimp_channel_select_channel (channel, C_("undo-type", "Alpha to Selection"), add_on,
+ off_x, off_y,
+ op, feather,
+ feather_radius_x,
+ feather_radius_y);
+ g_object_unref (add_on);
+}
+
+void
+gimp_channel_select_component (GimpChannel *channel,
+ GimpChannelType component,
+ GimpChannelOps op,
+ gboolean feather,
+ gdouble feather_radius_x,
+ gdouble feather_radius_y)
+{
+ GimpItem *item;
+ GimpChannel *add_on;
+ const gchar *desc;
+ gchar *undo_desc;
+
+ g_return_if_fail (GIMP_IS_CHANNEL (channel));
+ g_return_if_fail (gimp_item_is_attached (GIMP_ITEM (channel)));
+
+ item = GIMP_ITEM (channel);
+
+ add_on = gimp_channel_new_from_component (gimp_item_get_image (item),
+ component, NULL, NULL);
+
+ if (feather)
+ gimp_channel_feather (add_on,
+ feather_radius_x,
+ feather_radius_y,
+ TRUE,
+ FALSE /* no undo */);
+
+ gimp_enum_get_value (GIMP_TYPE_CHANNEL_TYPE, component,
+ NULL, NULL, &desc, NULL);
+
+ undo_desc = g_strdup_printf (C_("undo-type", "%s Channel to Selection"), desc);
+
+ gimp_channel_select_channel (channel, undo_desc, add_on,
+ 0, 0, op,
+ FALSE, 0.0, 0.0);
+
+ g_free (undo_desc);
+ g_object_unref (add_on);
+}
+
+void
+gimp_channel_select_fuzzy (GimpChannel *channel,
+ GimpDrawable *drawable,
+ gboolean sample_merged,
+ gint x,
+ gint y,
+ gfloat threshold,
+ gboolean select_transparent,
+ GimpSelectCriterion select_criterion,
+ gboolean diagonal_neighbors,
+ GimpChannelOps op,
+ gboolean antialias,
+ gboolean feather,
+ gdouble feather_radius_x,
+ gdouble feather_radius_y)
+{
+ GimpPickable *pickable;
+ GeglBuffer *add_on;
+ gint add_on_x = 0;
+ gint add_on_y = 0;
+
+ g_return_if_fail (GIMP_IS_CHANNEL (channel));
+ g_return_if_fail (gimp_item_is_attached (GIMP_ITEM (channel)));
+ g_return_if_fail (GIMP_IS_DRAWABLE (drawable));
+
+ if (sample_merged)
+ pickable = GIMP_PICKABLE (gimp_item_get_image (GIMP_ITEM (drawable)));
+ else
+ pickable = GIMP_PICKABLE (drawable);
+
+ add_on = gimp_pickable_contiguous_region_by_seed (pickable,
+ antialias,
+ threshold,
+ select_transparent,
+ select_criterion,
+ diagonal_neighbors,
+ x, y);
+
+ if (! sample_merged)
+ gimp_item_get_offset (GIMP_ITEM (drawable), &add_on_x, &add_on_y);
+
+ gimp_channel_select_buffer (channel, C_("undo-type", "Fuzzy Select"),
+ add_on, add_on_x, add_on_y,
+ op,
+ feather,
+ feather_radius_x,
+ feather_radius_y);
+ g_object_unref (add_on);
+}
+
+void
+gimp_channel_select_by_color (GimpChannel *channel,
+ GimpDrawable *drawable,
+ gboolean sample_merged,
+ const GimpRGB *color,
+ gfloat threshold,
+ gboolean select_transparent,
+ GimpSelectCriterion select_criterion,
+ GimpChannelOps op,
+ gboolean antialias,
+ gboolean feather,
+ gdouble feather_radius_x,
+ gdouble feather_radius_y)
+{
+ GimpPickable *pickable;
+ GeglBuffer *add_on;
+ gint add_on_x = 0;
+ gint add_on_y = 0;
+
+ g_return_if_fail (GIMP_IS_CHANNEL (channel));
+ g_return_if_fail (gimp_item_is_attached (GIMP_ITEM (channel)));
+ g_return_if_fail (GIMP_IS_DRAWABLE (drawable));
+ g_return_if_fail (color != NULL);
+
+ if (sample_merged)
+ pickable = GIMP_PICKABLE (gimp_item_get_image (GIMP_ITEM (drawable)));
+ else
+ pickable = GIMP_PICKABLE (drawable);
+
+ add_on = gimp_pickable_contiguous_region_by_color (pickable,
+ antialias,
+ threshold,
+ select_transparent,
+ select_criterion,
+ color);
+
+ if (! sample_merged)
+ gimp_item_get_offset (GIMP_ITEM (drawable), &add_on_x, &add_on_y);
+
+ gimp_channel_select_buffer (channel, C_("undo-type", "Select by Color"),
+ add_on, add_on_x, add_on_y,
+ op,
+ feather,
+ feather_radius_x,
+ feather_radius_y);
+ g_object_unref (add_on);
+}
+
+void
+gimp_channel_select_by_index (GimpChannel *channel,
+ GimpDrawable *drawable,
+ gint index,
+ GimpChannelOps op,
+ gboolean feather,
+ gdouble feather_radius_x,
+ gdouble feather_radius_y)
+{
+ GeglBuffer *add_on;
+ gint add_on_x = 0;
+ gint add_on_y = 0;
+
+ g_return_if_fail (GIMP_IS_CHANNEL (channel));
+ g_return_if_fail (gimp_item_is_attached (GIMP_ITEM (channel)));
+ g_return_if_fail (GIMP_IS_DRAWABLE (drawable));
+ g_return_if_fail (gimp_drawable_is_indexed (drawable));
+
+ add_on = gegl_buffer_new (GEGL_RECTANGLE (0, 0,
+ gimp_item_get_width (GIMP_ITEM (drawable)),
+ gimp_item_get_height (GIMP_ITEM (drawable))),
+ babl_format ("Y float"));
+
+ gimp_gegl_index_to_mask (gimp_drawable_get_buffer (drawable), NULL,
+ gimp_drawable_get_format_without_alpha (drawable),
+ add_on, NULL,
+ index);
+
+ gimp_item_get_offset (GIMP_ITEM (drawable), &add_on_x, &add_on_y);
+
+ gimp_channel_select_buffer (channel, C_("undo-type", "Select by Indexed Color"),
+ add_on, add_on_x, add_on_y,
+ op,
+ feather,
+ feather_radius_x,
+ feather_radius_y);
+ g_object_unref (add_on);
+}
diff --git a/app/core/gimpchannel-select.h b/app/core/gimpchannel-select.h
new file mode 100644
index 0000000..ca2680d
--- /dev/null
+++ b/app/core/gimpchannel-select.h
@@ -0,0 +1,160 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_CHANNEL_SELECT_H__
+#define __GIMP_CHANNEL_SELECT_H__
+
+
+/* basic selection functions */
+
+void gimp_channel_select_rectangle (GimpChannel *channel,
+ gint x,
+ gint y,
+ gint w,
+ gint h,
+ GimpChannelOps op,
+ gboolean feather,
+ gdouble feather_radius_x,
+ gdouble feather_radius_y,
+ gboolean push_undo);
+void gimp_channel_select_ellipse (GimpChannel *channel,
+ gint x,
+ gint y,
+ gint w,
+ gint h,
+ GimpChannelOps op,
+ gboolean antialias,
+ gboolean feather,
+ gdouble feather_radius_x,
+ gdouble feather_radius_y,
+ gboolean push_undo);
+void gimp_channel_select_round_rect (GimpChannel *channel,
+ gint x,
+ gint y,
+ gint w,
+ gint h,
+ gdouble corner_radius_y,
+ gdouble corner_radius_x,
+ GimpChannelOps op,
+ gboolean antialias,
+ gboolean feather,
+ gdouble feather_radius_x,
+ gdouble feather_radius_y,
+ gboolean push_undo);
+
+/* select by GimpScanConvert functions */
+
+void gimp_channel_select_scan_convert (GimpChannel *channel,
+ const gchar *undo_desc,
+ GimpScanConvert *scan_convert,
+ gint offset_x,
+ gint offset_y,
+ GimpChannelOps op,
+ gboolean antialias,
+ gboolean feather,
+ gdouble feather_radius_x,
+ gdouble feather_radius_y,
+ gboolean push_undo);
+void gimp_channel_select_polygon (GimpChannel *channel,
+ const gchar *undo_desc,
+ gint n_points,
+ const GimpVector2 *points,
+ GimpChannelOps op,
+ gboolean antialias,
+ gboolean feather,
+ gdouble feather_radius_x,
+ gdouble feather_radius_y,
+ gboolean push_undo);
+void gimp_channel_select_vectors (GimpChannel *channel,
+ const gchar *undo_desc,
+ GimpVectors *vectors,
+ GimpChannelOps op,
+ gboolean antialias,
+ gboolean feather,
+ gdouble feather_radius_x,
+ gdouble feather_radius_y,
+ gboolean push_undo);
+void gimp_channel_select_buffer (GimpChannel *channel,
+ const gchar *undo_desc,
+ GeglBuffer *add_on,
+ gint offset_x,
+ gint offset_y,
+ GimpChannelOps op,
+ gboolean feather,
+ gdouble feather_radius_x,
+ gdouble feather_radius_y);
+
+
+/* select by GimpChannel functions */
+
+void gimp_channel_select_channel (GimpChannel *channel,
+ const gchar *undo_desc,
+ GimpChannel *add_on,
+ gint offset_x,
+ gint offset_y,
+ GimpChannelOps op,
+ gboolean feather,
+ gdouble feather_radius_x,
+ gdouble feather_radius_y);
+void gimp_channel_select_alpha (GimpChannel *channel,
+ GimpDrawable *drawable,
+ GimpChannelOps op,
+ gboolean feather,
+ gdouble feather_radius_x,
+ gdouble feather_radius_y);
+void gimp_channel_select_component (GimpChannel *channel,
+ GimpChannelType component,
+ GimpChannelOps op,
+ gboolean feather,
+ gdouble feather_radius_x,
+ gdouble feather_radius_y);
+void gimp_channel_select_fuzzy (GimpChannel *channel,
+ GimpDrawable *drawable,
+ gboolean sample_merged,
+ gint x,
+ gint y,
+ gfloat threshold,
+ gboolean select_transparent,
+ GimpSelectCriterion select_criterion,
+ gboolean diagonal_neighbors,
+ GimpChannelOps op,
+ gboolean antialias,
+ gboolean feather,
+ gdouble feather_radius_x,
+ gdouble feather_radius_y);
+void gimp_channel_select_by_color (GimpChannel *channel,
+ GimpDrawable *drawable,
+ gboolean sample_merged,
+ const GimpRGB *color,
+ gfloat threshold,
+ gboolean select_transparent,
+ GimpSelectCriterion select_criterion,
+ GimpChannelOps op,
+ gboolean antialias,
+ gboolean feather,
+ gdouble feather_radius_x,
+ gdouble feather_radius_y);
+void gimp_channel_select_by_index (GimpChannel *channel,
+ GimpDrawable *drawable,
+ gint index,
+ GimpChannelOps op,
+ gboolean feather,
+ gdouble feather_radius_x,
+ gdouble feather_radius_y);
+
+
+#endif /* __GIMP_CHANNEL_SELECT_H__ */
diff --git a/app/core/gimpchannel.c b/app/core/gimpchannel.c
new file mode 100644
index 0000000..784551a
--- /dev/null
+++ b/app/core/gimpchannel.c
@@ -0,0 +1,1956 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <cairo.h>
+#include <gegl.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+#include "libgimpbase/gimpbase.h"
+#include "libgimpmath/gimpmath.h"
+#include "libgimpcolor/gimpcolor.h"
+
+#include "core-types.h"
+
+#include "paint/gimppaintcore-stroke.h"
+#include "paint/gimppaintoptions.h"
+
+#include "gegl/gimp-gegl-apply-operation.h"
+#include "gegl/gimp-gegl-loops.h"
+#include "gegl/gimp-gegl-mask.h"
+#include "gegl/gimp-gegl-nodes.h"
+
+#include "gimp.h"
+#include "gimp-utils.h"
+#include "gimpboundary.h"
+#include "gimpcontainer.h"
+#include "gimperror.h"
+#include "gimpimage.h"
+#include "gimpimage-quick-mask.h"
+#include "gimpimage-undo.h"
+#include "gimpimage-undo-push.h"
+#include "gimpchannel.h"
+#include "gimpchannel-select.h"
+#include "gimpcontext.h"
+#include "gimpdrawable-fill.h"
+#include "gimpdrawable-stroke.h"
+#include "gimpmarshal.h"
+#include "gimppaintinfo.h"
+#include "gimppickable.h"
+#include "gimpstrokeoptions.h"
+
+#include "gimp-intl.h"
+
+
+#define RGBA_EPSILON 1e-6
+
+enum
+{
+ COLOR_CHANGED,
+ LAST_SIGNAL
+};
+
+
+static void gimp_channel_pickable_iface_init (GimpPickableInterface *iface);
+
+static void gimp_channel_finalize (GObject *object);
+
+static gint64 gimp_channel_get_memsize (GimpObject *object,
+ gint64 *gui_size);
+
+static gchar * gimp_channel_get_description (GimpViewable *viewable,
+ gchar **tooltip);
+
+static GeglNode * gimp_channel_get_node (GimpFilter *filter);
+
+static gboolean gimp_channel_is_attached (GimpItem *item);
+static GimpItemTree * gimp_channel_get_tree (GimpItem *item);
+static gboolean gimp_channel_bounds (GimpItem *item,
+ gdouble *x,
+ gdouble *y,
+ gdouble *width,
+ gdouble *height);
+static GimpItem * gimp_channel_duplicate (GimpItem *item,
+ GType new_type);
+static void gimp_channel_convert (GimpItem *item,
+ GimpImage *dest_image,
+ GType old_type);
+static void gimp_channel_translate (GimpItem *item,
+ gdouble off_x,
+ gdouble off_y,
+ gboolean push_undo);
+static void gimp_channel_scale (GimpItem *item,
+ gint new_width,
+ gint new_height,
+ gint new_offset_x,
+ gint new_offset_y,
+ GimpInterpolationType interp_type,
+ GimpProgress *progress);
+static void gimp_channel_resize (GimpItem *item,
+ GimpContext *context,
+ GimpFillType fill_type,
+ gint new_width,
+ gint new_height,
+ gint offset_x,
+ gint offset_y);
+static GimpTransformResize
+ gimp_channel_get_clip (GimpItem *item,
+ GimpTransformResize clip_result);
+static gboolean gimp_channel_fill (GimpItem *item,
+ GimpDrawable *drawable,
+ GimpFillOptions *fill_options,
+ gboolean push_undo,
+ GimpProgress *progress,
+ GError **error);
+static gboolean gimp_channel_stroke (GimpItem *item,
+ GimpDrawable *drawable,
+ GimpStrokeOptions *stroke_options,
+ gboolean push_undo,
+ GimpProgress *progress,
+ GError **error);
+static void gimp_channel_to_selection (GimpItem *item,
+ GimpChannelOps op,
+ gboolean antialias,
+ gboolean feather,
+ gdouble feather_radius_x,
+ gdouble feather_radius_y);
+
+static void gimp_channel_convert_type (GimpDrawable *drawable,
+ GimpImage *dest_image,
+ const Babl *new_format,
+ GimpColorProfile *dest_profile,
+ GeglDitherMethod layer_dither_type,
+ GeglDitherMethod mask_dither_type,
+ gboolean push_undo,
+ GimpProgress *progress);
+static void gimp_channel_invalidate_boundary (GimpDrawable *drawable);
+static void gimp_channel_get_active_components (GimpDrawable *drawable,
+ gboolean *active);
+
+static void gimp_channel_set_buffer (GimpDrawable *drawable,
+ gboolean push_undo,
+ const gchar *undo_desc,
+ GeglBuffer *buffer,
+ const GeglRectangle *bounds);
+
+static gdouble gimp_channel_get_opacity_at (GimpPickable *pickable,
+ gint x,
+ gint y);
+
+static gboolean gimp_channel_real_boundary (GimpChannel *channel,
+ const GimpBoundSeg **segs_in,
+ const GimpBoundSeg **segs_out,
+ gint *num_segs_in,
+ gint *num_segs_out,
+ gint x1,
+ gint y1,
+ gint x2,
+ gint y2);
+static gboolean gimp_channel_real_is_empty (GimpChannel *channel);
+static void gimp_channel_real_feather (GimpChannel *channel,
+ gdouble radius_x,
+ gdouble radius_y,
+ gboolean edge_lock,
+ gboolean push_undo);
+static void gimp_channel_real_sharpen (GimpChannel *channel,
+ gboolean push_undo);
+static void gimp_channel_real_clear (GimpChannel *channel,
+ const gchar *undo_desc,
+ gboolean push_undo);
+static void gimp_channel_real_all (GimpChannel *channel,
+ gboolean push_undo);
+static void gimp_channel_real_invert (GimpChannel *channel,
+ gboolean push_undo);
+static void gimp_channel_real_border (GimpChannel *channel,
+ gint radius_x,
+ gint radius_y,
+ GimpChannelBorderStyle style,
+ gboolean edge_lock,
+ gboolean push_undo);
+static void gimp_channel_real_grow (GimpChannel *channel,
+ gint radius_x,
+ gint radius_y,
+ gboolean push_undo);
+static void gimp_channel_real_shrink (GimpChannel *channel,
+ gint radius_x,
+ gint radius_y,
+ gboolean edge_lock,
+ gboolean push_undo);
+static void gimp_channel_real_flood (GimpChannel *channel,
+ gboolean push_undo);
+
+
+static void gimp_channel_buffer_changed (GeglBuffer *buffer,
+ const GeglRectangle *rect,
+ GimpChannel *channel);
+
+
+G_DEFINE_TYPE_WITH_CODE (GimpChannel, gimp_channel, GIMP_TYPE_DRAWABLE,
+ G_IMPLEMENT_INTERFACE (GIMP_TYPE_PICKABLE,
+ gimp_channel_pickable_iface_init))
+
+#define parent_class gimp_channel_parent_class
+
+static guint channel_signals[LAST_SIGNAL] = { 0 };
+
+
+static void
+gimp_channel_class_init (GimpChannelClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpObjectClass *gimp_object_class = GIMP_OBJECT_CLASS (klass);
+ GimpViewableClass *viewable_class = GIMP_VIEWABLE_CLASS (klass);
+ GimpFilterClass *filter_class = GIMP_FILTER_CLASS (klass);
+ GimpItemClass *item_class = GIMP_ITEM_CLASS (klass);
+ GimpDrawableClass *drawable_class = GIMP_DRAWABLE_CLASS (klass);
+
+ channel_signals[COLOR_CHANGED] =
+ g_signal_new ("color-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpChannelClass, color_changed),
+ NULL, NULL,
+ gimp_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ object_class->finalize = gimp_channel_finalize;
+
+ gimp_object_class->get_memsize = gimp_channel_get_memsize;
+
+ viewable_class->get_description = gimp_channel_get_description;
+ viewable_class->default_icon_name = "gimp-channel";
+
+ filter_class->get_node = gimp_channel_get_node;
+
+ item_class->is_attached = gimp_channel_is_attached;
+ item_class->get_tree = gimp_channel_get_tree;
+ item_class->bounds = gimp_channel_bounds;
+ item_class->duplicate = gimp_channel_duplicate;
+ item_class->convert = gimp_channel_convert;
+ item_class->translate = gimp_channel_translate;
+ item_class->scale = gimp_channel_scale;
+ item_class->resize = gimp_channel_resize;
+ item_class->get_clip = gimp_channel_get_clip;
+ item_class->fill = gimp_channel_fill;
+ item_class->stroke = gimp_channel_stroke;
+ item_class->to_selection = gimp_channel_to_selection;
+ item_class->default_name = _("Channel");
+ item_class->rename_desc = C_("undo-type", "Rename Channel");
+ item_class->translate_desc = C_("undo-type", "Move Channel");
+ item_class->scale_desc = C_("undo-type", "Scale Channel");
+ item_class->resize_desc = C_("undo-type", "Resize Channel");
+ item_class->flip_desc = C_("undo-type", "Flip Channel");
+ item_class->rotate_desc = C_("undo-type", "Rotate Channel");
+ item_class->transform_desc = C_("undo-type", "Transform Channel");
+ item_class->fill_desc = C_("undo-type", "Fill Channel");
+ item_class->stroke_desc = C_("undo-type", "Stroke Channel");
+ item_class->to_selection_desc = C_("undo-type", "Channel to Selection");
+ item_class->reorder_desc = C_("undo-type", "Reorder Channel");
+ item_class->raise_desc = C_("undo-type", "Raise Channel");
+ item_class->raise_to_top_desc = C_("undo-type", "Raise Channel to Top");
+ item_class->lower_desc = C_("undo-type", "Lower Channel");
+ item_class->lower_to_bottom_desc = C_("undo-type", "Lower Channel to Bottom");
+ item_class->raise_failed = _("Channel cannot be raised higher.");
+ item_class->lower_failed = _("Channel cannot be lowered more.");
+
+ drawable_class->convert_type = gimp_channel_convert_type;
+ drawable_class->invalidate_boundary = gimp_channel_invalidate_boundary;
+ drawable_class->get_active_components = gimp_channel_get_active_components;
+ drawable_class->set_buffer = gimp_channel_set_buffer;
+
+ klass->boundary = gimp_channel_real_boundary;
+ klass->is_empty = gimp_channel_real_is_empty;
+ klass->feather = gimp_channel_real_feather;
+ klass->sharpen = gimp_channel_real_sharpen;
+ klass->clear = gimp_channel_real_clear;
+ klass->all = gimp_channel_real_all;
+ klass->invert = gimp_channel_real_invert;
+ klass->border = gimp_channel_real_border;
+ klass->grow = gimp_channel_real_grow;
+ klass->shrink = gimp_channel_real_shrink;
+ klass->flood = gimp_channel_real_flood;
+
+ klass->feather_desc = C_("undo-type", "Feather Channel");
+ klass->sharpen_desc = C_("undo-type", "Sharpen Channel");
+ klass->clear_desc = C_("undo-type", "Clear Channel");
+ klass->all_desc = C_("undo-type", "Fill Channel");
+ klass->invert_desc = C_("undo-type", "Invert Channel");
+ klass->border_desc = C_("undo-type", "Border Channel");
+ klass->grow_desc = C_("undo-type", "Grow Channel");
+ klass->shrink_desc = C_("undo-type", "Shrink Channel");
+ klass->flood_desc = C_("undo-type", "Flood Channel");
+}
+
+static void
+gimp_channel_init (GimpChannel *channel)
+{
+ gimp_rgba_set (&channel->color, 0.0, 0.0, 0.0, GIMP_OPACITY_OPAQUE);
+
+ channel->show_masked = FALSE;
+
+ /* Selection mask variables */
+ channel->boundary_known = FALSE;
+ channel->segs_in = NULL;
+ channel->segs_out = NULL;
+ channel->num_segs_in = 0;
+ channel->num_segs_out = 0;
+ channel->empty = FALSE;
+ channel->bounds_known = FALSE;
+ channel->x1 = 0;
+ channel->y1 = 0;
+ channel->x2 = 0;
+ channel->y2 = 0;
+}
+
+static void
+gimp_channel_pickable_iface_init (GimpPickableInterface *iface)
+{
+ iface->get_opacity_at = gimp_channel_get_opacity_at;
+}
+
+static void
+gimp_channel_finalize (GObject *object)
+{
+ GimpChannel *channel = GIMP_CHANNEL (object);
+
+ g_clear_pointer (&channel->segs_in, g_free);
+ g_clear_pointer (&channel->segs_out, g_free);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static gint64
+gimp_channel_get_memsize (GimpObject *object,
+ gint64 *gui_size)
+{
+ GimpChannel *channel = GIMP_CHANNEL (object);
+
+ *gui_size += channel->num_segs_in * sizeof (GimpBoundSeg);
+ *gui_size += channel->num_segs_out * sizeof (GimpBoundSeg);
+
+ return GIMP_OBJECT_CLASS (parent_class)->get_memsize (object, gui_size);
+}
+
+static gchar *
+gimp_channel_get_description (GimpViewable *viewable,
+ gchar **tooltip)
+{
+ if (! strcmp (GIMP_IMAGE_QUICK_MASK_NAME,
+ gimp_object_get_name (viewable)))
+ {
+ return g_strdup (_("Quick Mask"));
+ }
+
+ return GIMP_VIEWABLE_CLASS (parent_class)->get_description (viewable,
+ tooltip);
+}
+
+static GeglNode *
+gimp_channel_get_node (GimpFilter *filter)
+{
+ GimpDrawable *drawable = GIMP_DRAWABLE (filter);
+ GimpChannel *channel = GIMP_CHANNEL (filter);
+ GeglNode *node;
+ GeglNode *source;
+ GeglNode *mode_node;
+ const Babl *color_format;
+
+ node = GIMP_FILTER_CLASS (parent_class)->get_node (filter);
+
+ source = gimp_drawable_get_source_node (drawable);
+ gegl_node_add_child (node, source);
+
+ g_warn_if_fail (channel->color_node == NULL);
+
+ if (gimp_drawable_get_linear (drawable))
+ color_format = babl_format ("RGBA float");
+ else
+ color_format = babl_format ("R'G'B'A float");
+
+ channel->color_node = gegl_node_new_child (node,
+ "operation", "gegl:color",
+ "format", color_format,
+ NULL);
+ gimp_gegl_node_set_color (channel->color_node,
+ &channel->color);
+
+ g_warn_if_fail (channel->mask_node == NULL);
+
+ channel->mask_node = gegl_node_new_child (node,
+ "operation", "gegl:opacity",
+ NULL);
+ gegl_node_connect_to (channel->color_node, "output",
+ channel->mask_node, "input");
+
+ g_warn_if_fail (channel->invert_node == NULL);
+
+ channel->invert_node = gegl_node_new_child (node,
+ "operation", "gegl:invert-linear",
+ NULL);
+
+ if (channel->show_masked)
+ {
+ gegl_node_connect_to (source, "output",
+ channel->invert_node, "input");
+ gegl_node_connect_to (channel->invert_node, "output",
+ channel->mask_node, "aux");
+ }
+ else
+ {
+ gegl_node_connect_to (source, "output",
+ channel->mask_node, "aux");
+ }
+
+ mode_node = gimp_drawable_get_mode_node (drawable);
+
+ gegl_node_connect_to (channel->mask_node, "output",
+ mode_node, "aux");
+
+ return node;
+}
+
+static gboolean
+gimp_channel_is_attached (GimpItem *item)
+{
+ GimpImage *image = gimp_item_get_image (item);
+
+ return (GIMP_IS_IMAGE (image) &&
+ gimp_container_have (gimp_image_get_channels (image),
+ GIMP_OBJECT (item)));
+}
+
+static GimpItemTree *
+gimp_channel_get_tree (GimpItem *item)
+{
+ if (gimp_item_is_attached (item))
+ {
+ GimpImage *image = gimp_item_get_image (item);
+
+ return gimp_image_get_channel_tree (image);
+ }
+
+ return NULL;
+}
+
+static gboolean
+gimp_channel_bounds (GimpItem *item,
+ gdouble *x,
+ gdouble *y,
+ gdouble *width,
+ gdouble *height)
+{
+ GimpChannel *channel = GIMP_CHANNEL (item);
+
+ if (! channel->bounds_known)
+ {
+ GeglBuffer *buffer = gimp_drawable_get_buffer (GIMP_DRAWABLE (channel));
+
+ channel->empty = ! gimp_gegl_mask_bounds (buffer,
+ &channel->x1,
+ &channel->y1,
+ &channel->x2,
+ &channel->y2);
+
+ channel->bounds_known = TRUE;
+ }
+
+ *x = channel->x1;
+ *y = channel->y1;
+ *width = channel->x2 - channel->x1;
+ *height = channel->y2 - channel->y1;
+
+ return ! channel->empty;
+}
+
+static GimpItem *
+gimp_channel_duplicate (GimpItem *item,
+ GType new_type)
+{
+ GimpItem *new_item;
+
+ g_return_val_if_fail (g_type_is_a (new_type, GIMP_TYPE_DRAWABLE), NULL);
+
+ new_item = GIMP_ITEM_CLASS (parent_class)->duplicate (item, new_type);
+
+ if (GIMP_IS_CHANNEL (new_item))
+ {
+ GimpChannel *channel = GIMP_CHANNEL (item);
+ GimpChannel *new_channel = GIMP_CHANNEL (new_item);
+
+ new_channel->color = channel->color;
+ new_channel->show_masked = channel->show_masked;
+
+ /* selection mask variables */
+ new_channel->bounds_known = channel->bounds_known;
+ new_channel->empty = channel->empty;
+ new_channel->x1 = channel->x1;
+ new_channel->y1 = channel->y1;
+ new_channel->x2 = channel->x2;
+ new_channel->y2 = channel->y2;
+
+ if (new_type == GIMP_TYPE_CHANNEL)
+ {
+ /* 8-bit channel hack: make sure pixels between all sorts
+ * of channels of an image is always copied without any
+ * gamma conversion
+ */
+ GimpDrawable *new_drawable = GIMP_DRAWABLE (new_item);
+ GimpImage *image = gimp_item_get_image (item);
+ const Babl *format = gimp_image_get_channel_format (image);
+
+ if (format != gimp_drawable_get_format (new_drawable))
+ {
+ GeglBuffer *new_buffer;
+
+ new_buffer =
+ gegl_buffer_new (GEGL_RECTANGLE (0, 0,
+ gimp_item_get_width (new_item),
+ gimp_item_get_height (new_item)),
+ format);
+
+ gegl_buffer_set_format (new_buffer,
+ gimp_drawable_get_format (new_drawable));
+ gimp_gegl_buffer_copy (gimp_drawable_get_buffer (new_drawable),
+ NULL, GEGL_ABYSS_NONE,
+ new_buffer, NULL);
+ gegl_buffer_set_format (new_buffer, NULL);
+
+ gimp_drawable_set_buffer (new_drawable, FALSE, NULL, new_buffer);
+ g_object_unref (new_buffer);
+ }
+ }
+ }
+
+ return new_item;
+}
+
+static void
+gimp_channel_convert (GimpItem *item,
+ GimpImage *dest_image,
+ GType old_type)
+{
+ GimpChannel *channel = GIMP_CHANNEL (item);
+ GimpDrawable *drawable = GIMP_DRAWABLE (item);
+
+ if (! gimp_drawable_is_gray (drawable))
+ {
+ gimp_drawable_convert_type (drawable, dest_image,
+ GIMP_GRAY,
+ gimp_image_get_precision (dest_image),
+ gimp_drawable_has_alpha (drawable),
+ NULL,
+ GEGL_DITHER_NONE, GEGL_DITHER_NONE,
+ FALSE, NULL);
+ }
+
+ if (gimp_drawable_has_alpha (drawable))
+ {
+ GeglBuffer *new_buffer;
+ const Babl *format;
+ GimpRGB background;
+
+ format = gimp_drawable_get_format_without_alpha (drawable);
+
+ new_buffer =
+ gegl_buffer_new (GEGL_RECTANGLE (0, 0,
+ gimp_item_get_width (item),
+ gimp_item_get_height (item)),
+ format);
+
+ gimp_rgba_set (&background, 0.0, 0.0, 0.0, 0.0);
+
+ gimp_gegl_apply_flatten (gimp_drawable_get_buffer (drawable),
+ NULL, NULL,
+ new_buffer, &background,
+ GIMP_LAYER_COLOR_SPACE_RGB_LINEAR);
+
+ gimp_drawable_set_buffer_full (drawable, FALSE, NULL,
+ new_buffer,
+ GEGL_RECTANGLE (
+ gimp_item_get_offset_x (item),
+ gimp_item_get_offset_y (item),
+ 0, 0),
+ TRUE);
+ g_object_unref (new_buffer);
+ }
+
+ if (G_TYPE_FROM_INSTANCE (channel) == GIMP_TYPE_CHANNEL)
+ {
+ gint width = gimp_image_get_width (dest_image);
+ gint height = gimp_image_get_height (dest_image);
+
+ gimp_item_set_offset (item, 0, 0);
+
+ if (gimp_item_get_width (item) != width ||
+ gimp_item_get_height (item) != height)
+ {
+ gimp_item_resize (item, gimp_get_user_context (dest_image->gimp),
+ GIMP_FILL_TRANSPARENT,
+ width, height, 0, 0);
+ }
+ }
+
+ GIMP_ITEM_CLASS (parent_class)->convert (item, dest_image, old_type);
+}
+
+static void
+gimp_channel_translate (GimpItem *item,
+ gdouble off_x,
+ gdouble off_y,
+ gboolean push_undo)
+{
+ GimpChannel *channel = GIMP_CHANNEL (item);
+ gint x, y, width, height;
+
+ gimp_item_bounds (GIMP_ITEM (channel), &x, &y, &width, &height);
+
+ /* update the old area */
+ gimp_drawable_update (GIMP_DRAWABLE (item), x, y, width, height);
+
+ if (push_undo)
+ gimp_channel_push_undo (channel, NULL);
+
+ if (gimp_rectangle_intersect (x + SIGNED_ROUND (off_x),
+ y + SIGNED_ROUND (off_y),
+ width, height,
+ 0, 0,
+ gimp_item_get_width (GIMP_ITEM (channel)),
+ gimp_item_get_height (GIMP_ITEM (channel)),
+ &x, &y, &width, &height))
+ {
+ /* copy the portion of the mask we will keep to a temporary
+ * buffer
+ */
+ GeglBuffer *tmp_buffer =
+ gegl_buffer_new (GEGL_RECTANGLE (0, 0, width, height),
+ gimp_drawable_get_format (GIMP_DRAWABLE (channel)));
+
+ gimp_gegl_buffer_copy (gimp_drawable_get_buffer (GIMP_DRAWABLE (channel)),
+ GEGL_RECTANGLE (x - SIGNED_ROUND (off_x),
+ y - SIGNED_ROUND (off_y),
+ width, height),
+ GEGL_ABYSS_NONE,
+ tmp_buffer,
+ GEGL_RECTANGLE (0, 0, 0, 0));
+
+ /* clear the mask */
+ gegl_buffer_clear (gimp_drawable_get_buffer (GIMP_DRAWABLE (channel)),
+ NULL);
+
+ /* copy the temp mask back to the mask */
+ gimp_gegl_buffer_copy (tmp_buffer, NULL, GEGL_ABYSS_NONE,
+ gimp_drawable_get_buffer (GIMP_DRAWABLE (channel)),
+ GEGL_RECTANGLE (x, y, 0, 0));
+
+ /* free the temporary mask */
+ g_object_unref (tmp_buffer);
+
+ channel->x1 = x;
+ channel->y1 = y;
+ channel->x2 = x + width;
+ channel->y2 = y + height;
+ }
+ else
+ {
+ /* clear the mask */
+ gegl_buffer_clear (gimp_drawable_get_buffer (GIMP_DRAWABLE (channel)),
+ NULL);
+
+ channel->empty = TRUE;
+ channel->x1 = 0;
+ channel->y1 = 0;
+ channel->x2 = gimp_item_get_width (GIMP_ITEM (channel));
+ channel->y2 = gimp_item_get_height (GIMP_ITEM (channel));
+ }
+
+ /* update the new area */
+ gimp_drawable_update (GIMP_DRAWABLE (item),
+ channel->x1, channel->y1,
+ channel->x2 - channel->x1,
+ channel->y2 - channel->y1);
+}
+
+static void
+gimp_channel_scale (GimpItem *item,
+ gint new_width,
+ gint new_height,
+ gint new_offset_x,
+ gint new_offset_y,
+ GimpInterpolationType interpolation_type,
+ GimpProgress *progress)
+{
+ GimpChannel *channel = GIMP_CHANNEL (item);
+
+ if (G_TYPE_FROM_INSTANCE (item) == GIMP_TYPE_CHANNEL)
+ {
+ new_offset_x = 0;
+ new_offset_y = 0;
+ }
+
+ /* don't waste CPU cycles scaling an empty channel */
+ if (channel->bounds_known && channel->empty)
+ {
+ GimpDrawable *drawable = GIMP_DRAWABLE (item);
+ GeglBuffer *new_buffer;
+
+ new_buffer =
+ gegl_buffer_new (GEGL_RECTANGLE (0, 0, new_width, new_height),
+ gimp_drawable_get_format (drawable));
+
+ gimp_drawable_set_buffer_full (drawable,
+ gimp_item_is_attached (item), NULL,
+ new_buffer,
+ GEGL_RECTANGLE (new_offset_x, new_offset_y,
+ 0, 0),
+ TRUE);
+ g_object_unref (new_buffer);
+
+ gimp_channel_clear (GIMP_CHANNEL (item), NULL, FALSE);
+ }
+ else
+ {
+ GIMP_ITEM_CLASS (parent_class)->scale (item, new_width, new_height,
+ new_offset_x, new_offset_y,
+ interpolation_type, progress);
+ }
+}
+
+static void
+gimp_channel_resize (GimpItem *item,
+ GimpContext *context,
+ GimpFillType fill_type,
+ gint new_width,
+ gint new_height,
+ gint offset_x,
+ gint offset_y)
+{
+ GIMP_ITEM_CLASS (parent_class)->resize (item, context, GIMP_FILL_TRANSPARENT,
+ new_width, new_height,
+ offset_x, offset_y);
+
+ if (G_TYPE_FROM_INSTANCE (item) == GIMP_TYPE_CHANNEL)
+ {
+ gimp_item_set_offset (item, 0, 0);
+ }
+}
+
+static GimpTransformResize
+gimp_channel_get_clip (GimpItem *item,
+ GimpTransformResize clip_result)
+{
+ return GIMP_TRANSFORM_RESIZE_CLIP;
+}
+
+static gboolean
+gimp_channel_fill (GimpItem *item,
+ GimpDrawable *drawable,
+ GimpFillOptions *fill_options,
+ gboolean push_undo,
+ GimpProgress *progress,
+ GError **error)
+{
+ GimpChannel *channel = GIMP_CHANNEL (item);
+ const GimpBoundSeg *segs_in;
+ const GimpBoundSeg *segs_out;
+ gint n_segs_in;
+ gint n_segs_out;
+ gint offset_x, offset_y;
+
+ if (! gimp_channel_boundary (channel, &segs_in, &segs_out,
+ &n_segs_in, &n_segs_out,
+ 0, 0, 0, 0))
+ {
+ g_set_error_literal (error, GIMP_ERROR, GIMP_FAILED,
+ _("Cannot fill empty channel."));
+ return FALSE;
+ }
+
+ gimp_item_get_offset (item, &offset_x, &offset_y);
+
+ gimp_drawable_fill_boundary (drawable,
+ fill_options,
+ segs_in, n_segs_in,
+ offset_x, offset_y,
+ push_undo);
+
+ return TRUE;
+}
+
+static gboolean
+gimp_channel_stroke (GimpItem *item,
+ GimpDrawable *drawable,
+ GimpStrokeOptions *stroke_options,
+ gboolean push_undo,
+ GimpProgress *progress,
+ GError **error)
+{
+ GimpChannel *channel = GIMP_CHANNEL (item);
+ const GimpBoundSeg *segs_in;
+ const GimpBoundSeg *segs_out;
+ gint n_segs_in;
+ gint n_segs_out;
+ gboolean retval = FALSE;
+ gint offset_x, offset_y;
+
+ if (! gimp_channel_boundary (channel, &segs_in, &segs_out,
+ &n_segs_in, &n_segs_out,
+ 0, 0, 0, 0))
+ {
+ g_set_error_literal (error, GIMP_ERROR, GIMP_FAILED,
+ _("Cannot stroke empty channel."));
+ return FALSE;
+ }
+
+ gimp_item_get_offset (item, &offset_x, &offset_y);
+
+ switch (gimp_stroke_options_get_method (stroke_options))
+ {
+ case GIMP_STROKE_LINE:
+ gimp_drawable_stroke_boundary (drawable,
+ stroke_options,
+ segs_in, n_segs_in,
+ offset_x, offset_y,
+ push_undo);
+ retval = TRUE;
+ break;
+
+ case GIMP_STROKE_PAINT_METHOD:
+ {
+ GimpPaintInfo *paint_info;
+ GimpPaintCore *core;
+ GimpPaintOptions *paint_options;
+ gboolean emulate_dynamics;
+
+ paint_info = gimp_context_get_paint_info (GIMP_CONTEXT (stroke_options));
+
+ core = g_object_new (paint_info->paint_type, NULL);
+
+ paint_options = gimp_stroke_options_get_paint_options (stroke_options);
+ emulate_dynamics = gimp_stroke_options_get_emulate_dynamics (stroke_options);
+
+ retval = gimp_paint_core_stroke_boundary (core, drawable,
+ paint_options,
+ emulate_dynamics,
+ segs_in, n_segs_in,
+ offset_x, offset_y,
+ push_undo, error);
+
+ g_object_unref (core);
+ }
+ break;
+
+ default:
+ g_return_val_if_reached (FALSE);
+ }
+
+ return retval;
+}
+
+static void
+gimp_channel_to_selection (GimpItem *item,
+ GimpChannelOps op,
+ gboolean antialias,
+ gboolean feather,
+ gdouble feather_radius_x,
+ gdouble feather_radius_y)
+{
+ GimpChannel *channel = GIMP_CHANNEL (item);
+ GimpImage *image = gimp_item_get_image (item);
+ gint off_x, off_y;
+
+ gimp_item_get_offset (item, &off_x, &off_y);
+
+ gimp_channel_select_channel (gimp_image_get_mask (image),
+ GIMP_ITEM_GET_CLASS (item)->to_selection_desc,
+ channel, off_x, off_y,
+ op,
+ feather, feather_radius_x, feather_radius_x);
+}
+
+static void
+gimp_channel_convert_type (GimpDrawable *drawable,
+ GimpImage *dest_image,
+ const Babl *new_format,
+ GimpColorProfile *dest_profile,
+ GeglDitherMethod layer_dither_type,
+ GeglDitherMethod mask_dither_type,
+ gboolean push_undo,
+ GimpProgress *progress)
+{
+ GeglBuffer *dest_buffer;
+
+ dest_buffer =
+ gegl_buffer_new (GEGL_RECTANGLE (0, 0,
+ gimp_item_get_width (GIMP_ITEM (drawable)),
+ gimp_item_get_height (GIMP_ITEM (drawable))),
+ new_format);
+
+ if (mask_dither_type == GEGL_DITHER_NONE)
+ {
+ gimp_gegl_buffer_copy (gimp_drawable_get_buffer (drawable), NULL,
+ GEGL_ABYSS_NONE,
+ dest_buffer, NULL);
+ }
+ else
+ {
+ gint bits;
+
+ bits = (babl_format_get_bytes_per_pixel (new_format) * 8 /
+ babl_format_get_n_components (new_format));
+
+ gimp_gegl_apply_dither (gimp_drawable_get_buffer (drawable),
+ NULL, NULL,
+ dest_buffer, 1 << bits, mask_dither_type);
+ }
+
+ gimp_drawable_set_buffer (drawable, push_undo, NULL, dest_buffer);
+ g_object_unref (dest_buffer);
+}
+
+static void
+gimp_channel_invalidate_boundary (GimpDrawable *drawable)
+{
+ GimpChannel *channel = GIMP_CHANNEL (drawable);
+
+ channel->boundary_known = FALSE;
+ channel->bounds_known = FALSE;
+}
+
+static void
+gimp_channel_get_active_components (GimpDrawable *drawable,
+ gboolean *active)
+{
+ /* Make sure that the alpha channel is not valid. */
+ active[GRAY] = TRUE;
+ active[ALPHA_G] = FALSE;
+}
+
+static void
+gimp_channel_set_buffer (GimpDrawable *drawable,
+ gboolean push_undo,
+ const gchar *undo_desc,
+ GeglBuffer *buffer,
+ const GeglRectangle *bounds)
+{
+ GimpChannel *channel = GIMP_CHANNEL (drawable);
+ GeglBuffer *old_buffer = gimp_drawable_get_buffer (drawable);
+
+ if (old_buffer)
+ {
+ g_signal_handlers_disconnect_by_func (old_buffer,
+ gimp_channel_buffer_changed,
+ channel);
+ }
+
+ GIMP_DRAWABLE_CLASS (parent_class)->set_buffer (drawable,
+ push_undo, undo_desc,
+ buffer, bounds);
+
+ gegl_buffer_signal_connect (buffer, "changed",
+ G_CALLBACK (gimp_channel_buffer_changed),
+ channel);
+
+ if (gimp_filter_peek_node (GIMP_FILTER (channel)))
+ {
+ const Babl *color_format;
+
+ if (gimp_drawable_get_linear (drawable))
+ color_format = babl_format ("RGBA float");
+ else
+ color_format = babl_format ("R'G'B'A float");
+
+ gegl_node_set (channel->color_node,
+ "format", color_format,
+ NULL);
+ }
+}
+
+static gdouble
+gimp_channel_get_opacity_at (GimpPickable *pickable,
+ gint x,
+ gint y)
+{
+ GimpChannel *channel = GIMP_CHANNEL (pickable);
+ gdouble value = GIMP_OPACITY_TRANSPARENT;
+
+ if (x >= 0 && x < gimp_item_get_width (GIMP_ITEM (channel)) &&
+ y >= 0 && y < gimp_item_get_height (GIMP_ITEM (channel)))
+ {
+ if (! channel->bounds_known ||
+ (! channel->empty &&
+ x >= channel->x1 &&
+ x < channel->x2 &&
+ y >= channel->y1 &&
+ y < channel->y2))
+ {
+ gegl_buffer_sample (gimp_drawable_get_buffer (GIMP_DRAWABLE (channel)),
+ x, y, NULL, &value, babl_format ("Y double"),
+ GEGL_SAMPLER_NEAREST, GEGL_ABYSS_NONE);
+ }
+ }
+
+ return value;
+}
+
+static gboolean
+gimp_channel_real_boundary (GimpChannel *channel,
+ const GimpBoundSeg **segs_in,
+ const GimpBoundSeg **segs_out,
+ gint *num_segs_in,
+ gint *num_segs_out,
+ gint x1,
+ gint y1,
+ gint x2,
+ gint y2)
+{
+ if (! channel->boundary_known)
+ {
+ gint x3, y3, x4, y4;
+
+ /* free the out of date boundary segments */
+ g_free (channel->segs_in);
+ g_free (channel->segs_out);
+
+ if (gimp_item_bounds (GIMP_ITEM (channel), &x3, &y3, &x4, &y4))
+ {
+ GeglBuffer *buffer;
+ GeglRectangle rect = { x3, y3, x4, y4 };
+
+ x4 += x3;
+ y4 += y3;
+
+ buffer = gimp_drawable_get_buffer (GIMP_DRAWABLE (channel));
+
+ channel->segs_out = gimp_boundary_find (buffer, &rect,
+ babl_format ("Y float"),
+ GIMP_BOUNDARY_IGNORE_BOUNDS,
+ x1, y1, x2, y2,
+ GIMP_BOUNDARY_HALF_WAY,
+ &channel->num_segs_out);
+ x1 = MAX (x1, x3);
+ y1 = MAX (y1, y3);
+ x2 = MIN (x2, x4);
+ y2 = MIN (y2, y4);
+
+ if (x2 > x1 && y2 > y1)
+ {
+ channel->segs_in = gimp_boundary_find (buffer, NULL,
+ babl_format ("Y float"),
+ GIMP_BOUNDARY_WITHIN_BOUNDS,
+ x1, y1, x2, y2,
+ GIMP_BOUNDARY_HALF_WAY,
+ &channel->num_segs_in);
+ }
+ else
+ {
+ channel->segs_in = NULL;
+ channel->num_segs_in = 0;
+ }
+ }
+ else
+ {
+ channel->segs_in = NULL;
+ channel->segs_out = NULL;
+ channel->num_segs_in = 0;
+ channel->num_segs_out = 0;
+ }
+
+ channel->boundary_known = TRUE;
+ }
+
+ *segs_in = channel->segs_in;
+ *segs_out = channel->segs_out;
+ *num_segs_in = channel->num_segs_in;
+ *num_segs_out = channel->num_segs_out;
+
+ return (! channel->empty);
+}
+
+static gboolean
+gimp_channel_real_is_empty (GimpChannel *channel)
+{
+ GeglBuffer *buffer;
+
+ if (channel->bounds_known)
+ return channel->empty;
+
+ buffer = gimp_drawable_get_buffer (GIMP_DRAWABLE (channel));
+
+ if (! gimp_gegl_mask_is_empty (buffer))
+ return FALSE;
+
+ /* The mask is empty, meaning we can set the bounds as known */
+ g_clear_pointer (&channel->segs_in, g_free);
+ g_clear_pointer (&channel->segs_out, g_free);
+
+ channel->empty = TRUE;
+ channel->num_segs_in = 0;
+ channel->num_segs_out = 0;
+ channel->bounds_known = TRUE;
+ channel->boundary_known = TRUE;
+ channel->x1 = 0;
+ channel->y1 = 0;
+ channel->x2 = gimp_item_get_width (GIMP_ITEM (channel));
+ channel->y2 = gimp_item_get_height (GIMP_ITEM (channel));
+
+ return TRUE;
+}
+
+static void
+gimp_channel_real_feather (GimpChannel *channel,
+ gdouble radius_x,
+ gdouble radius_y,
+ gboolean edge_lock,
+ gboolean push_undo)
+{
+ gint x1, y1, x2, y2;
+
+ if (radius_x <= 0.0 && radius_y <= 0.0)
+ return;
+
+ if (! gimp_item_bounds (GIMP_ITEM (channel), &x1, &y1, &x2, &y2))
+ return;
+
+ x2 += x1;
+ y2 += y1;
+
+ if (gimp_channel_is_empty (channel))
+ return;
+
+ x1 = MAX (0, x1 - ceil (radius_x));
+ y1 = MAX (0, y1 - ceil (radius_y));
+
+ x2 = MIN (gimp_item_get_width (GIMP_ITEM (channel)), x2 + ceil (radius_x));
+ y2 = MIN (gimp_item_get_height (GIMP_ITEM (channel)), y2 + ceil (radius_y));
+
+ if (push_undo)
+ gimp_channel_push_undo (channel,
+ GIMP_CHANNEL_GET_CLASS (channel)->feather_desc);
+
+ gimp_gegl_apply_feather (gimp_drawable_get_buffer (GIMP_DRAWABLE (channel)),
+ NULL, NULL,
+ gimp_drawable_get_buffer (GIMP_DRAWABLE (channel)),
+ GEGL_RECTANGLE (x1, y1, x2 - x1, y2 - y1),
+ radius_x,
+ radius_y,
+ edge_lock);
+
+ gimp_drawable_update (GIMP_DRAWABLE (channel), 0, 0, -1, -1);
+}
+
+static void
+gimp_channel_real_sharpen (GimpChannel *channel,
+ gboolean push_undo)
+{
+ GimpDrawable *drawable = GIMP_DRAWABLE (channel);
+
+ if (push_undo)
+ gimp_channel_push_undo (channel,
+ GIMP_CHANNEL_GET_CLASS (channel)->sharpen_desc);
+
+ gimp_gegl_apply_threshold (gimp_drawable_get_buffer (drawable),
+ NULL, NULL,
+ gimp_drawable_get_buffer (drawable),
+ 0.5);
+
+ gimp_drawable_update (GIMP_DRAWABLE (channel), 0, 0, -1, -1);
+}
+
+static void
+gimp_channel_real_clear (GimpChannel *channel,
+ const gchar *undo_desc,
+ gboolean push_undo)
+{
+ GeglBuffer *buffer;
+ GeglRectangle rect;
+ GeglRectangle aligned_rect;
+
+ if (channel->bounds_known && channel->empty)
+ return;
+
+ if (push_undo)
+ {
+ if (! undo_desc)
+ undo_desc = GIMP_CHANNEL_GET_CLASS (channel)->clear_desc;
+
+ gimp_channel_push_undo (channel, undo_desc);
+ }
+
+ buffer = gimp_drawable_get_buffer (GIMP_DRAWABLE (channel));
+
+ if (channel->bounds_known)
+ {
+ rect.x = channel->x1;
+ rect.y = channel->y1;
+ rect.width = channel->x2 - channel->x1;
+ rect.height = channel->y2 - channel->y1;
+ }
+ else
+ {
+ rect.x = 0;
+ rect.y = 0;
+ rect.width = gimp_item_get_width (GIMP_ITEM (channel));
+ rect.height = gimp_item_get_height (GIMP_ITEM (channel));
+ }
+
+ gegl_rectangle_align_to_buffer (&aligned_rect, &rect, buffer,
+ GEGL_RECTANGLE_ALIGNMENT_SUPERSET);
+
+ gegl_buffer_clear (buffer, &aligned_rect);
+
+ /* we know the bounds */
+ channel->bounds_known = TRUE;
+ channel->empty = TRUE;
+ channel->x1 = 0;
+ channel->y1 = 0;
+ channel->x2 = gimp_item_get_width (GIMP_ITEM (channel));
+ channel->y2 = gimp_item_get_height (GIMP_ITEM (channel));
+
+ gimp_drawable_update (GIMP_DRAWABLE (channel),
+ rect.x, rect.y, rect.width, rect.height);
+}
+
+static void
+gimp_channel_real_all (GimpChannel *channel,
+ gboolean push_undo)
+{
+ GeglColor *color;
+
+ if (push_undo)
+ gimp_channel_push_undo (channel,
+ GIMP_CHANNEL_GET_CLASS (channel)->all_desc);
+
+ /* clear the channel */
+ color = gegl_color_new ("#fff");
+ gegl_buffer_set_color (gimp_drawable_get_buffer (GIMP_DRAWABLE (channel)),
+ NULL, color);
+ g_object_unref (color);
+
+ /* we know the bounds */
+ channel->bounds_known = TRUE;
+ channel->empty = FALSE;
+ channel->x1 = 0;
+ channel->y1 = 0;
+ channel->x2 = gimp_item_get_width (GIMP_ITEM (channel));
+ channel->y2 = gimp_item_get_height (GIMP_ITEM (channel));
+
+ gimp_drawable_update (GIMP_DRAWABLE (channel), 0, 0, -1, -1);
+}
+
+static void
+gimp_channel_real_invert (GimpChannel *channel,
+ gboolean push_undo)
+{
+ GimpDrawable *drawable = GIMP_DRAWABLE (channel);
+
+ if (push_undo)
+ gimp_channel_push_undo (channel,
+ GIMP_CHANNEL_GET_CLASS (channel)->invert_desc);
+
+ if (channel->bounds_known && channel->empty)
+ {
+ gimp_channel_all (channel, FALSE);
+ }
+ else
+ {
+ gimp_gegl_apply_invert_linear (gimp_drawable_get_buffer (drawable),
+ NULL, NULL,
+ gimp_drawable_get_buffer (drawable));
+
+ gimp_drawable_update (GIMP_DRAWABLE (channel), 0, 0, -1, -1);
+ }
+}
+
+static void
+gimp_channel_real_border (GimpChannel *channel,
+ gint radius_x,
+ gint radius_y,
+ GimpChannelBorderStyle style,
+ gboolean edge_lock,
+ gboolean push_undo)
+{
+ gint x1, y1, x2, y2;
+
+ if (radius_x == 0 && radius_y == 0)
+ {
+ /* The relevant GEGL operations require radius_x and radius_y to be > 0.
+ * When both are 0 (currently can only be achieved by the user through
+ * PDB), the effect should be to clear the channel.
+ */
+ gimp_channel_clear (channel,
+ GIMP_CHANNEL_GET_CLASS (channel)->border_desc,
+ push_undo);
+ return;
+ }
+ else if (radius_x <= 0 || radius_y <= 0)
+ {
+ /* FIXME: Implement the case where only one of radius_x and radius_y is 0.
+ * Currently, should never happen.
+ */
+ g_return_if_reached();
+ }
+
+ if (! gimp_item_bounds (GIMP_ITEM (channel), &x1, &y1, &x2, &y2))
+ return;
+
+ x2 += x1;
+ y2 += y1;
+
+ if (gimp_channel_is_empty (channel))
+ return;
+
+ if (x1 - radius_x < 0)
+ x1 = 0;
+ else
+ x1 -= radius_x;
+
+ if (x2 + radius_x > gimp_item_get_width (GIMP_ITEM (channel)))
+ x2 = gimp_item_get_width (GIMP_ITEM (channel));
+ else
+ x2 += radius_x;
+
+ if (y1 - radius_y < 0)
+ y1 = 0;
+ else
+ y1 -= radius_y;
+
+ if (y2 + radius_y > gimp_item_get_height (GIMP_ITEM (channel)))
+ y2 = gimp_item_get_height (GIMP_ITEM (channel));
+ else
+ y2 += radius_y;
+
+ if (push_undo)
+ gimp_channel_push_undo (channel,
+ GIMP_CHANNEL_GET_CLASS (channel)->border_desc);
+
+ gimp_gegl_apply_border (gimp_drawable_get_buffer (GIMP_DRAWABLE (channel)),
+ NULL, NULL,
+ gimp_drawable_get_buffer (GIMP_DRAWABLE (channel)),
+ GEGL_RECTANGLE (x1, y1, x2 - x1, y2 - y1),
+ radius_x, radius_y, style, edge_lock);
+
+ gimp_drawable_update (GIMP_DRAWABLE (channel), 0, 0, -1, -1);
+}
+
+static void
+gimp_channel_real_grow (GimpChannel *channel,
+ gint radius_x,
+ gint radius_y,
+ gboolean push_undo)
+{
+ gint x1, y1, x2, y2;
+
+ if (radius_x == 0 && radius_y == 0)
+ return;
+
+ if (radius_x <= 0 && radius_y <= 0)
+ {
+ gimp_channel_shrink (channel, -radius_x, -radius_y, FALSE, push_undo);
+ return;
+ }
+
+ if (radius_x < 0 || radius_y < 0)
+ return;
+
+ if (! gimp_item_bounds (GIMP_ITEM (channel), &x1, &y1, &x2, &y2))
+ return;
+
+ x2 += x1;
+ y2 += y1;
+
+ if (gimp_channel_is_empty (channel))
+ return;
+
+ if (x1 - radius_x > 0)
+ x1 = x1 - radius_x;
+ else
+ x1 = 0;
+
+ if (y1 - radius_y > 0)
+ y1 = y1 - radius_y;
+ else
+ y1 = 0;
+
+ if (x2 + radius_x < gimp_item_get_width (GIMP_ITEM (channel)))
+ x2 = x2 + radius_x;
+ else
+ x2 = gimp_item_get_width (GIMP_ITEM (channel));
+
+ if (y2 + radius_y < gimp_item_get_height (GIMP_ITEM (channel)))
+ y2 = y2 + radius_y;
+ else
+ y2 = gimp_item_get_height (GIMP_ITEM (channel));
+
+ if (push_undo)
+ gimp_channel_push_undo (channel,
+ GIMP_CHANNEL_GET_CLASS (channel)->grow_desc);
+
+ gimp_gegl_apply_grow (gimp_drawable_get_buffer (GIMP_DRAWABLE (channel)),
+ NULL, NULL,
+ gimp_drawable_get_buffer (GIMP_DRAWABLE (channel)),
+ GEGL_RECTANGLE (x1, y1, x2 - x1, y2 - y1),
+ radius_x, radius_y);
+
+ gimp_drawable_update (GIMP_DRAWABLE (channel), 0, 0, -1, -1);
+}
+
+static void
+gimp_channel_real_shrink (GimpChannel *channel,
+ gint radius_x,
+ gint radius_y,
+ gboolean edge_lock,
+ gboolean push_undo)
+{
+ gint x1, y1, x2, y2;
+
+ if (radius_x == 0 && radius_y == 0)
+ return;
+
+ if (radius_x <= 0 && radius_y <= 0)
+ {
+ gimp_channel_grow (channel, -radius_x, -radius_y, push_undo);
+ return;
+ }
+
+ if (radius_x < 0 || radius_y < 0)
+ return;
+
+ if (! gimp_item_bounds (GIMP_ITEM (channel), &x1, &y1, &x2, &y2))
+ return;
+
+ x2 += x1;
+ y2 += y1;
+
+ if (gimp_channel_is_empty (channel))
+ return;
+
+ if (x1 > 0)
+ x1--;
+ if (y1 > 0)
+ y1--;
+ if (x2 < gimp_item_get_width (GIMP_ITEM (channel)))
+ x2++;
+ if (y2 < gimp_item_get_height (GIMP_ITEM (channel)))
+ y2++;
+
+ if (push_undo)
+ gimp_channel_push_undo (channel,
+ GIMP_CHANNEL_GET_CLASS (channel)->shrink_desc);
+
+ gimp_gegl_apply_shrink (gimp_drawable_get_buffer (GIMP_DRAWABLE (channel)),
+ NULL, NULL,
+ gimp_drawable_get_buffer (GIMP_DRAWABLE (channel)),
+ GEGL_RECTANGLE (x1, y1, x2 - x1, y2 - y1),
+ radius_x, radius_y, edge_lock);
+
+ gimp_drawable_update (GIMP_DRAWABLE (channel), 0, 0, -1, -1);
+}
+
+static void
+gimp_channel_real_flood (GimpChannel *channel,
+ gboolean push_undo)
+{
+ gint x, y, width, height;
+
+ if (! gimp_item_bounds (GIMP_ITEM (channel), &x, &y, &width, &height))
+ return;
+
+ if (gimp_channel_is_empty (channel))
+ return;
+
+ if (push_undo)
+ gimp_channel_push_undo (channel,
+ GIMP_CHANNEL_GET_CLASS (channel)->flood_desc);
+
+ gimp_gegl_apply_flood (gimp_drawable_get_buffer (GIMP_DRAWABLE (channel)),
+ NULL, NULL,
+ gimp_drawable_get_buffer (GIMP_DRAWABLE (channel)),
+ GEGL_RECTANGLE (x, y, width, height));
+
+ gimp_drawable_update (GIMP_DRAWABLE (channel), x, y, width, height);
+}
+
+static void
+gimp_channel_buffer_changed (GeglBuffer *buffer,
+ const GeglRectangle *rect,
+ GimpChannel *channel)
+{
+ gimp_drawable_invalidate_boundary (GIMP_DRAWABLE (channel));
+}
+
+
+/* public functions */
+
+GimpChannel *
+gimp_channel_new (GimpImage *image,
+ gint width,
+ gint height,
+ const gchar *name,
+ const GimpRGB *color)
+{
+ GimpChannel *channel;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+
+ channel =
+ GIMP_CHANNEL (gimp_drawable_new (GIMP_TYPE_CHANNEL,
+ image, name,
+ 0, 0, width, height,
+ gimp_image_get_channel_format (image)));
+
+ if (color)
+ channel->color = *color;
+
+ channel->show_masked = TRUE;
+
+ /* selection mask variables */
+ channel->x2 = width;
+ channel->y2 = height;
+
+ return channel;
+}
+
+GimpChannel *
+gimp_channel_new_from_buffer (GimpImage *image,
+ GeglBuffer *buffer,
+ const gchar *name,
+ const GimpRGB *color)
+{
+ GimpChannel *channel;
+ GeglBuffer *dest;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+ g_return_val_if_fail (GEGL_IS_BUFFER (buffer), NULL);
+
+ channel = gimp_channel_new (image,
+ gegl_buffer_get_width (buffer),
+ gegl_buffer_get_height (buffer),
+ name, color);
+
+ dest = gimp_drawable_get_buffer (GIMP_DRAWABLE (channel));
+ gimp_gegl_buffer_copy (buffer, NULL, GEGL_ABYSS_NONE, dest, NULL);
+
+ return channel;
+}
+
+GimpChannel *
+gimp_channel_new_from_alpha (GimpImage *image,
+ GimpDrawable *drawable,
+ const gchar *name,
+ const GimpRGB *color)
+{
+ GimpChannel *channel;
+ GeglBuffer *dest_buffer;
+ gint width;
+ gint height;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), NULL);
+ g_return_val_if_fail (gimp_drawable_has_alpha (drawable), NULL);
+
+ width = gimp_item_get_width (GIMP_ITEM (drawable));
+ height = gimp_item_get_height (GIMP_ITEM (drawable));
+
+ channel = gimp_channel_new (image, width, height, name, color);
+
+ gimp_channel_clear (channel, NULL, FALSE);
+
+ dest_buffer = gimp_drawable_get_buffer (GIMP_DRAWABLE (channel));
+
+ gegl_buffer_set_format (dest_buffer,
+ gimp_drawable_get_component_format (drawable,
+ GIMP_CHANNEL_ALPHA));
+ gimp_gegl_buffer_copy (gimp_drawable_get_buffer (drawable), NULL,
+ GEGL_ABYSS_NONE,
+ dest_buffer, NULL);
+ gegl_buffer_set_format (dest_buffer, NULL);
+
+ return channel;
+}
+
+GimpChannel *
+gimp_channel_new_from_component (GimpImage *image,
+ GimpChannelType type,
+ const gchar *name,
+ const GimpRGB *color)
+{
+ GimpChannel *channel;
+ GeglBuffer *src_buffer;
+ GeglBuffer *dest_buffer;
+ gint width;
+ gint height;
+ const Babl *format;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+
+ format = gimp_image_get_component_format (image, type);
+
+ g_return_val_if_fail (format != NULL, NULL);
+
+ gimp_pickable_flush (GIMP_PICKABLE (image));
+
+ src_buffer = gimp_pickable_get_buffer (GIMP_PICKABLE (image));
+ width = gegl_buffer_get_width (src_buffer);
+ height = gegl_buffer_get_height (src_buffer);
+
+ channel = gimp_channel_new (image, width, height, name, color);
+
+ dest_buffer = gimp_drawable_get_buffer (GIMP_DRAWABLE (channel));
+
+ gegl_buffer_set_format (dest_buffer, format);
+ gimp_gegl_buffer_copy (src_buffer, NULL, GEGL_ABYSS_NONE, dest_buffer, NULL);
+ gegl_buffer_set_format (dest_buffer, NULL);
+
+ return channel;
+}
+
+GimpChannel *
+gimp_channel_get_parent (GimpChannel *channel)
+{
+ g_return_val_if_fail (GIMP_IS_CHANNEL (channel), NULL);
+
+ return GIMP_CHANNEL (gimp_viewable_get_parent (GIMP_VIEWABLE (channel)));
+}
+
+void
+gimp_channel_set_color (GimpChannel *channel,
+ const GimpRGB *color,
+ gboolean push_undo)
+{
+ g_return_if_fail (GIMP_IS_CHANNEL (channel));
+ g_return_if_fail (color != NULL);
+
+ if (gimp_rgba_distance (&channel->color, color) > RGBA_EPSILON)
+ {
+ if (push_undo && gimp_item_is_attached (GIMP_ITEM (channel)))
+ {
+ GimpImage *image = gimp_item_get_image (GIMP_ITEM (channel));
+
+ gimp_image_undo_push_channel_color (image, C_("undo-type", "Set Channel Color"),
+ channel);
+ }
+
+ channel->color = *color;
+
+ if (gimp_filter_peek_node (GIMP_FILTER (channel)))
+ {
+ gimp_gegl_node_set_color (channel->color_node,
+ &channel->color);
+ }
+
+ gimp_drawable_update (GIMP_DRAWABLE (channel), 0, 0, -1, -1);
+
+ g_signal_emit (channel, channel_signals[COLOR_CHANGED], 0);
+ }
+}
+
+void
+gimp_channel_get_color (GimpChannel *channel,
+ GimpRGB *color)
+{
+ g_return_if_fail (GIMP_IS_CHANNEL (channel));
+ g_return_if_fail (color != NULL);
+
+ *color = channel->color;
+}
+
+gdouble
+gimp_channel_get_opacity (GimpChannel *channel)
+{
+ g_return_val_if_fail (GIMP_IS_CHANNEL (channel), GIMP_OPACITY_TRANSPARENT);
+
+ return channel->color.a;
+}
+
+void
+gimp_channel_set_opacity (GimpChannel *channel,
+ gdouble opacity,
+ gboolean push_undo)
+{
+ g_return_if_fail (GIMP_IS_CHANNEL (channel));
+
+ opacity = CLAMP (opacity, GIMP_OPACITY_TRANSPARENT, GIMP_OPACITY_OPAQUE);
+
+ if (channel->color.a != opacity)
+ {
+ if (push_undo && gimp_item_is_attached (GIMP_ITEM (channel)))
+ {
+ GimpImage *image = gimp_item_get_image (GIMP_ITEM (channel));
+
+ gimp_image_undo_push_channel_color (image, C_("undo-type", "Set Channel Opacity"),
+ channel);
+ }
+
+ channel->color.a = opacity;
+
+ if (gimp_filter_peek_node (GIMP_FILTER (channel)))
+ {
+ gimp_gegl_node_set_color (channel->color_node,
+ &channel->color);
+ }
+
+ gimp_drawable_update (GIMP_DRAWABLE (channel), 0, 0, -1, -1);
+
+ g_signal_emit (channel, channel_signals[COLOR_CHANGED], 0);
+ }
+}
+
+gboolean
+gimp_channel_get_show_masked (GimpChannel *channel)
+{
+ g_return_val_if_fail (GIMP_IS_CHANNEL (channel), FALSE);
+
+ return channel->show_masked;
+}
+
+void
+gimp_channel_set_show_masked (GimpChannel *channel,
+ gboolean show_masked)
+{
+ g_return_if_fail (GIMP_IS_CHANNEL (channel));
+
+ if (show_masked != channel->show_masked)
+ {
+ channel->show_masked = show_masked ? TRUE : FALSE;
+
+ if (channel->invert_node)
+ {
+ GeglNode *source;
+
+ source = gimp_drawable_get_source_node (GIMP_DRAWABLE (channel));
+
+ if (channel->show_masked)
+ {
+ gegl_node_connect_to (source, "output",
+ channel->invert_node, "input");
+ gegl_node_connect_to (channel->invert_node, "output",
+ channel->mask_node, "aux");
+ }
+ else
+ {
+ gegl_node_disconnect (channel->invert_node, "input");
+
+ gegl_node_connect_to (source, "output",
+ channel->mask_node, "aux");
+ }
+ }
+
+ gimp_drawable_update (GIMP_DRAWABLE (channel), 0, 0, -1, -1);
+ }
+}
+
+void
+gimp_channel_push_undo (GimpChannel *channel,
+ const gchar *undo_desc)
+{
+ g_return_if_fail (GIMP_IS_CHANNEL (channel));
+ g_return_if_fail (gimp_item_is_attached (GIMP_ITEM (channel)));
+
+ gimp_image_undo_push_mask (gimp_item_get_image (GIMP_ITEM (channel)),
+ undo_desc, channel);
+}
+
+
+/******************************/
+/* selection mask functions */
+/******************************/
+
+GimpChannel *
+gimp_channel_new_mask (GimpImage *image,
+ gint width,
+ gint height)
+{
+ GimpChannel *channel;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+
+ channel =
+ GIMP_CHANNEL (gimp_drawable_new (GIMP_TYPE_CHANNEL,
+ image, _("Selection Mask"),
+ 0, 0, width, height,
+ gimp_image_get_mask_format (image)));
+
+ channel->show_masked = TRUE;
+ channel->x2 = width;
+ channel->y2 = height;
+
+ gegl_buffer_clear (gimp_drawable_get_buffer (GIMP_DRAWABLE (channel)),
+ NULL);
+
+ return channel;
+}
+
+gboolean
+gimp_channel_boundary (GimpChannel *channel,
+ const GimpBoundSeg **segs_in,
+ const GimpBoundSeg **segs_out,
+ gint *num_segs_in,
+ gint *num_segs_out,
+ gint x1,
+ gint y1,
+ gint x2,
+ gint y2)
+{
+ g_return_val_if_fail (GIMP_IS_CHANNEL (channel), FALSE);
+ g_return_val_if_fail (segs_in != NULL, FALSE);
+ g_return_val_if_fail (segs_out != NULL, FALSE);
+ g_return_val_if_fail (num_segs_in != NULL, FALSE);
+ g_return_val_if_fail (num_segs_out != NULL, FALSE);
+
+ return GIMP_CHANNEL_GET_CLASS (channel)->boundary (channel,
+ segs_in, segs_out,
+ num_segs_in, num_segs_out,
+ x1, y1,
+ x2, y2);
+}
+
+gboolean
+gimp_channel_is_empty (GimpChannel *channel)
+{
+ g_return_val_if_fail (GIMP_IS_CHANNEL (channel), TRUE);
+
+ return GIMP_CHANNEL_GET_CLASS (channel)->is_empty (channel);
+}
+
+void
+gimp_channel_feather (GimpChannel *channel,
+ gdouble radius_x,
+ gdouble radius_y,
+ gboolean edge_lock,
+ gboolean push_undo)
+{
+ g_return_if_fail (GIMP_IS_CHANNEL (channel));
+
+ if (! gimp_item_is_attached (GIMP_ITEM (channel)))
+ push_undo = FALSE;
+
+ GIMP_CHANNEL_GET_CLASS (channel)->feather (channel, radius_x, radius_y,
+ edge_lock, push_undo);
+}
+
+void
+gimp_channel_sharpen (GimpChannel *channel,
+ gboolean push_undo)
+{
+ g_return_if_fail (GIMP_IS_CHANNEL (channel));
+
+ if (! gimp_item_is_attached (GIMP_ITEM (channel)))
+ push_undo = FALSE;
+
+ GIMP_CHANNEL_GET_CLASS (channel)->sharpen (channel, push_undo);
+}
+
+void
+gimp_channel_clear (GimpChannel *channel,
+ const gchar *undo_desc,
+ gboolean push_undo)
+{
+ g_return_if_fail (GIMP_IS_CHANNEL (channel));
+
+ if (! gimp_item_is_attached (GIMP_ITEM (channel)))
+ push_undo = FALSE;
+
+ GIMP_CHANNEL_GET_CLASS (channel)->clear (channel, undo_desc, push_undo);
+}
+
+void
+gimp_channel_all (GimpChannel *channel,
+ gboolean push_undo)
+{
+ g_return_if_fail (GIMP_IS_CHANNEL (channel));
+
+ if (! gimp_item_is_attached (GIMP_ITEM (channel)))
+ push_undo = FALSE;
+
+ GIMP_CHANNEL_GET_CLASS (channel)->all (channel, push_undo);
+}
+
+void
+gimp_channel_invert (GimpChannel *channel,
+ gboolean push_undo)
+{
+ g_return_if_fail (GIMP_IS_CHANNEL (channel));
+
+ if (! gimp_item_is_attached (GIMP_ITEM (channel)))
+ push_undo = FALSE;
+
+ GIMP_CHANNEL_GET_CLASS (channel)->invert (channel, push_undo);
+}
+
+void
+gimp_channel_border (GimpChannel *channel,
+ gint radius_x,
+ gint radius_y,
+ GimpChannelBorderStyle style,
+ gboolean edge_lock,
+ gboolean push_undo)
+{
+ g_return_if_fail (GIMP_IS_CHANNEL (channel));
+
+ if (! gimp_item_is_attached (GIMP_ITEM (channel)))
+ push_undo = FALSE;
+
+ GIMP_CHANNEL_GET_CLASS (channel)->border (channel,
+ radius_x, radius_y, style, edge_lock,
+ push_undo);
+}
+
+void
+gimp_channel_grow (GimpChannel *channel,
+ gint radius_x,
+ gint radius_y,
+ gboolean push_undo)
+{
+ g_return_if_fail (GIMP_IS_CHANNEL (channel));
+
+ if (! gimp_item_is_attached (GIMP_ITEM (channel)))
+ push_undo = FALSE;
+
+ GIMP_CHANNEL_GET_CLASS (channel)->grow (channel, radius_x, radius_y,
+ push_undo);
+}
+
+void
+gimp_channel_shrink (GimpChannel *channel,
+ gint radius_x,
+ gint radius_y,
+ gboolean edge_lock,
+ gboolean push_undo)
+{
+ g_return_if_fail (GIMP_IS_CHANNEL (channel));
+
+ if (! gimp_item_is_attached (GIMP_ITEM (channel)))
+ push_undo = FALSE;
+
+ GIMP_CHANNEL_GET_CLASS (channel)->shrink (channel, radius_x, radius_y,
+ edge_lock, push_undo);
+}
+
+void
+gimp_channel_flood (GimpChannel *channel,
+ gboolean push_undo)
+{
+ g_return_if_fail (GIMP_IS_CHANNEL (channel));
+
+ if (! gimp_item_is_attached (GIMP_ITEM (channel)))
+ push_undo = FALSE;
+
+ GIMP_CHANNEL_GET_CLASS (channel)->flood (channel, push_undo);
+}
diff --git a/app/core/gimpchannel.h b/app/core/gimpchannel.h
new file mode 100644
index 0000000..348f4f6
--- /dev/null
+++ b/app/core/gimpchannel.h
@@ -0,0 +1,216 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_CHANNEL_H__
+#define __GIMP_CHANNEL_H__
+
+#include "gimpdrawable.h"
+
+
+#define GIMP_TYPE_CHANNEL (gimp_channel_get_type ())
+#define GIMP_CHANNEL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CHANNEL, GimpChannel))
+#define GIMP_CHANNEL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CHANNEL, GimpChannelClass))
+#define GIMP_IS_CHANNEL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CHANNEL))
+#define GIMP_IS_CHANNEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CHANNEL))
+#define GIMP_CHANNEL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_CHANNEL, GimpChannelClass))
+
+
+typedef struct _GimpChannelClass GimpChannelClass;
+
+struct _GimpChannel
+{
+ GimpDrawable parent_instance;
+
+ GimpRGB color; /* Also stores the opacity */
+ gboolean show_masked; /* Show masked areas--as */
+ /* opposed to selected areas */
+
+ GeglNode *color_node;
+ GeglNode *invert_node;
+ GeglNode *mask_node;
+
+ /* Selection mask variables */
+ gboolean boundary_known; /* is the current boundary valid */
+ GimpBoundSeg *segs_in; /* outline of selected region */
+ GimpBoundSeg *segs_out; /* outline of selected region */
+ gint num_segs_in; /* number of lines in boundary */
+ gint num_segs_out; /* number of lines in boundary */
+ gboolean empty; /* is the region empty? */
+ gboolean bounds_known; /* recalculate the bounds? */
+ gint x1, y1; /* coordinates for bounding box */
+ gint x2, y2; /* lower right hand coordinate */
+};
+
+struct _GimpChannelClass
+{
+ GimpDrawableClass parent_class;
+
+ /* signals */
+ void (* color_changed) (GimpChannel *channel);
+
+ /* virtual functions */
+ gboolean (* boundary) (GimpChannel *channel,
+ const GimpBoundSeg **segs_in,
+ const GimpBoundSeg **segs_out,
+ gint *num_segs_in,
+ gint *num_segs_out,
+ gint x1,
+ gint y1,
+ gint x2,
+ gint y2);
+ gboolean (* is_empty) (GimpChannel *channel);
+
+ void (* feather) (GimpChannel *channel,
+ gdouble radius_x,
+ gdouble radius_y,
+ gboolean edge_lock,
+ gboolean push_undo);
+ void (* sharpen) (GimpChannel *channel,
+ gboolean push_undo);
+ void (* clear) (GimpChannel *channel,
+ const gchar *undo_desc,
+ gboolean push_undo);
+ void (* all) (GimpChannel *channel,
+ gboolean push_undo);
+ void (* invert) (GimpChannel *channel,
+ gboolean push_undo);
+ void (* border) (GimpChannel *channel,
+ gint radius_x,
+ gint radius_y,
+ GimpChannelBorderStyle style,
+ gboolean edge_lock,
+ gboolean push_undo);
+ void (* grow) (GimpChannel *channel,
+ gint radius_x,
+ gint radius_y,
+ gboolean push_undo);
+ void (* shrink) (GimpChannel *channel,
+ gint radius_x,
+ gint radius_y,
+ gboolean edge_lock,
+ gboolean push_undo);
+ void (* flood) (GimpChannel *channel,
+ gboolean push_undo);
+
+ const gchar *feather_desc;
+ const gchar *sharpen_desc;
+ const gchar *clear_desc;
+ const gchar *all_desc;
+ const gchar *invert_desc;
+ const gchar *border_desc;
+ const gchar *grow_desc;
+ const gchar *shrink_desc;
+ const gchar *flood_desc;
+};
+
+
+/* function declarations */
+
+GType gimp_channel_get_type (void) G_GNUC_CONST;
+
+GimpChannel * gimp_channel_new (GimpImage *image,
+ gint width,
+ gint height,
+ const gchar *name,
+ const GimpRGB *color);
+GimpChannel * gimp_channel_new_from_buffer (GimpImage *image,
+ GeglBuffer *buffer,
+ const gchar *name,
+ const GimpRGB *color);
+GimpChannel * gimp_channel_new_from_alpha (GimpImage *image,
+ GimpDrawable *drawable,
+ const gchar *name,
+ const GimpRGB *color);
+GimpChannel * gimp_channel_new_from_component (GimpImage *image,
+ GimpChannelType type,
+ const gchar *name,
+ const GimpRGB *color);
+
+GimpChannel * gimp_channel_get_parent (GimpChannel *channel);
+
+gdouble gimp_channel_get_opacity (GimpChannel *channel);
+void gimp_channel_set_opacity (GimpChannel *channel,
+ gdouble opacity,
+ gboolean push_undo);
+
+void gimp_channel_get_color (GimpChannel *channel,
+ GimpRGB *color);
+void gimp_channel_set_color (GimpChannel *channel,
+ const GimpRGB *color,
+ gboolean push_undo);
+
+gboolean gimp_channel_get_show_masked (GimpChannel *channel);
+void gimp_channel_set_show_masked (GimpChannel *channel,
+ gboolean show_masked);
+
+void gimp_channel_push_undo (GimpChannel *mask,
+ const gchar *undo_desc);
+
+
+/* selection mask functions */
+
+GimpChannel * gimp_channel_new_mask (GimpImage *image,
+ gint width,
+ gint height);
+
+gboolean gimp_channel_boundary (GimpChannel *mask,
+ const GimpBoundSeg **segs_in,
+ const GimpBoundSeg **segs_out,
+ gint *num_segs_in,
+ gint *num_segs_out,
+ gint x1,
+ gint y1,
+ gint x2,
+ gint y2);
+gboolean gimp_channel_is_empty (GimpChannel *mask);
+
+void gimp_channel_feather (GimpChannel *mask,
+ gdouble radius_x,
+ gdouble radius_y,
+ gboolean edge_lock,
+ gboolean push_undo);
+void gimp_channel_sharpen (GimpChannel *mask,
+ gboolean push_undo);
+
+void gimp_channel_clear (GimpChannel *mask,
+ const gchar *undo_desc,
+ gboolean push_undo);
+void gimp_channel_all (GimpChannel *mask,
+ gboolean push_undo);
+void gimp_channel_invert (GimpChannel *mask,
+ gboolean push_undo);
+
+void gimp_channel_border (GimpChannel *mask,
+ gint radius_x,
+ gint radius_y,
+ GimpChannelBorderStyle style,
+ gboolean edge_lock,
+ gboolean push_undo);
+void gimp_channel_grow (GimpChannel *mask,
+ gint radius_x,
+ gint radius_y,
+ gboolean push_undo);
+void gimp_channel_shrink (GimpChannel *mask,
+ gint radius_x,
+ gint radius_y,
+ gboolean edge_lock,
+ gboolean push_undo);
+void gimp_channel_flood (GimpChannel *mask,
+ gboolean push_undo);
+
+
+#endif /* __GIMP_CHANNEL_H__ */
diff --git a/app/core/gimpchannelpropundo.c b/app/core/gimpchannelpropundo.c
new file mode 100644
index 0000000..f140d28
--- /dev/null
+++ b/app/core/gimpchannelpropundo.c
@@ -0,0 +1,108 @@
+/* Gimp - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpbase/gimpbase.h"
+
+#include "core-types.h"
+
+#include "gimpimage.h"
+#include "gimpchannel.h"
+#include "gimpchannelpropundo.h"
+
+
+static void gimp_channel_prop_undo_constructed (GObject *object);
+
+static void gimp_channel_prop_undo_pop (GimpUndo *undo,
+ GimpUndoMode undo_mode,
+ GimpUndoAccumulator *accum);
+
+
+G_DEFINE_TYPE (GimpChannelPropUndo, gimp_channel_prop_undo, GIMP_TYPE_ITEM_UNDO)
+
+#define parent_class gimp_channel_prop_undo_parent_class
+
+
+static void
+gimp_channel_prop_undo_class_init (GimpChannelPropUndoClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpUndoClass *undo_class = GIMP_UNDO_CLASS (klass);
+
+ object_class->constructed = gimp_channel_prop_undo_constructed;
+
+ undo_class->pop = gimp_channel_prop_undo_pop;
+}
+
+static void
+gimp_channel_prop_undo_init (GimpChannelPropUndo *undo)
+{
+}
+
+static void
+gimp_channel_prop_undo_constructed (GObject *object)
+{
+ GimpChannelPropUndo *channel_prop_undo = GIMP_CHANNEL_PROP_UNDO (object);
+ GimpChannel *channel;
+
+ G_OBJECT_CLASS (parent_class)->constructed (object);
+
+ gimp_assert (GIMP_IS_CHANNEL (GIMP_ITEM_UNDO (object)->item));
+
+ channel = GIMP_CHANNEL (GIMP_ITEM_UNDO (object)->item);
+
+ switch (GIMP_UNDO (object)->undo_type)
+ {
+ case GIMP_UNDO_CHANNEL_COLOR:
+ gimp_channel_get_color (channel, &channel_prop_undo->color);
+ break;
+
+ default:
+ g_return_if_reached ();
+ }
+}
+
+static void
+gimp_channel_prop_undo_pop (GimpUndo *undo,
+ GimpUndoMode undo_mode,
+ GimpUndoAccumulator *accum)
+{
+ GimpChannelPropUndo *channel_prop_undo = GIMP_CHANNEL_PROP_UNDO (undo);
+ GimpChannel *channel = GIMP_CHANNEL (GIMP_ITEM_UNDO (undo)->item);
+
+ GIMP_UNDO_CLASS (parent_class)->pop (undo, undo_mode, accum);
+
+ switch (undo->undo_type)
+ {
+ case GIMP_UNDO_CHANNEL_COLOR:
+ {
+ GimpRGB color;
+
+ gimp_channel_get_color (channel, &color);
+ gimp_channel_set_color (channel, &channel_prop_undo->color, FALSE);
+ channel_prop_undo->color = color;
+ }
+ break;
+
+ default:
+ g_return_if_reached ();
+ }
+}
diff --git a/app/core/gimpchannelpropundo.h b/app/core/gimpchannelpropundo.h
new file mode 100644
index 0000000..2aa7ad5
--- /dev/null
+++ b/app/core/gimpchannelpropundo.h
@@ -0,0 +1,52 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_CHANNEL_PROP_UNDO_H__
+#define __GIMP_CHANNEL_PROP_UNDO_H__
+
+
+#include "gimpitemundo.h"
+
+
+#define GIMP_TYPE_CHANNEL_PROP_UNDO (gimp_channel_prop_undo_get_type ())
+#define GIMP_CHANNEL_PROP_UNDO(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CHANNEL_PROP_UNDO, GimpChannelPropUndo))
+#define GIMP_CHANNEL_PROP_UNDO_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CHANNEL_PROP_UNDO, GimpChannelPropUndoClass))
+#define GIMP_IS_CHANNEL_PROP_UNDO(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CHANNEL_PROP_UNDO))
+#define GIMP_IS_CHANNEL_PROP_UNDO_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CHANNEL_PROP_UNDO))
+#define GIMP_CHANNEL_PROP_UNDO_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_CHANNEL_PROP_UNDO, GimpChannelPropUndoClass))
+
+
+typedef struct _GimpChannelPropUndo GimpChannelPropUndo;
+typedef struct _GimpChannelPropUndoClass GimpChannelPropUndoClass;
+
+struct _GimpChannelPropUndo
+{
+ GimpItemUndo parent_instance;
+
+ GimpRGB color;
+};
+
+struct _GimpChannelPropUndoClass
+{
+ GimpItemUndoClass parent_class;
+};
+
+
+GType gimp_channel_prop_undo_get_type (void) G_GNUC_CONST;
+
+
+#endif /* __GIMP_CHANNEL_PROP_UNDO_H__ */
diff --git a/app/core/gimpchannelundo.c b/app/core/gimpchannelundo.c
new file mode 100644
index 0000000..1892ced
--- /dev/null
+++ b/app/core/gimpchannelundo.c
@@ -0,0 +1,214 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "core-types.h"
+
+#include "gimpimage.h"
+#include "gimpchannel.h"
+#include "gimpchannelundo.h"
+
+
+enum
+{
+ PROP_0,
+ PROP_PREV_PARENT,
+ PROP_PREV_POSITION,
+ PROP_PREV_CHANNEL
+};
+
+
+static void gimp_channel_undo_constructed (GObject *object);
+static void gimp_channel_undo_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_channel_undo_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static gint64 gimp_channel_undo_get_memsize (GimpObject *object,
+ gint64 *gui_size);
+
+static void gimp_channel_undo_pop (GimpUndo *undo,
+ GimpUndoMode undo_mode,
+ GimpUndoAccumulator *accum);
+
+
+G_DEFINE_TYPE (GimpChannelUndo, gimp_channel_undo, GIMP_TYPE_ITEM_UNDO)
+
+#define parent_class gimp_channel_undo_parent_class
+
+
+static void
+gimp_channel_undo_class_init (GimpChannelUndoClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpObjectClass *gimp_object_class = GIMP_OBJECT_CLASS (klass);
+ GimpUndoClass *undo_class = GIMP_UNDO_CLASS (klass);
+
+ object_class->constructed = gimp_channel_undo_constructed;
+ object_class->set_property = gimp_channel_undo_set_property;
+ object_class->get_property = gimp_channel_undo_get_property;
+
+ gimp_object_class->get_memsize = gimp_channel_undo_get_memsize;
+
+ undo_class->pop = gimp_channel_undo_pop;
+
+ g_object_class_install_property (object_class, PROP_PREV_PARENT,
+ g_param_spec_object ("prev-parent",
+ NULL, NULL,
+ GIMP_TYPE_CHANNEL,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+
+ g_object_class_install_property (object_class, PROP_PREV_POSITION,
+ g_param_spec_int ("prev-position",
+ NULL, NULL,
+ 0, G_MAXINT, 0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+
+ g_object_class_install_property (object_class, PROP_PREV_CHANNEL,
+ g_param_spec_object ("prev-channel",
+ NULL, NULL,
+ GIMP_TYPE_CHANNEL,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+}
+
+static void
+gimp_channel_undo_init (GimpChannelUndo *undo)
+{
+}
+
+static void
+gimp_channel_undo_constructed (GObject *object)
+{
+ G_OBJECT_CLASS (parent_class)->constructed (object);
+
+ gimp_assert (GIMP_IS_CHANNEL (GIMP_ITEM_UNDO (object)->item));
+}
+
+static void
+gimp_channel_undo_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpChannelUndo *channel_undo = GIMP_CHANNEL_UNDO (object);
+
+ switch (property_id)
+ {
+ case PROP_PREV_PARENT:
+ channel_undo->prev_parent = g_value_get_object (value);
+ break;
+ case PROP_PREV_POSITION:
+ channel_undo->prev_position = g_value_get_int (value);
+ break;
+ case PROP_PREV_CHANNEL:
+ channel_undo->prev_channel = g_value_get_object (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_channel_undo_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpChannelUndo *channel_undo = GIMP_CHANNEL_UNDO (object);
+
+ switch (property_id)
+ {
+ case PROP_PREV_PARENT:
+ g_value_set_object (value, channel_undo->prev_parent);
+ break;
+ case PROP_PREV_POSITION:
+ g_value_set_int (value, channel_undo->prev_position);
+ break;
+ case PROP_PREV_CHANNEL:
+ g_value_set_object (value, channel_undo->prev_channel);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static gint64
+gimp_channel_undo_get_memsize (GimpObject *object,
+ gint64 *gui_size)
+{
+ GimpItemUndo *item_undo = GIMP_ITEM_UNDO (object);
+ gint64 memsize = 0;
+
+ if (! gimp_item_is_attached (item_undo->item))
+ memsize += gimp_object_get_memsize (GIMP_OBJECT (item_undo->item),
+ gui_size);
+
+ return memsize + GIMP_OBJECT_CLASS (parent_class)->get_memsize (object,
+ gui_size);
+}
+
+static void
+gimp_channel_undo_pop (GimpUndo *undo,
+ GimpUndoMode undo_mode,
+ GimpUndoAccumulator *accum)
+{
+ GimpChannelUndo *channel_undo = GIMP_CHANNEL_UNDO (undo);
+ GimpChannel *channel = GIMP_CHANNEL (GIMP_ITEM_UNDO (undo)->item);
+
+ GIMP_UNDO_CLASS (parent_class)->pop (undo, undo_mode, accum);
+
+ if ((undo_mode == GIMP_UNDO_MODE_UNDO &&
+ undo->undo_type == GIMP_UNDO_CHANNEL_ADD) ||
+ (undo_mode == GIMP_UNDO_MODE_REDO &&
+ undo->undo_type == GIMP_UNDO_CHANNEL_REMOVE))
+ {
+ /* remove channel */
+
+ /* record the current parent and position */
+ channel_undo->prev_parent = gimp_channel_get_parent (channel);
+ channel_undo->prev_position = gimp_item_get_index (GIMP_ITEM (channel));
+
+ gimp_image_remove_channel (undo->image, channel, FALSE,
+ channel_undo->prev_channel);
+ }
+ else
+ {
+ /* restore channel */
+
+ /* record the active channel */
+ channel_undo->prev_channel = gimp_image_get_active_channel (undo->image);
+
+ gimp_image_add_channel (undo->image, channel,
+ channel_undo->prev_parent,
+ channel_undo->prev_position, FALSE);
+ }
+}
diff --git a/app/core/gimpchannelundo.h b/app/core/gimpchannelundo.h
new file mode 100644
index 0000000..7bfceb9
--- /dev/null
+++ b/app/core/gimpchannelundo.h
@@ -0,0 +1,54 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_CHANNEL_UNDO_H__
+#define __GIMP_CHANNEL_UNDO_H__
+
+
+#include "gimpitemundo.h"
+
+
+#define GIMP_TYPE_CHANNEL_UNDO (gimp_channel_undo_get_type ())
+#define GIMP_CHANNEL_UNDO(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CHANNEL_UNDO, GimpChannelUndo))
+#define GIMP_CHANNEL_UNDO_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CHANNEL_UNDO, GimpChannelUndoClass))
+#define GIMP_IS_CHANNEL_UNDO(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CHANNEL_UNDO))
+#define GIMP_IS_CHANNEL_UNDO_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CHANNEL_UNDO))
+#define GIMP_CHANNEL_UNDO_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_CHANNEL_UNDO, GimpChannelUndoClass))
+
+
+typedef struct _GimpChannelUndo GimpChannelUndo;
+typedef struct _GimpChannelUndoClass GimpChannelUndoClass;
+
+struct _GimpChannelUndo
+{
+ GimpItemUndo parent_instance;
+
+ GimpChannel *prev_parent;
+ gint prev_position; /* former position in list */
+ GimpChannel *prev_channel; /* previous active channel */
+};
+
+struct _GimpChannelUndoClass
+{
+ GimpItemUndoClass parent_class;
+};
+
+
+GType gimp_channel_undo_get_type (void) G_GNUC_CONST;
+
+
+#endif /* __GIMP_CHANNEL_UNDO_H__ */
diff --git a/app/core/gimpchunkiterator.c b/app/core/gimpchunkiterator.c
new file mode 100644
index 0000000..616bb46
--- /dev/null
+++ b/app/core/gimpchunkiterator.c
@@ -0,0 +1,555 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-1999 Spencer Kimball and Peter Mattis
+ *
+ * gimpchunkiterator.c
+ * Copyright (C) 2019 Ell
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+
+#include "config.h"
+
+#include <stdlib.h>
+
+#include <cairo.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpmath/gimpmath.h"
+
+#include "core-types.h"
+
+#include "gimpchunkiterator.h"
+
+
+/* the maximal chunk size */
+#define MAX_CHUNK_WIDTH 4096
+#define MAX_CHUNK_HEIGHT 4096
+
+/* the default iteration interval */
+#define DEFAULT_INTERVAL (1.0 / 15.0) /* seconds */
+
+/* the minimal area to process per iteration */
+#define MIN_AREA_PER_ITERATION 4096
+
+/* the maximal ratio between the actual processed area and the target area,
+ * above which the current chunk height is readjusted, even in the middle of a
+ * row, to better match the target area
+ */
+#define MAX_AREA_RATIO 2.0
+
+/* the width of the target-area sliding window */
+#define TARGET_AREA_HISTORY_SIZE 3
+
+
+struct _GimpChunkIterator
+{
+ cairo_region_t *region;
+ cairo_region_t *priority_region;
+
+ GeglRectangle tile_rect;
+ GeglRectangle priority_rect;
+
+ gdouble interval;
+
+ cairo_region_t *current_region;
+ GeglRectangle current_rect;
+
+ gint current_x;
+ gint current_y;
+ gint current_height;
+
+ gint64 iteration_time;
+
+ gint64 last_time;
+ gint last_area;
+
+ gdouble target_area;
+ gdouble target_area_min;
+ gdouble target_area_history[TARGET_AREA_HISTORY_SIZE];
+ gint target_area_history_i;
+ gint target_area_history_n;
+};
+
+
+/* local function prototypes */
+
+static void gimp_chunk_iterator_set_current_rect (GimpChunkIterator *iter,
+ const GeglRectangle *rect);
+static void gimp_chunk_iterator_merge_current_rect (GimpChunkIterator *iter);
+
+static void gimp_chunk_iterator_merge (GimpChunkIterator *iter);
+
+static gboolean gimp_chunk_iterator_prepare (GimpChunkIterator *iter);
+
+static void gimp_chunk_iterator_set_target_area (GimpChunkIterator *iter,
+ gdouble target_area);
+static gdouble gimp_chunk_iterator_get_target_area (GimpChunkIterator *iter);
+static void gimp_chunk_iterator_reset_target_area (GimpChunkIterator *iter);
+
+static void gimp_chunk_iterator_calc_rect (GimpChunkIterator *iter,
+ GeglRectangle *rect,
+ gboolean readjust_height);
+
+
+/* private functions */
+
+static void
+gimp_chunk_iterator_set_current_rect (GimpChunkIterator *iter,
+ const GeglRectangle *rect)
+{
+ cairo_region_subtract_rectangle (iter->current_region,
+ (const cairo_rectangle_int_t *) rect);
+
+ iter->current_rect = *rect;
+
+ iter->current_x = rect->x;
+ iter->current_y = rect->y;
+ iter->current_height = 0;
+}
+
+static void
+gimp_chunk_iterator_merge_current_rect (GimpChunkIterator *iter)
+{
+ GeglRectangle rect;
+
+ if (gegl_rectangle_is_empty (&iter->current_rect))
+ return;
+
+ /* merge the remainder of the current row */
+ rect.x = iter->current_x;
+ rect.y = iter->current_y;
+ rect.width = iter->current_rect.x + iter->current_rect.width -
+ iter->current_x;
+ rect.height = iter->current_height;
+
+ cairo_region_union_rectangle (iter->current_region,
+ (const cairo_rectangle_int_t *) &rect);
+
+ /* merge the remainder of the current rect */
+ rect.x = iter->current_rect.x;
+ rect.y = iter->current_y + iter->current_height;
+ rect.width = iter->current_rect.width;
+ rect.height = iter->current_rect.y + iter->current_rect.height - rect.y;
+
+ cairo_region_union_rectangle (iter->current_region,
+ (const cairo_rectangle_int_t *) &rect);
+
+ /* reset the current rect and coordinates */
+ iter->current_rect.x = 0;
+ iter->current_rect.y = 0;
+ iter->current_rect.width = 0;
+ iter->current_rect.height = 0;
+
+ iter->current_x = 0;
+ iter->current_y = 0;
+ iter->current_height = 0;
+}
+
+static void
+gimp_chunk_iterator_merge (GimpChunkIterator *iter)
+{
+ /* merge the current rect back to the current region */
+ gimp_chunk_iterator_merge_current_rect (iter);
+
+ /* merge the priority region back to the global region */
+ if (iter->priority_region)
+ {
+ cairo_region_union (iter->region, iter->priority_region);
+
+ g_clear_pointer (&iter->priority_region, cairo_region_destroy);
+
+ iter->current_region = iter->region;
+ }
+}
+
+static gboolean
+gimp_chunk_iterator_prepare (GimpChunkIterator *iter)
+{
+ if (iter->current_x == iter->current_rect.x + iter->current_rect.width)
+ {
+ iter->current_x = iter->current_rect.x;
+ iter->current_y += iter->current_height;
+ iter->current_height = 0;
+
+ if (iter->current_y == iter->current_rect.y + iter->current_rect.height)
+ {
+ GeglRectangle rect;
+
+ if (! iter->priority_region &&
+ ! gegl_rectangle_is_empty (&iter->priority_rect))
+ {
+ iter->priority_region = cairo_region_copy (iter->region);
+
+ cairo_region_intersect_rectangle (
+ iter->priority_region,
+ (const cairo_rectangle_int_t *) &iter->priority_rect);
+
+ cairo_region_subtract_rectangle (
+ iter->region,
+ (const cairo_rectangle_int_t *) &iter->priority_rect);
+ }
+
+ if (! iter->priority_region ||
+ cairo_region_is_empty (iter->priority_region))
+ {
+ iter->current_region = iter->region;
+ }
+ else
+ {
+ iter->current_region = iter->priority_region;
+ }
+
+ if (cairo_region_is_empty (iter->current_region))
+ {
+ iter->current_rect.x = 0;
+ iter->current_rect.y = 0;
+ iter->current_rect.width = 0;
+ iter->current_rect.height = 0;
+
+ iter->current_x = 0;
+ iter->current_y = 0;
+ iter->current_height = 0;
+
+ return FALSE;
+ }
+
+ cairo_region_get_rectangle (iter->current_region, 0,
+ (cairo_rectangle_int_t *) &rect);
+
+ gimp_chunk_iterator_set_current_rect (iter, &rect);
+ }
+ }
+
+ return TRUE;
+}
+
+static gint
+compare_double (const gdouble *x,
+ const gdouble *y)
+{
+ return (*x > *y) - (*x < *y);
+}
+
+static void
+gimp_chunk_iterator_set_target_area (GimpChunkIterator *iter,
+ gdouble target_area)
+{
+ gdouble target_area_history[TARGET_AREA_HISTORY_SIZE];
+
+ iter->target_area_min = MIN (iter->target_area_min, target_area);
+
+ iter->target_area_history[iter->target_area_history_i++] = target_area;
+
+ iter->target_area_history_n = MAX (iter->target_area_history_n,
+ iter->target_area_history_i);
+ iter->target_area_history_i %= TARGET_AREA_HISTORY_SIZE;
+
+ memcpy (target_area_history, iter->target_area_history,
+ iter->target_area_history_n * sizeof (gdouble));
+
+ qsort (target_area_history, iter->target_area_history_n, sizeof (gdouble),
+ (gpointer) compare_double);
+
+ iter->target_area = target_area_history[iter->target_area_history_n / 2];
+}
+
+static gdouble
+gimp_chunk_iterator_get_target_area (GimpChunkIterator *iter)
+{
+ if (iter->target_area)
+ return iter->target_area;
+ else
+ return iter->tile_rect.width * iter->tile_rect.height;
+}
+
+static void
+gimp_chunk_iterator_reset_target_area (GimpChunkIterator *iter)
+{
+ if (iter->target_area_history_n)
+ {
+ iter->target_area = iter->target_area_min;
+ iter->target_area_min = MAX_CHUNK_WIDTH * MAX_CHUNK_HEIGHT;
+ iter->target_area_history_i = 0;
+ iter->target_area_history_n = 0;
+ }
+}
+
+static void
+gimp_chunk_iterator_calc_rect (GimpChunkIterator *iter,
+ GeglRectangle *rect,
+ gboolean readjust_height)
+{
+ gdouble target_area;
+ gdouble aspect_ratio;
+ gint offset_x;
+ gint offset_y;
+
+ if (readjust_height)
+ gimp_chunk_iterator_reset_target_area (iter);
+
+ target_area = gimp_chunk_iterator_get_target_area (iter);
+
+ aspect_ratio = (gdouble) iter->tile_rect.height /
+ (gdouble) iter->tile_rect.width;
+
+ rect->x = iter->current_x;
+ rect->y = iter->current_y;
+
+ offset_x = rect->x - iter->tile_rect.x;
+ offset_y = rect->y - iter->tile_rect.y;
+
+ if (readjust_height)
+ {
+ rect->height = RINT ((offset_y + sqrt (target_area * aspect_ratio)) /
+ iter->tile_rect.height) *
+ iter->tile_rect.height -
+ offset_y;
+
+ if (rect->height <= 0)
+ rect->height += iter->tile_rect.height;
+
+ rect->height = MIN (rect->height,
+ iter->current_rect.y + iter->current_rect.height -
+ rect->y);
+ rect->height = MIN (rect->height, MAX_CHUNK_HEIGHT);
+ }
+ else
+ {
+ rect->height = iter->current_height;
+ }
+
+ rect->width = RINT ((offset_x + (gdouble) target_area /
+ (gdouble) rect->height) /
+ iter->tile_rect.width) *
+ iter->tile_rect.width -
+ offset_x;
+
+ if (rect->width <= 0)
+ rect->width += iter->tile_rect.width;
+
+ rect->width = MIN (rect->width,
+ iter->current_rect.x + iter->current_rect.width -
+ rect->x);
+ rect->width = MIN (rect->width, MAX_CHUNK_WIDTH);
+}
+
+
+/* public functions */
+
+GimpChunkIterator *
+gimp_chunk_iterator_new (cairo_region_t *region)
+{
+ GimpChunkIterator *iter;
+
+ g_return_val_if_fail (region != NULL, NULL);
+
+ iter = g_slice_new0 (GimpChunkIterator);
+
+ iter->region = region;
+ iter->current_region = region;
+
+ g_object_get (gegl_config (),
+ "tile-width", &iter->tile_rect.width,
+ "tile-height", &iter->tile_rect.height,
+ NULL);
+
+ iter->interval = DEFAULT_INTERVAL;
+
+ return iter;
+}
+
+void
+gimp_chunk_iterator_set_tile_rect (GimpChunkIterator *iter,
+ const GeglRectangle *rect)
+{
+ g_return_if_fail (iter != NULL);
+ g_return_if_fail (rect != NULL);
+ g_return_if_fail (! gegl_rectangle_is_empty (rect));
+
+ iter->tile_rect = *rect;
+}
+
+void
+gimp_chunk_iterator_set_priority_rect (GimpChunkIterator *iter,
+ const GeglRectangle *rect)
+{
+ const GeglRectangle empty_rect = {};
+
+ g_return_if_fail (iter != NULL);
+
+ if (! rect)
+ rect = &empty_rect;
+
+ if (! gegl_rectangle_equal (rect, &iter->priority_rect))
+ {
+ iter->priority_rect = *rect;
+
+ gimp_chunk_iterator_merge (iter);
+ }
+}
+
+void
+gimp_chunk_iterator_set_interval (GimpChunkIterator *iter,
+ gdouble interval)
+{
+ g_return_if_fail (iter != NULL);
+
+ interval = MAX (interval, 0.0);
+
+ if (interval != iter->interval)
+ {
+ if (iter->interval)
+ {
+ gdouble ratio = interval / iter->interval;
+ gint i;
+
+ iter->target_area *= ratio;
+
+ for (i = 0; i < TARGET_AREA_HISTORY_SIZE; i++)
+ iter->target_area_history[i] *= ratio;
+ }
+
+ iter->interval = interval;
+ }
+}
+
+gboolean
+gimp_chunk_iterator_next (GimpChunkIterator *iter)
+{
+ g_return_val_if_fail (iter != NULL, FALSE);
+
+ if (! gimp_chunk_iterator_prepare (iter))
+ {
+ gimp_chunk_iterator_stop (iter, TRUE);
+
+ return FALSE;
+ }
+
+ iter->iteration_time = g_get_monotonic_time ();
+
+ iter->last_time = iter->iteration_time;
+ iter->last_area = 0;
+
+ return TRUE;
+}
+
+gboolean
+gimp_chunk_iterator_get_rect (GimpChunkIterator *iter,
+ GeglRectangle *rect)
+{
+ gint64 time;
+
+ g_return_val_if_fail (iter != NULL, FALSE);
+ g_return_val_if_fail (rect != NULL, FALSE);
+
+ if (! gimp_chunk_iterator_prepare (iter))
+ return FALSE;
+
+ time = g_get_monotonic_time ();
+
+ if (iter->last_area >= MIN_AREA_PER_ITERATION)
+ {
+ gdouble interval;
+
+ interval = (gdouble) (time - iter->last_time) / G_TIME_SPAN_SECOND;
+
+ gimp_chunk_iterator_set_target_area (
+ iter,
+ iter->last_area * iter->interval / interval);
+
+ interval = (gdouble) (time - iter->iteration_time) / G_TIME_SPAN_SECOND;
+
+ if (interval > iter->interval)
+ return FALSE;
+ }
+
+ if (iter->current_x == iter->current_rect.x)
+ {
+ gimp_chunk_iterator_calc_rect (iter, rect, TRUE);
+ }
+ else
+ {
+ gimp_chunk_iterator_calc_rect (iter, rect, FALSE);
+
+ if (rect->width * rect->height >=
+ MAX_AREA_RATIO * gimp_chunk_iterator_get_target_area (iter))
+ {
+ GeglRectangle old_rect = *rect;
+
+ gimp_chunk_iterator_calc_rect (iter, rect, TRUE);
+
+ if (rect->height >= old_rect.height)
+ *rect = old_rect;
+ }
+ }
+
+ if (rect->height != iter->current_height)
+ {
+ /* if the chunk height changed in the middle of a row, merge the
+ * remaining area back into the current region, and reset the current
+ * area to the remainder of the row, using the new chunk height
+ */
+ if (rect->x != iter->current_rect.x)
+ {
+ GeglRectangle rem;
+
+ rem.x = rect->x;
+ rem.y = rect->y;
+ rem.width = iter->current_rect.x + iter->current_rect.width -
+ rect->x;
+ rem.height = rect->height;
+
+ gimp_chunk_iterator_merge_current_rect (iter);
+
+ gimp_chunk_iterator_set_current_rect (iter, &rem);
+ }
+
+ iter->current_height = rect->height;
+ }
+
+ iter->current_x += rect->width;
+
+ iter->last_time = time;
+ iter->last_area = rect->width * rect->height;
+
+ return TRUE;
+}
+
+cairo_region_t *
+gimp_chunk_iterator_stop (GimpChunkIterator *iter,
+ gboolean free_region)
+{
+ cairo_region_t *result = NULL;
+
+ g_return_val_if_fail (iter != NULL, NULL);
+
+ if (free_region)
+ {
+ cairo_region_destroy (iter->region);
+ }
+ else
+ {
+ gimp_chunk_iterator_merge (iter);
+
+ result = iter->region;
+ }
+
+ g_clear_pointer (&iter->priority_region, cairo_region_destroy);
+
+ g_slice_free (GimpChunkIterator, iter);
+
+ return result;
+}
diff --git a/app/core/gimpchunkiterator.h b/app/core/gimpchunkiterator.h
new file mode 100644
index 0000000..e1756f3
--- /dev/null
+++ b/app/core/gimpchunkiterator.h
@@ -0,0 +1,44 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpchunkiterator.h
+ * Copyright (C) 2019 Ell
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_CHUNK_ITEARTOR_H__
+#define __GIMP_CHUNK_ITEARTOR_H__
+
+
+GimpChunkIterator * gimp_chunk_iterator_new (cairo_region_t *region);
+
+void gimp_chunk_iterator_set_tile_rect (GimpChunkIterator *iter,
+ const GeglRectangle *rect);
+
+void gimp_chunk_iterator_set_priority_rect (GimpChunkIterator *iter,
+ const GeglRectangle *rect);
+
+void gimp_chunk_iterator_set_interval (GimpChunkIterator *iter,
+ gdouble interval);
+
+gboolean gimp_chunk_iterator_next (GimpChunkIterator *iter);
+gboolean gimp_chunk_iterator_get_rect (GimpChunkIterator *iter,
+ GeglRectangle *rect);
+
+cairo_region_t * gimp_chunk_iterator_stop (GimpChunkIterator *iter,
+ gboolean free_region);
+
+
+#endif /* __GIMP_CHUNK_ITEARTOR_H__ */
diff --git a/app/core/gimpcontainer-filter.c b/app/core/gimpcontainer-filter.c
new file mode 100644
index 0000000..956a4a0
--- /dev/null
+++ b/app/core/gimpcontainer-filter.c
@@ -0,0 +1,174 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-1997 Spencer Kimball and Peter Mattis
+ *
+ * gimpcontainer-filter.c
+ * Copyright (C) 2003 Sven Neumann <sven@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <glib-object.h>
+
+#include "core-types.h"
+
+#include "gimpcontainer.h"
+#include "gimpcontainer-filter.h"
+#include "gimplist.h"
+
+
+typedef struct
+{
+ GimpObjectFilterFunc filter;
+ GimpContainer *container;
+ gpointer user_data;
+} GimpContainerFilterContext;
+
+
+static void
+gimp_container_filter_foreach_func (GimpObject *object,
+ GimpContainerFilterContext *context)
+{
+ if (context->filter (object, context->user_data))
+ gimp_container_add (context->container, object);
+}
+
+/**
+ * gimp_container_filter:
+ * @container: a #GimpContainer to filter
+ * @filter: a #GimpObjectFilterFunc
+ * @user_data: a pointer passed to @filter
+ *
+ * Calls the supplied @filter function on each object in @container.
+ * A return value of %TRUE is interpreted as a match.
+ *
+ * Returns: a weak #GimpContainer filled with matching objects.
+ **/
+GimpContainer *
+gimp_container_filter (GimpContainer *container,
+ GimpObjectFilterFunc filter,
+ gpointer user_data)
+{
+ GimpContainer *result;
+ GimpContainerFilterContext context;
+
+ g_return_val_if_fail (GIMP_IS_CONTAINER (container), NULL);
+ g_return_val_if_fail (filter != NULL, NULL);
+
+ result =
+ g_object_new (G_TYPE_FROM_INSTANCE (container),
+ "children-type", gimp_container_get_children_type (container),
+ "policy", GIMP_CONTAINER_POLICY_WEAK,
+ NULL);
+
+ context.filter = filter;
+ context.container = result;
+ context.user_data = user_data;
+
+ gimp_container_foreach (container,
+ (GFunc) gimp_container_filter_foreach_func,
+ &context);
+
+ /* This is somewhat ugly, but it keeps lists in the same order. */
+ if (GIMP_IS_LIST (result))
+ gimp_list_reverse (GIMP_LIST (result));
+
+
+ return result;
+}
+
+
+static gboolean
+gimp_object_filter_by_name (GimpObject *object,
+ const GRegex *regex)
+{
+ return g_regex_match (regex, gimp_object_get_name (object), 0, NULL);
+}
+
+/**
+ * gimp_container_filter_by_name:
+ * @container: a #GimpContainer to filter
+ * @regexp: a regular expression (as a %NULL-terminated string)
+ * @error: error location to report errors or %NULL
+ *
+ * This function performs a case-insensitive regular expression search
+ * on the names of the GimpObjects in @container.
+ *
+ * Returns: a weak #GimpContainer filled with matching objects.
+ **/
+GimpContainer *
+gimp_container_filter_by_name (GimpContainer *container,
+ const gchar *regexp,
+ GError **error)
+{
+ GimpContainer *result;
+ GRegex *regex;
+
+ g_return_val_if_fail (GIMP_IS_CONTAINER (container), NULL);
+ g_return_val_if_fail (regexp != NULL, NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ regex = g_regex_new (regexp, G_REGEX_CASELESS | G_REGEX_OPTIMIZE, 0,
+ error);
+
+ if (! regex)
+ return NULL;
+
+ result =
+ gimp_container_filter (container,
+ (GimpObjectFilterFunc) gimp_object_filter_by_name,
+ regex);
+
+ g_regex_unref (regex);
+
+ return result;
+}
+
+
+gchar **
+gimp_container_get_filtered_name_array (GimpContainer *container,
+ const gchar *regexp,
+ gint *length)
+{
+ GimpContainer *weak;
+ GError *error = NULL;
+
+ g_return_val_if_fail (GIMP_IS_CONTAINER (container), NULL);
+ g_return_val_if_fail (length != NULL, NULL);
+
+ if (regexp == NULL || strlen (regexp) == 0)
+ return (gimp_container_get_name_array (container, length));
+
+ weak = gimp_container_filter_by_name (container, regexp, &error);
+
+ if (weak)
+ {
+ gchar **retval = gimp_container_get_name_array (weak, length);
+
+ g_object_unref (weak);
+
+ return retval;
+ }
+ else
+ {
+ g_warning ("%s", error->message);
+ g_error_free (error);
+
+ *length = 0;
+ return NULL;
+ }
+}
diff --git a/app/core/gimpcontainer-filter.h b/app/core/gimpcontainer-filter.h
new file mode 100644
index 0000000..7c98b32
--- /dev/null
+++ b/app/core/gimpcontainer-filter.h
@@ -0,0 +1,38 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-1997 Spencer Kimball and Peter Mattis
+ *
+ * gimpcontainer-filter.c
+ * Copyright (C) 2003 Sven Neumann <sven@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_CONTAINER_FILTER_H__
+#define __GIMP_CONTAINER_FILTER_H__
+
+
+GimpContainer * gimp_container_filter (GimpContainer *container,
+ GimpObjectFilterFunc filter,
+ gpointer user_data);
+GimpContainer * gimp_container_filter_by_name (GimpContainer *container,
+ const gchar *regexp,
+ GError **error);
+
+gchar ** gimp_container_get_filtered_name_array
+ (GimpContainer *container,
+ const gchar *regexp,
+ gint *length);
+
+
+#endif /* __GIMP_CONTAINER_FILTER_H__ */
diff --git a/app/core/gimpcontainer.c b/app/core/gimpcontainer.c
new file mode 100644
index 0000000..322a3ec
--- /dev/null
+++ b/app/core/gimpcontainer.c
@@ -0,0 +1,1167 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-1997 Spencer Kimball and Peter Mattis
+ *
+ * gimpcontainer.c
+ * Copyright (C) 2001 Michael Natterer <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gio/gio.h>
+#include <gegl.h>
+
+#include "libgimpconfig/gimpconfig.h"
+
+#include "core-types.h"
+
+#include "gimp.h"
+#include "gimp-memsize.h"
+#include "gimpcontainer.h"
+#include "gimpmarshal.h"
+
+
+/* #define DEBUG_CONTAINER */
+
+#ifdef DEBUG_CONTAINER
+#define D(stmnt) stmnt
+#else
+#define D(stmnt)
+#endif
+
+
+enum
+{
+ ADD,
+ REMOVE,
+ REORDER,
+ FREEZE,
+ THAW,
+ LAST_SIGNAL
+};
+
+enum
+{
+ PROP_0,
+ PROP_CHILDREN_TYPE,
+ PROP_POLICY
+};
+
+
+typedef struct
+{
+ gchar *signame;
+ GCallback callback;
+ gpointer callback_data;
+
+ GQuark quark; /* used to attach the signal id's of child signals */
+} GimpContainerHandler;
+
+struct _GimpContainerPrivate
+{
+ GType children_type;
+ GimpContainerPolicy policy;
+ gint n_children;
+
+ GList *handlers;
+ gint freeze_count;
+};
+
+
+/* local function prototypes */
+
+static void gimp_container_config_iface_init (GimpConfigInterface *iface);
+
+static void gimp_container_dispose (GObject *object);
+
+static void gimp_container_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_container_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static gint64 gimp_container_get_memsize (GimpObject *object,
+ gint64 *gui_size);
+
+static void gimp_container_real_add (GimpContainer *container,
+ GimpObject *object);
+static void gimp_container_real_remove (GimpContainer *container,
+ GimpObject *object);
+
+static gboolean gimp_container_serialize (GimpConfig *config,
+ GimpConfigWriter *writer,
+ gpointer data);
+static gboolean gimp_container_deserialize (GimpConfig *config,
+ GScanner *scanner,
+ gint nest_level,
+ gpointer data);
+
+static void gimp_container_disconnect_callback (GimpObject *object,
+ gpointer data);
+
+static void gimp_container_free_handler (GimpContainer *container,
+ GimpContainerHandler *handler);
+
+
+G_DEFINE_TYPE_WITH_CODE (GimpContainer, gimp_container, GIMP_TYPE_OBJECT,
+ G_ADD_PRIVATE (GimpContainer)
+ G_IMPLEMENT_INTERFACE (GIMP_TYPE_CONFIG,
+ gimp_container_config_iface_init))
+
+#define parent_class gimp_container_parent_class
+
+static guint container_signals[LAST_SIGNAL] = { 0, };
+
+
+static void
+gimp_container_class_init (GimpContainerClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpObjectClass *gimp_object_class = GIMP_OBJECT_CLASS (klass);
+
+ container_signals[ADD] =
+ g_signal_new ("add",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpContainerClass, add),
+ NULL, NULL,
+ gimp_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1,
+ GIMP_TYPE_OBJECT);
+
+ container_signals[REMOVE] =
+ g_signal_new ("remove",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpContainerClass, remove),
+ NULL, NULL,
+ gimp_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1,
+ GIMP_TYPE_OBJECT);
+
+ container_signals[REORDER] =
+ g_signal_new ("reorder",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpContainerClass, reorder),
+ NULL, NULL,
+ gimp_marshal_VOID__OBJECT_INT,
+ G_TYPE_NONE, 2,
+ GIMP_TYPE_OBJECT,
+ G_TYPE_INT);
+
+ container_signals[FREEZE] =
+ g_signal_new ("freeze",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GimpContainerClass, freeze),
+ NULL, NULL,
+ gimp_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ container_signals[THAW] =
+ g_signal_new ("thaw",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GimpContainerClass, thaw),
+ NULL, NULL,
+ gimp_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ object_class->dispose = gimp_container_dispose;
+ object_class->set_property = gimp_container_set_property;
+ object_class->get_property = gimp_container_get_property;
+
+ gimp_object_class->get_memsize = gimp_container_get_memsize;
+
+ klass->add = gimp_container_real_add;
+ klass->remove = gimp_container_real_remove;
+ klass->reorder = NULL;
+ klass->freeze = NULL;
+ klass->thaw = NULL;
+
+ klass->clear = NULL;
+ klass->have = NULL;
+ klass->foreach = NULL;
+ klass->search = NULL;
+ klass->get_unique_names = NULL;
+ klass->get_child_by_name = NULL;
+ klass->get_child_by_index = NULL;
+ klass->get_child_index = NULL;
+
+ g_object_class_install_property (object_class, PROP_CHILDREN_TYPE,
+ g_param_spec_gtype ("children-type",
+ NULL, NULL,
+ GIMP_TYPE_OBJECT,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+
+ g_object_class_install_property (object_class, PROP_POLICY,
+ g_param_spec_enum ("policy",
+ NULL, NULL,
+ GIMP_TYPE_CONTAINER_POLICY,
+ GIMP_CONTAINER_POLICY_STRONG,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+}
+
+static void
+gimp_container_config_iface_init (GimpConfigInterface *iface)
+{
+ iface->serialize = gimp_container_serialize;
+ iface->deserialize = gimp_container_deserialize;
+}
+
+static void
+gimp_container_init (GimpContainer *container)
+{
+ container->priv = gimp_container_get_instance_private (container);
+ container->priv->handlers = NULL;
+ container->priv->freeze_count = 0;
+
+ container->priv->children_type = G_TYPE_NONE;
+ container->priv->policy = GIMP_CONTAINER_POLICY_STRONG;
+ container->priv->n_children = 0;
+}
+
+static void
+gimp_container_dispose (GObject *object)
+{
+ GimpContainer *container = GIMP_CONTAINER (object);
+
+ gimp_container_clear (container);
+
+ while (container->priv->handlers)
+ gimp_container_remove_handler (container,
+ ((GimpContainerHandler *)
+ container->priv->handlers->data)->quark);
+
+ if (container->priv->children_type != G_TYPE_NONE)
+ {
+ g_type_class_unref (g_type_class_peek (container->priv->children_type));
+ container->priv->children_type = G_TYPE_NONE;
+ }
+
+ G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+gimp_container_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpContainer *container = GIMP_CONTAINER (object);
+
+ switch (property_id)
+ {
+ case PROP_CHILDREN_TYPE:
+ container->priv->children_type = g_value_get_gtype (value);
+ g_type_class_ref (container->priv->children_type);
+ break;
+ case PROP_POLICY:
+ container->priv->policy = g_value_get_enum (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_container_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpContainer *container = GIMP_CONTAINER (object);
+
+ switch (property_id)
+ {
+ case PROP_CHILDREN_TYPE:
+ g_value_set_gtype (value, container->priv->children_type);
+ break;
+ case PROP_POLICY:
+ g_value_set_enum (value, container->priv->policy);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static gint64
+gimp_container_get_memsize (GimpObject *object,
+ gint64 *gui_size)
+{
+ GimpContainer *container = GIMP_CONTAINER (object);
+ gint64 memsize = 0;
+ GList *list;
+
+ for (list = container->priv->handlers; list; list = g_list_next (list))
+ {
+ GimpContainerHandler *handler = list->data;
+
+ memsize += (sizeof (GList) +
+ sizeof (GimpContainerHandler) +
+ gimp_string_get_memsize (handler->signame));
+ }
+
+ return memsize + GIMP_OBJECT_CLASS (parent_class)->get_memsize (object,
+ gui_size);
+}
+
+static void
+gimp_container_real_add (GimpContainer *container,
+ GimpObject *object)
+{
+ container->priv->n_children++;
+}
+
+static void
+gimp_container_real_remove (GimpContainer *container,
+ GimpObject *object)
+{
+ container->priv->n_children--;
+}
+
+
+typedef struct
+{
+ GimpConfigWriter *writer;
+ gpointer data;
+ gboolean success;
+} SerializeData;
+
+static void
+gimp_container_serialize_foreach (GObject *object,
+ SerializeData *serialize_data)
+{
+ GimpConfigInterface *config_iface;
+ const gchar *name;
+
+ config_iface = GIMP_CONFIG_GET_INTERFACE (object);
+
+ if (! config_iface)
+ serialize_data->success = FALSE;
+
+ if (! serialize_data->success)
+ return;
+
+ gimp_config_writer_open (serialize_data->writer,
+ g_type_name (G_TYPE_FROM_INSTANCE (object)));
+
+ name = gimp_object_get_name (object);
+
+ if (name)
+ gimp_config_writer_string (serialize_data->writer, name);
+ else
+ gimp_config_writer_print (serialize_data->writer, "NULL", 4);
+
+ serialize_data->success = config_iface->serialize (GIMP_CONFIG (object),
+ serialize_data->writer,
+ serialize_data->data);
+ gimp_config_writer_close (serialize_data->writer);
+}
+
+static gboolean
+gimp_container_serialize (GimpConfig *config,
+ GimpConfigWriter *writer,
+ gpointer data)
+{
+ GimpContainer *container = GIMP_CONTAINER (config);
+ SerializeData serialize_data;
+
+ serialize_data.writer = writer;
+ serialize_data.data = data;
+ serialize_data.success = TRUE;
+
+ gimp_container_foreach (container,
+ (GFunc) gimp_container_serialize_foreach,
+ &serialize_data);
+
+ return serialize_data.success;
+}
+
+static gboolean
+gimp_container_deserialize (GimpConfig *config,
+ GScanner *scanner,
+ gint nest_level,
+ gpointer data)
+{
+ GimpContainer *container = GIMP_CONTAINER (config);
+ GTokenType token;
+
+ token = G_TOKEN_LEFT_PAREN;
+
+ while (g_scanner_peek_next_token (scanner) == token)
+ {
+ token = g_scanner_get_next_token (scanner);
+
+ switch (token)
+ {
+ case G_TOKEN_LEFT_PAREN:
+ token = G_TOKEN_IDENTIFIER;
+ break;
+
+ case G_TOKEN_IDENTIFIER:
+ {
+ GimpObject *child = NULL;
+ GType type;
+ gchar *name = NULL;
+ gboolean add_child = FALSE;
+
+ type = g_type_from_name (scanner->value.v_identifier);
+
+ if (! type)
+ {
+ g_scanner_error (scanner,
+ "unable to determine type of '%s'",
+ scanner->value.v_identifier);
+ return FALSE;
+ }
+
+ if (! g_type_is_a (type, container->priv->children_type))
+ {
+ g_scanner_error (scanner,
+ "'%s' is not a subclass of '%s'",
+ scanner->value.v_identifier,
+ g_type_name (container->priv->children_type));
+ return FALSE;
+ }
+
+ if (! g_type_is_a (type, GIMP_TYPE_CONFIG))
+ {
+ g_scanner_error (scanner,
+ "'%s' does not implement GimpConfigInterface",
+ scanner->value.v_identifier);
+ return FALSE;
+ }
+
+ if (! gimp_scanner_parse_string (scanner, &name))
+ {
+ token = G_TOKEN_STRING;
+ break;
+ }
+
+ if (! name)
+ name = g_strdup ("");
+
+ if (gimp_container_get_unique_names (container))
+ child = gimp_container_get_child_by_name (container, name);
+
+ if (! child)
+ {
+ if (GIMP_IS_GIMP (data))
+ child = g_object_new (type, "gimp", data, NULL);
+ else
+ child = g_object_new (type, NULL);
+
+ add_child = TRUE;
+ }
+
+ /* always use the deserialized name. while it normally
+ * doesn't make a difference there are obscure case like
+ * template migration.
+ */
+ gimp_object_take_name (child, name);
+
+ if (! GIMP_CONFIG_GET_INTERFACE (child)->deserialize (GIMP_CONFIG (child),
+ scanner,
+ nest_level + 1,
+ NULL))
+ {
+ if (add_child)
+ g_object_unref (child);
+
+ /* warning should be already set by child */
+ return FALSE;
+ }
+
+ if (add_child)
+ {
+ gimp_container_add (container, child);
+
+ if (container->priv->policy == GIMP_CONTAINER_POLICY_STRONG)
+ g_object_unref (child);
+ }
+ }
+ token = G_TOKEN_RIGHT_PAREN;
+ break;
+
+ case G_TOKEN_RIGHT_PAREN:
+ token = G_TOKEN_LEFT_PAREN;
+ break;
+
+ default: /* do nothing */
+ break;
+ }
+ }
+
+ return gimp_config_deserialize_return (scanner, token, nest_level);
+}
+
+static void
+gimp_container_disconnect_callback (GimpObject *object,
+ gpointer data)
+{
+ GimpContainer *container = GIMP_CONTAINER (data);
+
+ gimp_container_remove (container, object);
+}
+
+static void
+gimp_container_free_handler_foreach_func (GimpObject *object,
+ GimpContainerHandler *handler)
+{
+ gulong handler_id;
+
+ handler_id = GPOINTER_TO_UINT (g_object_get_qdata (G_OBJECT (object),
+ handler->quark));
+
+ if (handler_id)
+ {
+ g_signal_handler_disconnect (object, handler_id);
+
+ g_object_set_qdata (G_OBJECT (object), handler->quark, NULL);
+ }
+}
+
+static void
+gimp_container_free_handler (GimpContainer *container,
+ GimpContainerHandler *handler)
+{
+ D (g_print ("%s: id = %d\n", G_STRFUNC, handler->quark));
+
+ gimp_container_foreach (container,
+ (GFunc) gimp_container_free_handler_foreach_func,
+ handler);
+
+ g_free (handler->signame);
+ g_slice_free (GimpContainerHandler, handler);
+}
+
+GType
+gimp_container_get_children_type (GimpContainer *container)
+{
+ g_return_val_if_fail (GIMP_IS_CONTAINER (container), G_TYPE_NONE);
+
+ return container->priv->children_type;
+}
+
+GimpContainerPolicy
+gimp_container_get_policy (GimpContainer *container)
+{
+ g_return_val_if_fail (GIMP_IS_CONTAINER (container), 0);
+
+ return container->priv->policy;
+}
+
+gint
+gimp_container_get_n_children (GimpContainer *container)
+{
+ g_return_val_if_fail (GIMP_IS_CONTAINER (container), 0);
+
+ return container->priv->n_children;
+}
+
+gboolean
+gimp_container_add (GimpContainer *container,
+ GimpObject *object)
+{
+ GList *list;
+ gint n_children;
+
+ g_return_val_if_fail (GIMP_IS_CONTAINER (container), FALSE);
+ g_return_val_if_fail (object != NULL, FALSE);
+ g_return_val_if_fail (G_TYPE_CHECK_INSTANCE_TYPE (object,
+ container->priv->children_type),
+ FALSE);
+
+ if (gimp_container_have (container, object))
+ {
+ g_warning ("%s: container %p already contains object %p",
+ G_STRFUNC, container, object);
+ return FALSE;
+ }
+
+ for (list = container->priv->handlers; list; list = g_list_next (list))
+ {
+ GimpContainerHandler *handler = list->data;
+ gulong handler_id;
+
+ handler_id = g_signal_connect (object,
+ handler->signame,
+ handler->callback,
+ handler->callback_data);
+
+ g_object_set_qdata (G_OBJECT (object), handler->quark,
+ GUINT_TO_POINTER (handler_id));
+ }
+
+ switch (container->priv->policy)
+ {
+ case GIMP_CONTAINER_POLICY_STRONG:
+ g_object_ref (object);
+ break;
+
+ case GIMP_CONTAINER_POLICY_WEAK:
+ g_signal_connect (object, "disconnect",
+ G_CALLBACK (gimp_container_disconnect_callback),
+ container);
+ break;
+ }
+
+ n_children = container->priv->n_children;
+
+ g_signal_emit (container, container_signals[ADD], 0, object);
+
+ if (n_children == container->priv->n_children)
+ {
+ g_warning ("%s: GimpContainer::add() implementation did not "
+ "chain up. Please report this at https://www.gimp.org/bugs/",
+ G_STRFUNC);
+
+ container->priv->n_children++;
+ }
+
+ return TRUE;
+}
+
+gboolean
+gimp_container_remove (GimpContainer *container,
+ GimpObject *object)
+{
+ GList *list;
+ gint n_children;
+
+ g_return_val_if_fail (GIMP_IS_CONTAINER (container), FALSE);
+ g_return_val_if_fail (object != NULL, FALSE);
+ g_return_val_if_fail (G_TYPE_CHECK_INSTANCE_TYPE (object,
+ container->priv->children_type),
+ FALSE);
+
+ if (! gimp_container_have (container, object))
+ {
+ g_warning ("%s: container %p does not contain object %p",
+ G_STRFUNC, container, object);
+ return FALSE;
+ }
+
+ for (list = container->priv->handlers; list; list = g_list_next (list))
+ {
+ GimpContainerHandler *handler = list->data;
+ gulong handler_id;
+
+ handler_id = GPOINTER_TO_UINT (g_object_get_qdata (G_OBJECT (object),
+ handler->quark));
+
+ if (handler_id)
+ {
+ g_signal_handler_disconnect (object, handler_id);
+
+ g_object_set_qdata (G_OBJECT (object), handler->quark, NULL);
+ }
+ }
+
+ n_children = container->priv->n_children;
+
+ g_signal_emit (container, container_signals[REMOVE], 0, object);
+
+ if (n_children == container->priv->n_children)
+ {
+ g_warning ("%s: GimpContainer::remove() implementation did not "
+ "chain up. Please report this at https://www.gimp.org/bugs/",
+ G_STRFUNC);
+
+ container->priv->n_children--;
+ }
+
+ switch (container->priv->policy)
+ {
+ case GIMP_CONTAINER_POLICY_STRONG:
+ g_object_unref (object);
+ break;
+
+ case GIMP_CONTAINER_POLICY_WEAK:
+ g_signal_handlers_disconnect_by_func (object,
+ gimp_container_disconnect_callback,
+ container);
+ break;
+ }
+
+ return TRUE;
+}
+
+gboolean
+gimp_container_insert (GimpContainer *container,
+ GimpObject *object,
+ gint index)
+{
+ g_return_val_if_fail (GIMP_IS_CONTAINER (container), FALSE);
+ g_return_val_if_fail (object != NULL, FALSE);
+ g_return_val_if_fail (G_TYPE_CHECK_INSTANCE_TYPE (object,
+ container->priv->children_type),
+ FALSE);
+
+ g_return_val_if_fail (index >= -1 &&
+ index <= container->priv->n_children, FALSE);
+
+ if (gimp_container_have (container, object))
+ {
+ g_warning ("%s: container %p already contains object %p",
+ G_STRFUNC, container, object);
+ return FALSE;
+ }
+
+ if (gimp_container_add (container, object))
+ {
+ return gimp_container_reorder (container, object, index);
+ }
+
+ return FALSE;
+}
+
+gboolean
+gimp_container_reorder (GimpContainer *container,
+ GimpObject *object,
+ gint new_index)
+{
+ gint index;
+
+ g_return_val_if_fail (GIMP_IS_CONTAINER (container), FALSE);
+ g_return_val_if_fail (object != NULL, FALSE);
+ g_return_val_if_fail (G_TYPE_CHECK_INSTANCE_TYPE (object,
+ container->priv->children_type),
+ FALSE);
+
+ g_return_val_if_fail (new_index >= -1 &&
+ new_index < container->priv->n_children, FALSE);
+
+ if (new_index == -1)
+ new_index = container->priv->n_children - 1;
+
+ index = gimp_container_get_child_index (container, object);
+
+ if (index == -1)
+ {
+ g_warning ("%s: container %p does not contain object %p",
+ G_STRFUNC, container, object);
+ return FALSE;
+ }
+
+ if (index != new_index)
+ g_signal_emit (container, container_signals[REORDER], 0,
+ object, new_index);
+
+ return TRUE;
+}
+
+void
+gimp_container_freeze (GimpContainer *container)
+{
+ g_return_if_fail (GIMP_IS_CONTAINER (container));
+
+ container->priv->freeze_count++;
+
+ if (container->priv->freeze_count == 1)
+ g_signal_emit (container, container_signals[FREEZE], 0);
+}
+
+void
+gimp_container_thaw (GimpContainer *container)
+{
+ g_return_if_fail (GIMP_IS_CONTAINER (container));
+
+ if (container->priv->freeze_count > 0)
+ container->priv->freeze_count--;
+
+ if (container->priv->freeze_count == 0)
+ g_signal_emit (container, container_signals[THAW], 0);
+}
+
+gboolean
+gimp_container_frozen (GimpContainer *container)
+{
+ g_return_val_if_fail (GIMP_IS_CONTAINER (container), FALSE);
+
+ return (container->priv->freeze_count > 0) ? TRUE : FALSE;
+}
+
+gint
+gimp_container_freeze_count (GimpContainer *container)
+{
+ g_return_val_if_fail (GIMP_IS_CONTAINER (container), 0);
+
+ return container->priv->freeze_count;
+}
+
+void
+gimp_container_clear (GimpContainer *container)
+{
+ g_return_if_fail (GIMP_IS_CONTAINER (container));
+
+ if (container->priv->n_children > 0)
+ {
+ gimp_container_freeze (container);
+ GIMP_CONTAINER_GET_CLASS (container)->clear (container);
+ gimp_container_thaw (container);
+ }
+}
+
+gboolean
+gimp_container_is_empty (GimpContainer *container)
+{
+ g_return_val_if_fail (GIMP_IS_CONTAINER (container), FALSE);
+
+ return (container->priv->n_children == 0);
+}
+
+gboolean
+gimp_container_have (GimpContainer *container,
+ GimpObject *object)
+{
+ g_return_val_if_fail (GIMP_IS_CONTAINER (container), FALSE);
+
+ if (container->priv->n_children < 1)
+ return FALSE;
+
+ return GIMP_CONTAINER_GET_CLASS (container)->have (container, object);
+}
+
+void
+gimp_container_foreach (GimpContainer *container,
+ GFunc func,
+ gpointer user_data)
+{
+ g_return_if_fail (GIMP_IS_CONTAINER (container));
+ g_return_if_fail (func != NULL);
+
+ if (container->priv->n_children > 0)
+ GIMP_CONTAINER_GET_CLASS (container)->foreach (container, func, user_data);
+}
+
+GimpObject *
+gimp_container_search (GimpContainer *container,
+ GimpContainerSearchFunc func,
+ gpointer user_data)
+{
+ g_return_val_if_fail (GIMP_IS_CONTAINER (container), NULL);
+ g_return_val_if_fail (func != NULL, NULL);
+
+ if (container->priv->n_children > 0)
+ {
+ return GIMP_CONTAINER_GET_CLASS (container)->search (container,
+ func, user_data);
+ }
+
+ return NULL;
+}
+
+gboolean
+gimp_container_get_unique_names (GimpContainer *container)
+{
+ g_return_val_if_fail (GIMP_IS_CONTAINER (container), FALSE);
+
+ if (GIMP_CONTAINER_GET_CLASS (container)->get_unique_names)
+ return GIMP_CONTAINER_GET_CLASS (container)->get_unique_names (container);
+
+ return FALSE;
+}
+
+GimpObject *
+gimp_container_get_child_by_name (GimpContainer *container,
+ const gchar *name)
+{
+ g_return_val_if_fail (GIMP_IS_CONTAINER (container), NULL);
+
+ if (!name)
+ return NULL;
+
+ return GIMP_CONTAINER_GET_CLASS (container)->get_child_by_name (container,
+ name);
+}
+
+GimpObject *
+gimp_container_get_child_by_index (GimpContainer *container,
+ gint index)
+{
+ g_return_val_if_fail (GIMP_IS_CONTAINER (container), NULL);
+
+ if (index < 0 || index >= container->priv->n_children)
+ return NULL;
+
+ return GIMP_CONTAINER_GET_CLASS (container)->get_child_by_index (container,
+ index);
+}
+
+/**
+ * gimp_container_get_first_child:
+ * @container: a #GimpContainer
+ *
+ * Return value: the first child object stored in @container or %NULL if the
+ * container is empty
+ */
+GimpObject *
+gimp_container_get_first_child (GimpContainer *container)
+{
+ g_return_val_if_fail (GIMP_IS_CONTAINER (container), NULL);
+
+ if (container->priv->n_children > 0)
+ return GIMP_CONTAINER_GET_CLASS (container)->get_child_by_index (container,
+ 0);
+
+ return NULL;
+}
+
+/**
+ * gimp_container_get_last_child:
+ * @container: a #GimpContainer
+ *
+ * Return value: the last child object stored in @container or %NULL if the
+ * container is empty
+ */
+GimpObject *
+gimp_container_get_last_child (GimpContainer *container)
+{
+ g_return_val_if_fail (GIMP_IS_CONTAINER (container), NULL);
+
+ if (container->priv->n_children > 0)
+ return GIMP_CONTAINER_GET_CLASS (container)->get_child_by_index (container,
+ container->priv->n_children - 1);
+
+ return NULL;
+}
+
+gint
+gimp_container_get_child_index (GimpContainer *container,
+ GimpObject *object)
+{
+ g_return_val_if_fail (GIMP_IS_CONTAINER (container), -1);
+ g_return_val_if_fail (object != NULL, -1);
+ g_return_val_if_fail (G_TYPE_CHECK_INSTANCE_TYPE (object,
+ container->priv->children_type),
+ -1);
+
+ return GIMP_CONTAINER_GET_CLASS (container)->get_child_index (container,
+ object);
+}
+
+GimpObject *
+gimp_container_get_neighbor_of (GimpContainer *container,
+ GimpObject *object)
+{
+ gint index;
+
+ g_return_val_if_fail (GIMP_IS_CONTAINER (container), NULL);
+ g_return_val_if_fail (GIMP_IS_OBJECT (object), NULL);
+
+ index = gimp_container_get_child_index (container, object);
+
+ if (index != -1)
+ {
+ GimpObject *new;
+
+ new = gimp_container_get_child_by_index (container, index + 1);
+
+ if (! new && index > 0)
+ new = gimp_container_get_child_by_index (container, index - 1);
+
+ return new;
+ }
+
+ return NULL;
+}
+
+static void
+gimp_container_get_name_array_foreach_func (GimpObject *object,
+ gchar ***iter)
+{
+ gchar **array = *iter;
+
+ *array = g_strdup (gimp_object_get_name (object));
+ (*iter)++;
+}
+
+gchar **
+gimp_container_get_name_array (GimpContainer *container,
+ gint *length)
+{
+ gchar **names;
+ gchar **iter;
+
+ g_return_val_if_fail (GIMP_IS_CONTAINER (container), NULL);
+ g_return_val_if_fail (length != NULL, NULL);
+
+ *length = gimp_container_get_n_children (container);
+ if (*length == 0)
+ return NULL;
+
+ names = iter = g_new (gchar *, *length);
+
+ gimp_container_foreach (container,
+ (GFunc) gimp_container_get_name_array_foreach_func,
+ &iter);
+
+ return names;
+}
+
+static void
+gimp_container_add_handler_foreach_func (GimpObject *object,
+ GimpContainerHandler *handler)
+{
+ gulong handler_id;
+
+ handler_id = g_signal_connect (object,
+ handler->signame,
+ handler->callback,
+ handler->callback_data);
+
+ g_object_set_qdata (G_OBJECT (object), handler->quark,
+ GUINT_TO_POINTER (handler_id));
+}
+
+GQuark
+gimp_container_add_handler (GimpContainer *container,
+ const gchar *signame,
+ GCallback callback,
+ gpointer callback_data)
+{
+ GimpContainerHandler *handler;
+ gchar *key;
+
+ static gint handler_id = 0;
+
+ g_return_val_if_fail (GIMP_IS_CONTAINER (container), 0);
+ g_return_val_if_fail (signame != NULL, 0);
+ g_return_val_if_fail (callback != NULL, 0);
+
+ if (! g_str_has_prefix (signame, "notify::"))
+ g_return_val_if_fail (g_signal_lookup (signame,
+ container->priv->children_type), 0);
+
+ handler = g_slice_new0 (GimpContainerHandler);
+
+ /* create a unique key for this handler */
+ key = g_strdup_printf ("%s-%d", signame, handler_id++);
+
+ handler->signame = g_strdup (signame);
+ handler->callback = callback;
+ handler->callback_data = callback_data;
+ handler->quark = g_quark_from_string (key);
+
+ D (g_print ("%s: key = %s, id = %d\n", G_STRFUNC, key, handler->quark));
+
+ g_free (key);
+
+ container->priv->handlers = g_list_prepend (container->priv->handlers, handler);
+
+ gimp_container_foreach (container,
+ (GFunc) gimp_container_add_handler_foreach_func,
+ handler);
+
+ return handler->quark;
+}
+
+void
+gimp_container_remove_handler (GimpContainer *container,
+ GQuark id)
+{
+ GimpContainerHandler *handler;
+ GList *list;
+
+ g_return_if_fail (GIMP_IS_CONTAINER (container));
+ g_return_if_fail (id != 0);
+
+ for (list = container->priv->handlers; list; list = g_list_next (list))
+ {
+ handler = (GimpContainerHandler *) list->data;
+
+ if (handler->quark == id)
+ break;
+ }
+
+ if (! list)
+ {
+ g_warning ("%s: tried to remove handler which unknown id %d",
+ G_STRFUNC, id);
+ return;
+ }
+
+ gimp_container_free_handler (container, handler);
+
+ container->priv->handlers = g_list_delete_link (container->priv->handlers,
+ list);
+}
+
+void
+gimp_container_remove_handlers_by_func (GimpContainer *container,
+ GCallback callback,
+ gpointer callback_data)
+{
+ GList *list;
+
+ g_return_if_fail (GIMP_IS_CONTAINER (container));
+ g_return_if_fail (callback != NULL);
+
+ list = container->priv->handlers;
+
+ while (list)
+ {
+ GimpContainerHandler *handler = list->data;
+ GList *next = g_list_next (list);
+
+ if (handler->callback == callback &&
+ handler->callback_data == callback_data)
+ {
+ gimp_container_free_handler (container, handler);
+
+ container->priv->handlers = g_list_delete_link (
+ container->priv->handlers, list);
+ }
+
+ list = next;
+ }
+}
+
+void
+gimp_container_remove_handlers_by_data (GimpContainer *container,
+ gpointer callback_data)
+{
+ GList *list;
+
+ g_return_if_fail (GIMP_IS_CONTAINER (container));
+
+ list = container->priv->handlers;
+
+ while (list)
+ {
+ GimpContainerHandler *handler = list->data;
+ GList *next = g_list_next (list);
+
+ if (handler->callback_data == callback_data)
+ {
+ gimp_container_free_handler (container, handler);
+
+ container->priv->handlers = g_list_delete_link (
+ container->priv->handlers, list);
+ }
+
+ list = next;
+ }
+}
diff --git a/app/core/gimpcontainer.h b/app/core/gimpcontainer.h
new file mode 100644
index 0000000..88465b5
--- /dev/null
+++ b/app/core/gimpcontainer.h
@@ -0,0 +1,150 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-1997 Spencer Kimball and Peter Mattis
+ *
+ * gimpcontainer.h
+ * Copyright (C) 2001 Michael Natterer <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_CONTAINER_H__
+#define __GIMP_CONTAINER_H__
+
+
+#include "gimpobject.h"
+
+
+#define GIMP_TYPE_CONTAINER (gimp_container_get_type ())
+#define GIMP_CONTAINER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CONTAINER, GimpContainer))
+#define GIMP_CONTAINER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CONTAINER, GimpContainerClass))
+#define GIMP_IS_CONTAINER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CONTAINER))
+#define GIMP_IS_CONTAINER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CONTAINER))
+#define GIMP_CONTAINER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_CONTAINER, GimpContainerClass))
+
+
+typedef gboolean (* GimpContainerSearchFunc) (GimpObject *object,
+ gpointer user_data);
+
+
+typedef struct _GimpContainerClass GimpContainerClass;
+typedef struct _GimpContainerPrivate GimpContainerPrivate;
+
+struct _GimpContainer
+{
+ GimpObject parent_instance;
+
+ GimpContainerPrivate *priv;
+};
+
+struct _GimpContainerClass
+{
+ GimpObjectClass parent_class;
+
+ /* signals */
+ void (* add) (GimpContainer *container,
+ GimpObject *object);
+ void (* remove) (GimpContainer *container,
+ GimpObject *object);
+ void (* reorder) (GimpContainer *container,
+ GimpObject *object,
+ gint new_index);
+ void (* freeze) (GimpContainer *container);
+ void (* thaw) (GimpContainer *container);
+
+ /* virtual functions */
+ void (* clear) (GimpContainer *container);
+ gboolean (* have) (GimpContainer *container,
+ GimpObject *object);
+ void (* foreach) (GimpContainer *container,
+ GFunc func,
+ gpointer user_data);
+ GimpObject * (* search) (GimpContainer *container,
+ GimpContainerSearchFunc func,
+ gpointer user_data);
+ gboolean (* get_unique_names) (GimpContainer *container);
+ GimpObject * (* get_child_by_name) (GimpContainer *container,
+ const gchar *name);
+ GimpObject * (* get_child_by_index) (GimpContainer *container,
+ gint index);
+ gint (* get_child_index) (GimpContainer *container,
+ GimpObject *object);
+};
+
+
+GType gimp_container_get_type (void) G_GNUC_CONST;
+
+GType gimp_container_get_children_type (GimpContainer *container);
+GimpContainerPolicy gimp_container_get_policy (GimpContainer *container);
+gint gimp_container_get_n_children (GimpContainer *container);
+
+gboolean gimp_container_add (GimpContainer *container,
+ GimpObject *object);
+gboolean gimp_container_remove (GimpContainer *container,
+ GimpObject *object);
+gboolean gimp_container_insert (GimpContainer *container,
+ GimpObject *object,
+ gint new_index);
+gboolean gimp_container_reorder (GimpContainer *container,
+ GimpObject *object,
+ gint new_index);
+
+void gimp_container_freeze (GimpContainer *container);
+void gimp_container_thaw (GimpContainer *container);
+gboolean gimp_container_frozen (GimpContainer *container);
+gint gimp_container_freeze_count (GimpContainer *container);
+
+void gimp_container_clear (GimpContainer *container);
+gboolean gimp_container_is_empty (GimpContainer *container);
+gboolean gimp_container_have (GimpContainer *container,
+ GimpObject *object);
+void gimp_container_foreach (GimpContainer *container,
+ GFunc func,
+ gpointer user_data);
+GimpObject * gimp_container_search (GimpContainer *container,
+ GimpContainerSearchFunc func,
+ gpointer user_data);
+
+gboolean gimp_container_get_unique_names (GimpContainer *container);
+
+GimpObject * gimp_container_get_child_by_name (GimpContainer *container,
+ const gchar *name);
+GimpObject * gimp_container_get_child_by_index (GimpContainer *container,
+ gint index);
+GimpObject * gimp_container_get_first_child (GimpContainer *container);
+GimpObject * gimp_container_get_last_child (GimpContainer *container);
+gint gimp_container_get_child_index (GimpContainer *container,
+ GimpObject *object);
+
+GimpObject * gimp_container_get_neighbor_of (GimpContainer *container,
+ GimpObject *object);
+
+gchar ** gimp_container_get_name_array (GimpContainer *container,
+ gint *length);
+
+GQuark gimp_container_add_handler (GimpContainer *container,
+ const gchar *signame,
+ GCallback callback,
+ gpointer callback_data);
+void gimp_container_remove_handler (GimpContainer *container,
+ GQuark id);
+void gimp_container_remove_handlers_by_func
+ (GimpContainer *container,
+ GCallback callback,
+ gpointer callback_data);
+void gimp_container_remove_handlers_by_data
+ (GimpContainer *container,
+ gpointer callback_data);
+
+
+#endif /* __GIMP_CONTAINER_H__ */
diff --git a/app/core/gimpcontext.c b/app/core/gimpcontext.c
new file mode 100644
index 0000000..3788583
--- /dev/null
+++ b/app/core/gimpcontext.c
@@ -0,0 +1,3828 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpcontext.c
+ * Copyright (C) 1999-2010 Michael Natterer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <cairo.h>
+#include <gegl.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+#include "libgimpbase/gimpbase.h"
+#include "libgimpcolor/gimpcolor.h"
+#include "libgimpconfig/gimpconfig.h"
+
+#include "core-types.h"
+
+#include "config/gimpcoreconfig.h"
+
+#include "gimp.h"
+#include "gimp-memsize.h"
+#include "gimpbrush.h"
+#include "gimpbuffer.h"
+#include "gimpcontainer.h"
+#include "gimpcontext.h"
+#include "gimpdatafactory.h"
+#include "gimpdynamics.h"
+#include "gimpimagefile.h"
+#include "gimpgradient.h"
+#include "gimpimage.h"
+#include "gimpmarshal.h"
+#include "gimpmybrush.h"
+#include "gimppaintinfo.h"
+#include "gimppalette.h"
+#include "gimppattern.h"
+#include "gimptemplate.h"
+#include "gimptoolinfo.h"
+#include "gimptoolpreset.h"
+
+#include "text/gimpfont.h"
+
+#include "gimp-intl.h"
+
+
+#define RGBA_EPSILON 1e-10
+
+typedef void (* GimpContextCopyPropFunc) (GimpContext *src,
+ GimpContext *dest);
+
+#define context_find_defined(context, prop) \
+ while (!(((context)->defined_props) & (1 << (prop))) && (context)->parent) \
+ (context) = (context)->parent
+
+#define COPY_NAME(src, dest, member) \
+ g_free (dest->member); \
+ dest->member = g_strdup (src->member)
+
+
+/* local function prototypes */
+
+static void gimp_context_config_iface_init (GimpConfigInterface *iface);
+
+static void gimp_context_constructed (GObject *object);
+static void gimp_context_dispose (GObject *object);
+static void gimp_context_finalize (GObject *object);
+static void gimp_context_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_context_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+static gint64 gimp_context_get_memsize (GimpObject *object,
+ gint64 *gui_size);
+
+static gboolean gimp_context_serialize (GimpConfig *config,
+ GimpConfigWriter *writer,
+ gpointer data);
+static gboolean gimp_context_deserialize (GimpConfig *config,
+ GScanner *scanner,
+ gint nest_level,
+ gpointer data);
+static gboolean gimp_context_serialize_property (GimpConfig *config,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec,
+ GimpConfigWriter *writer);
+static gboolean gimp_context_deserialize_property (GimpConfig *config,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec,
+ GScanner *scanner,
+ GTokenType *expected);
+static GimpConfig * gimp_context_duplicate (GimpConfig *config);
+static gboolean gimp_context_copy (GimpConfig *src,
+ GimpConfig *dest,
+ GParamFlags flags);
+
+/* image */
+static void gimp_context_image_removed (GimpContainer *container,
+ GimpImage *image,
+ GimpContext *context);
+static void gimp_context_real_set_image (GimpContext *context,
+ GimpImage *image);
+
+/* display */
+static void gimp_context_display_removed (GimpContainer *container,
+ gpointer display,
+ GimpContext *context);
+static void gimp_context_real_set_display (GimpContext *context,
+ gpointer display);
+
+/* tool */
+static void gimp_context_tool_dirty (GimpToolInfo *tool_info,
+ GimpContext *context);
+static void gimp_context_tool_removed (GimpContainer *container,
+ GimpToolInfo *tool_info,
+ GimpContext *context);
+static void gimp_context_tool_list_thaw (GimpContainer *container,
+ GimpContext *context);
+static void gimp_context_real_set_tool (GimpContext *context,
+ GimpToolInfo *tool_info);
+
+/* paint info */
+static void gimp_context_paint_info_dirty (GimpPaintInfo *paint_info,
+ GimpContext *context);
+static void gimp_context_paint_info_removed (GimpContainer *container,
+ GimpPaintInfo *paint_info,
+ GimpContext *context);
+static void gimp_context_paint_info_list_thaw(GimpContainer *container,
+ GimpContext *context);
+static void gimp_context_real_set_paint_info (GimpContext *context,
+ GimpPaintInfo *paint_info);
+
+/* foreground */
+static void gimp_context_real_set_foreground (GimpContext *context,
+ const GimpRGB *color);
+
+/* background */
+static void gimp_context_real_set_background (GimpContext *context,
+ const GimpRGB *color);
+
+/* opacity */
+static void gimp_context_real_set_opacity (GimpContext *context,
+ gdouble opacity);
+
+/* paint mode */
+static void gimp_context_real_set_paint_mode (GimpContext *context,
+ GimpLayerMode paint_mode);
+
+/* brush */
+static void gimp_context_brush_dirty (GimpBrush *brush,
+ GimpContext *context);
+static void gimp_context_brush_removed (GimpContainer *brush_list,
+ GimpBrush *brush,
+ GimpContext *context);
+static void gimp_context_brush_list_thaw (GimpContainer *container,
+ GimpContext *context);
+static void gimp_context_real_set_brush (GimpContext *context,
+ GimpBrush *brush);
+
+/* dynamics */
+
+static void gimp_context_dynamics_dirty (GimpDynamics *dynamics,
+ GimpContext *context);
+static void gimp_context_dynamics_removed (GimpContainer *container,
+ GimpDynamics *dynamics,
+ GimpContext *context);
+static void gimp_context_dynamics_list_thaw (GimpContainer *container,
+ GimpContext *context);
+static void gimp_context_real_set_dynamics (GimpContext *context,
+ GimpDynamics *dynamics);
+
+/* mybrush */
+static void gimp_context_mybrush_dirty (GimpMybrush *brush,
+ GimpContext *context);
+static void gimp_context_mybrush_removed (GimpContainer *brush_list,
+ GimpMybrush *brush,
+ GimpContext *context);
+static void gimp_context_mybrush_list_thaw (GimpContainer *container,
+ GimpContext *context);
+static void gimp_context_real_set_mybrush (GimpContext *context,
+ GimpMybrush *brush);
+
+/* pattern */
+static void gimp_context_pattern_dirty (GimpPattern *pattern,
+ GimpContext *context);
+static void gimp_context_pattern_removed (GimpContainer *container,
+ GimpPattern *pattern,
+ GimpContext *context);
+static void gimp_context_pattern_list_thaw (GimpContainer *container,
+ GimpContext *context);
+static void gimp_context_real_set_pattern (GimpContext *context,
+ GimpPattern *pattern);
+
+/* gradient */
+static void gimp_context_gradient_dirty (GimpGradient *gradient,
+ GimpContext *context);
+static void gimp_context_gradient_removed (GimpContainer *container,
+ GimpGradient *gradient,
+ GimpContext *context);
+static void gimp_context_gradient_list_thaw (GimpContainer *container,
+ GimpContext *context);
+static void gimp_context_real_set_gradient (GimpContext *context,
+ GimpGradient *gradient);
+
+/* palette */
+static void gimp_context_palette_dirty (GimpPalette *palette,
+ GimpContext *context);
+static void gimp_context_palette_removed (GimpContainer *container,
+ GimpPalette *palette,
+ GimpContext *context);
+static void gimp_context_palette_list_thaw (GimpContainer *container,
+ GimpContext *context);
+static void gimp_context_real_set_palette (GimpContext *context,
+ GimpPalette *palette);
+
+/* font */
+static void gimp_context_font_dirty (GimpFont *font,
+ GimpContext *context);
+static void gimp_context_font_removed (GimpContainer *container,
+ GimpFont *font,
+ GimpContext *context);
+static void gimp_context_font_list_thaw (GimpContainer *container,
+ GimpContext *context);
+static void gimp_context_real_set_font (GimpContext *context,
+ GimpFont *font);
+
+/* tool preset */
+static void gimp_context_tool_preset_dirty (GimpToolPreset *tool_preset,
+ GimpContext *context);
+static void gimp_context_tool_preset_removed (GimpContainer *container,
+ GimpToolPreset *tool_preset,
+ GimpContext *context);
+static void gimp_context_tool_preset_list_thaw (GimpContainer *container,
+ GimpContext *context);
+static void gimp_context_real_set_tool_preset (GimpContext *context,
+ GimpToolPreset *tool_preset);
+
+/* buffer */
+static void gimp_context_buffer_dirty (GimpBuffer *buffer,
+ GimpContext *context);
+static void gimp_context_buffer_removed (GimpContainer *container,
+ GimpBuffer *buffer,
+ GimpContext *context);
+static void gimp_context_buffer_list_thaw (GimpContainer *container,
+ GimpContext *context);
+static void gimp_context_real_set_buffer (GimpContext *context,
+ GimpBuffer *buffer);
+
+/* imagefile */
+static void gimp_context_imagefile_dirty (GimpImagefile *imagefile,
+ GimpContext *context);
+static void gimp_context_imagefile_removed (GimpContainer *container,
+ GimpImagefile *imagefile,
+ GimpContext *context);
+static void gimp_context_imagefile_list_thaw (GimpContainer *container,
+ GimpContext *context);
+static void gimp_context_real_set_imagefile (GimpContext *context,
+ GimpImagefile *imagefile);
+
+/* template */
+static void gimp_context_template_dirty (GimpTemplate *template,
+ GimpContext *context);
+static void gimp_context_template_removed (GimpContainer *container,
+ GimpTemplate *template,
+ GimpContext *context);
+static void gimp_context_template_list_thaw (GimpContainer *container,
+ GimpContext *context);
+static void gimp_context_real_set_template (GimpContext *context,
+ GimpTemplate *template);
+
+
+/* utilities */
+static gpointer gimp_context_find_object (GimpContext *context,
+ GimpContainer *container,
+ const gchar *object_name,
+ gpointer standard_object);
+
+
+/* properties & signals */
+
+enum
+{
+ GIMP_CONTEXT_PROP_0,
+ GIMP_CONTEXT_PROP_GIMP
+
+ /* remaining values are in core-enums.h (GimpContextPropType) */
+};
+
+enum
+{
+ DUMMY_0,
+ DUMMY_1,
+ IMAGE_CHANGED,
+ DISPLAY_CHANGED,
+ TOOL_CHANGED,
+ PAINT_INFO_CHANGED,
+ FOREGROUND_CHANGED,
+ BACKGROUND_CHANGED,
+ OPACITY_CHANGED,
+ PAINT_MODE_CHANGED,
+ BRUSH_CHANGED,
+ DYNAMICS_CHANGED,
+ MYBRUSH_CHANGED,
+ PATTERN_CHANGED,
+ GRADIENT_CHANGED,
+ PALETTE_CHANGED,
+ FONT_CHANGED,
+ TOOL_PRESET_CHANGED,
+ BUFFER_CHANGED,
+ IMAGEFILE_CHANGED,
+ TEMPLATE_CHANGED,
+ PROP_NAME_CHANGED,
+ LAST_SIGNAL
+};
+
+static const gchar * const gimp_context_prop_names[] =
+{
+ NULL, /* PROP_0 */
+ "gimp",
+ "image",
+ "display",
+ "tool",
+ "paint-info",
+ "foreground",
+ "background",
+ "opacity",
+ "paint-mode",
+ "brush",
+ "dynamics",
+ "mybrush",
+ "pattern",
+ "gradient",
+ "palette",
+ "font",
+ "tool-preset",
+ "buffer",
+ "imagefile",
+ "template"
+};
+
+static GType gimp_context_prop_types[] =
+{
+ G_TYPE_NONE, /* PROP_0 */
+ G_TYPE_NONE, /* PROP_GIMP */
+ 0,
+ G_TYPE_NONE,
+ 0,
+ 0,
+ G_TYPE_NONE,
+ G_TYPE_NONE,
+ G_TYPE_NONE,
+ G_TYPE_NONE,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0
+};
+
+
+G_DEFINE_TYPE_WITH_CODE (GimpContext, gimp_context, GIMP_TYPE_VIEWABLE,
+ G_IMPLEMENT_INTERFACE (GIMP_TYPE_CONFIG,
+ gimp_context_config_iface_init))
+
+#define parent_class gimp_context_parent_class
+
+static GimpConfigInterface *parent_config_iface = NULL;
+
+static guint gimp_context_signals[LAST_SIGNAL] = { 0 };
+
+
+static void
+gimp_context_class_init (GimpContextClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpObjectClass *gimp_object_class = GIMP_OBJECT_CLASS (klass);
+ GimpRGB black;
+ GimpRGB white;
+
+ gimp_rgba_set (&black, 0.0, 0.0, 0.0, GIMP_OPACITY_OPAQUE);
+ gimp_rgba_set (&white, 1.0, 1.0, 1.0, GIMP_OPACITY_OPAQUE);
+
+ gimp_context_signals[IMAGE_CHANGED] =
+ g_signal_new ("image-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpContextClass, image_changed),
+ NULL, NULL,
+ gimp_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1,
+ GIMP_TYPE_IMAGE);
+
+ gimp_context_signals[DISPLAY_CHANGED] =
+ g_signal_new ("display-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpContextClass, display_changed),
+ NULL, NULL,
+ gimp_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1,
+ GIMP_TYPE_OBJECT);
+
+ gimp_context_signals[TOOL_CHANGED] =
+ g_signal_new ("tool-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpContextClass, tool_changed),
+ NULL, NULL,
+ gimp_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1,
+ GIMP_TYPE_TOOL_INFO);
+
+ gimp_context_signals[PAINT_INFO_CHANGED] =
+ g_signal_new ("paint-info-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpContextClass, paint_info_changed),
+ NULL, NULL,
+ gimp_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1,
+ GIMP_TYPE_PAINT_INFO);
+
+ gimp_context_signals[FOREGROUND_CHANGED] =
+ g_signal_new ("foreground-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpContextClass, foreground_changed),
+ NULL, NULL,
+ gimp_marshal_VOID__BOXED,
+ G_TYPE_NONE, 1,
+ GIMP_TYPE_RGB | G_SIGNAL_TYPE_STATIC_SCOPE);
+
+ gimp_context_signals[BACKGROUND_CHANGED] =
+ g_signal_new ("background-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpContextClass, background_changed),
+ NULL, NULL,
+ gimp_marshal_VOID__BOXED,
+ G_TYPE_NONE, 1,
+ GIMP_TYPE_RGB | G_SIGNAL_TYPE_STATIC_SCOPE);
+
+ gimp_context_signals[OPACITY_CHANGED] =
+ g_signal_new ("opacity-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpContextClass, opacity_changed),
+ NULL, NULL,
+ gimp_marshal_VOID__DOUBLE,
+ G_TYPE_NONE, 1,
+ G_TYPE_DOUBLE);
+
+ gimp_context_signals[PAINT_MODE_CHANGED] =
+ g_signal_new ("paint-mode-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpContextClass, paint_mode_changed),
+ NULL, NULL,
+ gimp_marshal_VOID__ENUM,
+ G_TYPE_NONE, 1,
+ GIMP_TYPE_LAYER_MODE);
+
+ gimp_context_signals[BRUSH_CHANGED] =
+ g_signal_new ("brush-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpContextClass, brush_changed),
+ NULL, NULL,
+ gimp_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1,
+ GIMP_TYPE_BRUSH);
+
+ gimp_context_signals[DYNAMICS_CHANGED] =
+ g_signal_new ("dynamics-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpContextClass, dynamics_changed),
+ NULL, NULL,
+ gimp_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1,
+ GIMP_TYPE_DYNAMICS);
+
+ gimp_context_signals[MYBRUSH_CHANGED] =
+ g_signal_new ("mybrush-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpContextClass, mybrush_changed),
+ NULL, NULL,
+ gimp_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1,
+ GIMP_TYPE_MYBRUSH);
+
+ gimp_context_signals[PATTERN_CHANGED] =
+ g_signal_new ("pattern-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpContextClass, pattern_changed),
+ NULL, NULL,
+ gimp_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1,
+ GIMP_TYPE_PATTERN);
+
+ gimp_context_signals[GRADIENT_CHANGED] =
+ g_signal_new ("gradient-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpContextClass, gradient_changed),
+ NULL, NULL,
+ gimp_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1,
+ GIMP_TYPE_GRADIENT);
+
+ gimp_context_signals[PALETTE_CHANGED] =
+ g_signal_new ("palette-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpContextClass, palette_changed),
+ NULL, NULL,
+ gimp_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1,
+ GIMP_TYPE_PALETTE);
+
+ gimp_context_signals[FONT_CHANGED] =
+ g_signal_new ("font-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpContextClass, font_changed),
+ NULL, NULL,
+ gimp_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1,
+ GIMP_TYPE_FONT);
+
+ gimp_context_signals[TOOL_PRESET_CHANGED] =
+ g_signal_new ("tool-preset-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpContextClass, tool_preset_changed),
+ NULL, NULL,
+ gimp_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1,
+ GIMP_TYPE_TOOL_PRESET);
+
+ gimp_context_signals[BUFFER_CHANGED] =
+ g_signal_new ("buffer-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpContextClass, buffer_changed),
+ NULL, NULL,
+ gimp_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1,
+ GIMP_TYPE_BUFFER);
+
+ gimp_context_signals[IMAGEFILE_CHANGED] =
+ g_signal_new ("imagefile-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpContextClass, imagefile_changed),
+ NULL, NULL,
+ gimp_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1,
+ GIMP_TYPE_IMAGEFILE);
+
+ gimp_context_signals[TEMPLATE_CHANGED] =
+ g_signal_new ("template-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpContextClass, template_changed),
+ NULL, NULL,
+ gimp_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1,
+ GIMP_TYPE_TEMPLATE);
+
+ gimp_context_signals[PROP_NAME_CHANGED] =
+ g_signal_new ("prop-name-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpContextClass, prop_name_changed),
+ NULL, NULL,
+ gimp_marshal_VOID__INT,
+ G_TYPE_NONE, 1,
+ G_TYPE_INT);
+
+ object_class->constructed = gimp_context_constructed;
+ object_class->set_property = gimp_context_set_property;
+ object_class->get_property = gimp_context_get_property;
+ object_class->dispose = gimp_context_dispose;
+ object_class->finalize = gimp_context_finalize;
+
+ gimp_object_class->get_memsize = gimp_context_get_memsize;
+
+ klass->image_changed = NULL;
+ klass->display_changed = NULL;
+ klass->tool_changed = NULL;
+ klass->paint_info_changed = NULL;
+ klass->foreground_changed = NULL;
+ klass->background_changed = NULL;
+ klass->opacity_changed = NULL;
+ klass->paint_mode_changed = NULL;
+ klass->brush_changed = NULL;
+ klass->dynamics_changed = NULL;
+ klass->mybrush_changed = NULL;
+ klass->pattern_changed = NULL;
+ klass->gradient_changed = NULL;
+ klass->palette_changed = NULL;
+ klass->font_changed = NULL;
+ klass->tool_preset_changed = NULL;
+ klass->buffer_changed = NULL;
+ klass->imagefile_changed = NULL;
+ klass->template_changed = NULL;
+ klass->prop_name_changed = NULL;
+
+ gimp_context_prop_types[GIMP_CONTEXT_PROP_IMAGE] = GIMP_TYPE_IMAGE;
+ gimp_context_prop_types[GIMP_CONTEXT_PROP_TOOL] = GIMP_TYPE_TOOL_INFO;
+ gimp_context_prop_types[GIMP_CONTEXT_PROP_PAINT_INFO] = GIMP_TYPE_PAINT_INFO;
+ gimp_context_prop_types[GIMP_CONTEXT_PROP_BRUSH] = GIMP_TYPE_BRUSH;
+ gimp_context_prop_types[GIMP_CONTEXT_PROP_DYNAMICS] = GIMP_TYPE_DYNAMICS;
+ gimp_context_prop_types[GIMP_CONTEXT_PROP_MYBRUSH] = GIMP_TYPE_MYBRUSH;
+ gimp_context_prop_types[GIMP_CONTEXT_PROP_PATTERN] = GIMP_TYPE_PATTERN;
+ gimp_context_prop_types[GIMP_CONTEXT_PROP_GRADIENT] = GIMP_TYPE_GRADIENT;
+ gimp_context_prop_types[GIMP_CONTEXT_PROP_PALETTE] = GIMP_TYPE_PALETTE;
+ gimp_context_prop_types[GIMP_CONTEXT_PROP_FONT] = GIMP_TYPE_FONT;
+ gimp_context_prop_types[GIMP_CONTEXT_PROP_TOOL_PRESET] = GIMP_TYPE_TOOL_PRESET;
+ gimp_context_prop_types[GIMP_CONTEXT_PROP_BUFFER] = GIMP_TYPE_BUFFER;
+ gimp_context_prop_types[GIMP_CONTEXT_PROP_IMAGEFILE] = GIMP_TYPE_IMAGEFILE;
+ gimp_context_prop_types[GIMP_CONTEXT_PROP_TEMPLATE] = GIMP_TYPE_TEMPLATE;
+
+ g_object_class_install_property (object_class, GIMP_CONTEXT_PROP_GIMP,
+ g_param_spec_object ("gimp",
+ NULL, NULL,
+ GIMP_TYPE_GIMP,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+
+ g_object_class_install_property (object_class, GIMP_CONTEXT_PROP_IMAGE,
+ g_param_spec_object (gimp_context_prop_names[GIMP_CONTEXT_PROP_IMAGE],
+ NULL, NULL,
+ GIMP_TYPE_IMAGE,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, GIMP_CONTEXT_PROP_DISPLAY,
+ g_param_spec_object (gimp_context_prop_names[GIMP_CONTEXT_PROP_DISPLAY],
+ NULL, NULL,
+ GIMP_TYPE_OBJECT,
+ GIMP_PARAM_READWRITE));
+
+ GIMP_CONFIG_PROP_OBJECT (object_class, GIMP_CONTEXT_PROP_TOOL,
+ gimp_context_prop_names[GIMP_CONTEXT_PROP_TOOL],
+ NULL, NULL,
+ GIMP_TYPE_TOOL_INFO,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_OBJECT (object_class, GIMP_CONTEXT_PROP_PAINT_INFO,
+ gimp_context_prop_names[GIMP_CONTEXT_PROP_PAINT_INFO],
+ NULL, NULL,
+ GIMP_TYPE_PAINT_INFO,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_RGB (object_class, GIMP_CONTEXT_PROP_FOREGROUND,
+ gimp_context_prop_names[GIMP_CONTEXT_PROP_FOREGROUND],
+ _("Foreground"),
+ _("Foreground color"),
+ FALSE, &black,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_RGB (object_class, GIMP_CONTEXT_PROP_BACKGROUND,
+ gimp_context_prop_names[GIMP_CONTEXT_PROP_BACKGROUND],
+ _("Background"),
+ _("Background color"),
+ FALSE, &white,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_DOUBLE (object_class, GIMP_CONTEXT_PROP_OPACITY,
+ gimp_context_prop_names[GIMP_CONTEXT_PROP_OPACITY],
+ _("Opacity"),
+ _("Opacity"),
+ GIMP_OPACITY_TRANSPARENT,
+ GIMP_OPACITY_OPAQUE,
+ GIMP_OPACITY_OPAQUE,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_ENUM (object_class, GIMP_CONTEXT_PROP_PAINT_MODE,
+ gimp_context_prop_names[GIMP_CONTEXT_PROP_PAINT_MODE],
+ _("Paint Mode"),
+ _("Paint Mode"),
+ GIMP_TYPE_LAYER_MODE,
+ GIMP_LAYER_MODE_NORMAL,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_OBJECT (object_class, GIMP_CONTEXT_PROP_BRUSH,
+ gimp_context_prop_names[GIMP_CONTEXT_PROP_BRUSH],
+ _("Brush"),
+ _("Brush"),
+ GIMP_TYPE_BRUSH,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_OBJECT (object_class, GIMP_CONTEXT_PROP_DYNAMICS,
+ gimp_context_prop_names[GIMP_CONTEXT_PROP_DYNAMICS],
+ _("Dynamics"),
+ _("Paint dynamics"),
+ GIMP_TYPE_DYNAMICS,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_OBJECT (object_class, GIMP_CONTEXT_PROP_MYBRUSH,
+ gimp_context_prop_names[GIMP_CONTEXT_PROP_MYBRUSH],
+ _("MyPaint Brush"),
+ _("MyPaint Brush"),
+ GIMP_TYPE_MYBRUSH,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_OBJECT (object_class, GIMP_CONTEXT_PROP_PATTERN,
+ gimp_context_prop_names[GIMP_CONTEXT_PROP_PATTERN],
+ _("Pattern"),
+ _("Pattern"),
+ GIMP_TYPE_PATTERN,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_OBJECT (object_class, GIMP_CONTEXT_PROP_GRADIENT,
+ gimp_context_prop_names[GIMP_CONTEXT_PROP_GRADIENT],
+ _("Gradient"),
+ _("Gradient"),
+ GIMP_TYPE_GRADIENT,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_OBJECT (object_class, GIMP_CONTEXT_PROP_PALETTE,
+ gimp_context_prop_names[GIMP_CONTEXT_PROP_PALETTE],
+ _("Palette"),
+ _("Palette"),
+ GIMP_TYPE_PALETTE,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_OBJECT (object_class, GIMP_CONTEXT_PROP_FONT,
+ gimp_context_prop_names[GIMP_CONTEXT_PROP_FONT],
+ _("Font"),
+ _("Font"),
+ GIMP_TYPE_FONT,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_OBJECT (object_class, GIMP_CONTEXT_PROP_TOOL_PRESET,
+ gimp_context_prop_names[GIMP_CONTEXT_PROP_TOOL_PRESET],
+ _("Tool Preset"),
+ _("Tool Preset"),
+ GIMP_TYPE_TOOL_PRESET,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_property (object_class, GIMP_CONTEXT_PROP_BUFFER,
+ g_param_spec_object (gimp_context_prop_names[GIMP_CONTEXT_PROP_BUFFER],
+ NULL, NULL,
+ GIMP_TYPE_BUFFER,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, GIMP_CONTEXT_PROP_IMAGEFILE,
+ g_param_spec_object (gimp_context_prop_names[GIMP_CONTEXT_PROP_IMAGEFILE],
+ NULL, NULL,
+ GIMP_TYPE_IMAGEFILE,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, GIMP_CONTEXT_PROP_TEMPLATE,
+ g_param_spec_object (gimp_context_prop_names[GIMP_CONTEXT_PROP_TEMPLATE],
+ NULL, NULL,
+ GIMP_TYPE_TEMPLATE,
+ GIMP_PARAM_READWRITE));
+}
+
+static void
+gimp_context_init (GimpContext *context)
+{
+ context->gimp = NULL;
+
+ context->parent = NULL;
+
+ context->defined_props = GIMP_CONTEXT_PROP_MASK_ALL;
+ context->serialize_props = GIMP_CONTEXT_PROP_MASK_ALL;
+
+ context->image = NULL;
+ context->display = NULL;
+
+ context->tool_info = NULL;
+ context->tool_name = NULL;
+
+ context->paint_info = NULL;
+ context->paint_name = NULL;
+
+ context->brush = NULL;
+ context->brush_name = NULL;
+
+ context->dynamics = NULL;
+ context->dynamics_name = NULL;
+
+ context->mybrush = NULL;
+ context->mybrush_name = NULL;
+
+ context->pattern = NULL;
+ context->pattern_name = NULL;
+
+ context->gradient = NULL;
+ context->gradient_name = NULL;
+
+ context->palette = NULL;
+ context->palette_name = NULL;
+
+ context->font = NULL;
+ context->font_name = NULL;
+
+ context->tool_preset = NULL;
+ context->tool_preset_name = NULL;
+
+ context->buffer = NULL;
+ context->buffer_name = NULL;
+
+ context->imagefile = NULL;
+ context->imagefile_name = NULL;
+
+ context->template = NULL;
+ context->template_name = NULL;
+}
+
+static void
+gimp_context_config_iface_init (GimpConfigInterface *iface)
+{
+ parent_config_iface = g_type_interface_peek_parent (iface);
+
+ if (! parent_config_iface)
+ parent_config_iface = g_type_default_interface_peek (GIMP_TYPE_CONFIG);
+
+ iface->serialize = gimp_context_serialize;
+ iface->deserialize = gimp_context_deserialize;
+ iface->serialize_property = gimp_context_serialize_property;
+ iface->deserialize_property = gimp_context_deserialize_property;
+ iface->duplicate = gimp_context_duplicate;
+ iface->copy = gimp_context_copy;
+}
+
+static void
+gimp_context_constructed (GObject *object)
+{
+ Gimp *gimp;
+ GimpContainer *container;
+
+ G_OBJECT_CLASS (parent_class)->constructed (object);
+
+ gimp = GIMP_CONTEXT (object)->gimp;
+
+ gimp_assert (GIMP_IS_GIMP (gimp));
+
+ gimp->context_list = g_list_prepend (gimp->context_list, object);
+
+ g_signal_connect_object (gimp->images, "remove",
+ G_CALLBACK (gimp_context_image_removed),
+ object, 0);
+ g_signal_connect_object (gimp->displays, "remove",
+ G_CALLBACK (gimp_context_display_removed),
+ object, 0);
+
+ g_signal_connect_object (gimp->tool_info_list, "remove",
+ G_CALLBACK (gimp_context_tool_removed),
+ object, 0);
+ g_signal_connect_object (gimp->tool_info_list, "thaw",
+ G_CALLBACK (gimp_context_tool_list_thaw),
+ object, 0);
+
+ g_signal_connect_object (gimp->paint_info_list, "remove",
+ G_CALLBACK (gimp_context_paint_info_removed),
+ object, 0);
+ g_signal_connect_object (gimp->paint_info_list, "thaw",
+ G_CALLBACK (gimp_context_paint_info_list_thaw),
+ object, 0);
+
+ container = gimp_data_factory_get_container (gimp->brush_factory);
+ g_signal_connect_object (container, "remove",
+ G_CALLBACK (gimp_context_brush_removed),
+ object, 0);
+ g_signal_connect_object (container, "thaw",
+ G_CALLBACK (gimp_context_brush_list_thaw),
+ object, 0);
+
+ container = gimp_data_factory_get_container (gimp->dynamics_factory);
+ g_signal_connect_object (container, "remove",
+ G_CALLBACK (gimp_context_dynamics_removed),
+ object, 0);
+ g_signal_connect_object (container, "thaw",
+ G_CALLBACK (gimp_context_dynamics_list_thaw),
+ object, 0);
+
+ container = gimp_data_factory_get_container (gimp->mybrush_factory);
+ g_signal_connect_object (container, "remove",
+ G_CALLBACK (gimp_context_mybrush_removed),
+ object, 0);
+ g_signal_connect_object (container, "thaw",
+ G_CALLBACK (gimp_context_mybrush_list_thaw),
+ object, 0);
+
+ container = gimp_data_factory_get_container (gimp->pattern_factory);
+ g_signal_connect_object (container, "remove",
+ G_CALLBACK (gimp_context_pattern_removed),
+ object, 0);
+ g_signal_connect_object (container, "thaw",
+ G_CALLBACK (gimp_context_pattern_list_thaw),
+ object, 0);
+
+ container = gimp_data_factory_get_container (gimp->gradient_factory);
+ g_signal_connect_object (container, "remove",
+ G_CALLBACK (gimp_context_gradient_removed),
+ object, 0);
+ g_signal_connect_object (container, "thaw",
+ G_CALLBACK (gimp_context_gradient_list_thaw),
+ object, 0);
+
+ container = gimp_data_factory_get_container (gimp->palette_factory);
+ g_signal_connect_object (container, "remove",
+ G_CALLBACK (gimp_context_palette_removed),
+ object, 0);
+ g_signal_connect_object (container, "thaw",
+ G_CALLBACK (gimp_context_palette_list_thaw),
+ object, 0);
+
+ container = gimp_data_factory_get_container (gimp->font_factory);
+ g_signal_connect_object (container, "remove",
+ G_CALLBACK (gimp_context_font_removed),
+ object, 0);
+ g_signal_connect_object (container, "thaw",
+ G_CALLBACK (gimp_context_font_list_thaw),
+ object, 0);
+
+ container = gimp_data_factory_get_container (gimp->tool_preset_factory);
+ g_signal_connect_object (container, "remove",
+ G_CALLBACK (gimp_context_tool_preset_removed),
+ object, 0);
+ g_signal_connect_object (container, "thaw",
+ G_CALLBACK (gimp_context_tool_preset_list_thaw),
+ object, 0);
+
+ g_signal_connect_object (gimp->named_buffers, "remove",
+ G_CALLBACK (gimp_context_buffer_removed),
+ object, 0);
+ g_signal_connect_object (gimp->named_buffers, "thaw",
+ G_CALLBACK (gimp_context_buffer_list_thaw),
+ object, 0);
+
+ g_signal_connect_object (gimp->documents, "remove",
+ G_CALLBACK (gimp_context_imagefile_removed),
+ object, 0);
+ g_signal_connect_object (gimp->documents, "thaw",
+ G_CALLBACK (gimp_context_imagefile_list_thaw),
+ object, 0);
+
+ g_signal_connect_object (gimp->templates, "remove",
+ G_CALLBACK (gimp_context_template_removed),
+ object, 0);
+ g_signal_connect_object (gimp->templates, "thaw",
+ G_CALLBACK (gimp_context_template_list_thaw),
+ object, 0);
+
+ gimp_context_set_paint_info (GIMP_CONTEXT (object),
+ gimp_paint_info_get_standard (gimp));
+}
+
+static void
+gimp_context_dispose (GObject *object)
+{
+ GimpContext *context = GIMP_CONTEXT (object);
+
+ gimp_context_set_parent (context, NULL);
+
+ if (context->gimp)
+ {
+ context->gimp->context_list = g_list_remove (context->gimp->context_list,
+ context);
+ context->gimp = NULL;
+ }
+
+ g_clear_object (&context->tool_info);
+ g_clear_object (&context->paint_info);
+ g_clear_object (&context->brush);
+ g_clear_object (&context->dynamics);
+ g_clear_object (&context->mybrush);
+ g_clear_object (&context->pattern);
+ g_clear_object (&context->gradient);
+ g_clear_object (&context->palette);
+ g_clear_object (&context->font);
+ g_clear_object (&context->tool_preset);
+ g_clear_object (&context->buffer);
+ g_clear_object (&context->imagefile);
+ g_clear_object (&context->template);
+
+ G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+gimp_context_finalize (GObject *object)
+{
+ GimpContext *context = GIMP_CONTEXT (object);
+
+ context->parent = NULL;
+ context->image = NULL;
+ context->display = NULL;
+
+ g_clear_pointer (&context->tool_name, g_free);
+ g_clear_pointer (&context->paint_name, g_free);
+ g_clear_pointer (&context->brush_name, g_free);
+ g_clear_pointer (&context->dynamics_name, g_free);
+ g_clear_pointer (&context->mybrush_name, g_free);
+ g_clear_pointer (&context->pattern_name, g_free);
+ g_clear_pointer (&context->gradient_name, g_free);
+ g_clear_pointer (&context->palette_name, g_free);
+ g_clear_pointer (&context->font_name, g_free);
+ g_clear_pointer (&context->tool_preset_name, g_free);
+ g_clear_pointer (&context->buffer_name, g_free);
+ g_clear_pointer (&context->imagefile_name, g_free);
+ g_clear_pointer (&context->template_name, g_free);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gimp_context_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpContext *context = GIMP_CONTEXT (object);
+
+ switch (property_id)
+ {
+ case GIMP_CONTEXT_PROP_GIMP:
+ context->gimp = g_value_get_object (value);
+ break;
+ case GIMP_CONTEXT_PROP_IMAGE:
+ gimp_context_set_image (context, g_value_get_object (value));
+ break;
+ case GIMP_CONTEXT_PROP_DISPLAY:
+ gimp_context_set_display (context, g_value_get_object (value));
+ break;
+ case GIMP_CONTEXT_PROP_TOOL:
+ gimp_context_set_tool (context, g_value_get_object (value));
+ break;
+ case GIMP_CONTEXT_PROP_PAINT_INFO:
+ gimp_context_set_paint_info (context, g_value_get_object (value));
+ break;
+ case GIMP_CONTEXT_PROP_FOREGROUND:
+ gimp_context_set_foreground (context, g_value_get_boxed (value));
+ break;
+ case GIMP_CONTEXT_PROP_BACKGROUND:
+ gimp_context_set_background (context, g_value_get_boxed (value));
+ break;
+ case GIMP_CONTEXT_PROP_OPACITY:
+ gimp_context_set_opacity (context, g_value_get_double (value));
+ break;
+ case GIMP_CONTEXT_PROP_PAINT_MODE:
+ gimp_context_set_paint_mode (context, g_value_get_enum (value));
+ break;
+ case GIMP_CONTEXT_PROP_BRUSH:
+ gimp_context_set_brush (context, g_value_get_object (value));
+ break;
+ case GIMP_CONTEXT_PROP_DYNAMICS:
+ gimp_context_set_dynamics (context, g_value_get_object (value));
+ break;
+ case GIMP_CONTEXT_PROP_MYBRUSH:
+ gimp_context_set_mybrush (context, g_value_get_object (value));
+ break;
+ case GIMP_CONTEXT_PROP_PATTERN:
+ gimp_context_set_pattern (context, g_value_get_object (value));
+ break;
+ case GIMP_CONTEXT_PROP_GRADIENT:
+ gimp_context_set_gradient (context, g_value_get_object (value));
+ break;
+ case GIMP_CONTEXT_PROP_PALETTE:
+ gimp_context_set_palette (context, g_value_get_object (value));
+ break;
+ case GIMP_CONTEXT_PROP_FONT:
+ gimp_context_set_font (context, g_value_get_object (value));
+ break;
+ case GIMP_CONTEXT_PROP_TOOL_PRESET:
+ gimp_context_set_tool_preset (context, g_value_get_object (value));
+ break;
+ case GIMP_CONTEXT_PROP_BUFFER:
+ gimp_context_set_buffer (context, g_value_get_object (value));
+ break;
+ case GIMP_CONTEXT_PROP_IMAGEFILE:
+ gimp_context_set_imagefile (context, g_value_get_object (value));
+ break;
+ case GIMP_CONTEXT_PROP_TEMPLATE:
+ gimp_context_set_template (context, g_value_get_object (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_context_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpContext *context = GIMP_CONTEXT (object);
+
+ switch (property_id)
+ {
+ case GIMP_CONTEXT_PROP_GIMP:
+ g_value_set_object (value, context->gimp);
+ break;
+ case GIMP_CONTEXT_PROP_IMAGE:
+ g_value_set_object (value, gimp_context_get_image (context));
+ break;
+ case GIMP_CONTEXT_PROP_DISPLAY:
+ g_value_set_object (value, gimp_context_get_display (context));
+ break;
+ case GIMP_CONTEXT_PROP_TOOL:
+ g_value_set_object (value, gimp_context_get_tool (context));
+ break;
+ case GIMP_CONTEXT_PROP_PAINT_INFO:
+ g_value_set_object (value, gimp_context_get_paint_info (context));
+ break;
+ case GIMP_CONTEXT_PROP_FOREGROUND:
+ {
+ GimpRGB color;
+
+ gimp_context_get_foreground (context, &color);
+ g_value_set_boxed (value, &color);
+ }
+ break;
+ case GIMP_CONTEXT_PROP_BACKGROUND:
+ {
+ GimpRGB color;
+
+ gimp_context_get_background (context, &color);
+ g_value_set_boxed (value, &color);
+ }
+ break;
+ case GIMP_CONTEXT_PROP_OPACITY:
+ g_value_set_double (value, gimp_context_get_opacity (context));
+ break;
+ case GIMP_CONTEXT_PROP_PAINT_MODE:
+ g_value_set_enum (value, gimp_context_get_paint_mode (context));
+ break;
+ case GIMP_CONTEXT_PROP_BRUSH:
+ g_value_set_object (value, gimp_context_get_brush (context));
+ break;
+ case GIMP_CONTEXT_PROP_DYNAMICS:
+ g_value_set_object (value, gimp_context_get_dynamics (context));
+ break;
+ case GIMP_CONTEXT_PROP_MYBRUSH:
+ g_value_set_object (value, gimp_context_get_mybrush (context));
+ break;
+ case GIMP_CONTEXT_PROP_PATTERN:
+ g_value_set_object (value, gimp_context_get_pattern (context));
+ break;
+ case GIMP_CONTEXT_PROP_GRADIENT:
+ g_value_set_object (value, gimp_context_get_gradient (context));
+ break;
+ case GIMP_CONTEXT_PROP_PALETTE:
+ g_value_set_object (value, gimp_context_get_palette (context));
+ break;
+ case GIMP_CONTEXT_PROP_FONT:
+ g_value_set_object (value, gimp_context_get_font (context));
+ break;
+ case GIMP_CONTEXT_PROP_TOOL_PRESET:
+ g_value_set_object (value, gimp_context_get_tool_preset (context));
+ break;
+ case GIMP_CONTEXT_PROP_BUFFER:
+ g_value_set_object (value, gimp_context_get_buffer (context));
+ break;
+ case GIMP_CONTEXT_PROP_IMAGEFILE:
+ g_value_set_object (value, gimp_context_get_imagefile (context));
+ break;
+ case GIMP_CONTEXT_PROP_TEMPLATE:
+ g_value_set_object (value, gimp_context_get_template (context));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static gint64
+gimp_context_get_memsize (GimpObject *object,
+ gint64 *gui_size)
+{
+ GimpContext *context = GIMP_CONTEXT (object);
+ gint64 memsize = 0;
+
+ memsize += gimp_string_get_memsize (context->tool_name);
+ memsize += gimp_string_get_memsize (context->paint_name);
+ memsize += gimp_string_get_memsize (context->brush_name);
+ memsize += gimp_string_get_memsize (context->dynamics_name);
+ memsize += gimp_string_get_memsize (context->mybrush_name);
+ memsize += gimp_string_get_memsize (context->pattern_name);
+ memsize += gimp_string_get_memsize (context->palette_name);
+ memsize += gimp_string_get_memsize (context->font_name);
+ memsize += gimp_string_get_memsize (context->tool_preset_name);
+ memsize += gimp_string_get_memsize (context->buffer_name);
+ memsize += gimp_string_get_memsize (context->imagefile_name);
+ memsize += gimp_string_get_memsize (context->template_name);
+
+ return memsize + GIMP_OBJECT_CLASS (parent_class)->get_memsize (object,
+ gui_size);
+}
+
+static gboolean
+gimp_context_serialize (GimpConfig *config,
+ GimpConfigWriter *writer,
+ gpointer data)
+{
+ return gimp_config_serialize_changed_properties (config, writer);
+}
+
+static gboolean
+gimp_context_deserialize (GimpConfig *config,
+ GScanner *scanner,
+ gint nest_level,
+ gpointer data)
+{
+ GimpContext *context = GIMP_CONTEXT (config);
+ GimpLayerMode old_paint_mode = context->paint_mode;
+ gboolean success;
+
+ success = gimp_config_deserialize_properties (config, scanner, nest_level);
+
+ if (context->paint_mode != old_paint_mode)
+ {
+ if (context->paint_mode == GIMP_LAYER_MODE_OVERLAY_LEGACY)
+ g_object_set (context,
+ "paint-mode", GIMP_LAYER_MODE_SOFTLIGHT_LEGACY,
+ NULL);
+ }
+
+ return success;
+}
+
+static gboolean
+gimp_context_serialize_property (GimpConfig *config,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec,
+ GimpConfigWriter *writer)
+{
+ GimpContext *context = GIMP_CONTEXT (config);
+ GimpObject *serialize_obj;
+
+ /* serialize nothing if the property is not in serialize_props */
+ if (! ((1 << property_id) & context->serialize_props))
+ return TRUE;
+
+ switch (property_id)
+ {
+ case GIMP_CONTEXT_PROP_TOOL:
+ case GIMP_CONTEXT_PROP_PAINT_INFO:
+ case GIMP_CONTEXT_PROP_BRUSH:
+ case GIMP_CONTEXT_PROP_DYNAMICS:
+ case GIMP_CONTEXT_PROP_MYBRUSH:
+ case GIMP_CONTEXT_PROP_PATTERN:
+ case GIMP_CONTEXT_PROP_GRADIENT:
+ case GIMP_CONTEXT_PROP_PALETTE:
+ case GIMP_CONTEXT_PROP_FONT:
+ case GIMP_CONTEXT_PROP_TOOL_PRESET:
+ serialize_obj = g_value_get_object (value);
+ break;
+
+ default:
+ return FALSE;
+ }
+
+ gimp_config_writer_open (writer, pspec->name);
+
+ if (serialize_obj)
+ gimp_config_writer_string (writer, gimp_object_get_name (serialize_obj));
+ else
+ gimp_config_writer_print (writer, "NULL", 4);
+
+ gimp_config_writer_close (writer);
+
+ return TRUE;
+}
+
+static gboolean
+gimp_context_deserialize_property (GimpConfig *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec,
+ GScanner *scanner,
+ GTokenType *expected)
+{
+ GimpContext *context = GIMP_CONTEXT (object);
+ GimpContainer *container;
+ gpointer standard;
+ gchar **name_loc;
+ gchar *object_name;
+
+ switch (property_id)
+ {
+ case GIMP_CONTEXT_PROP_TOOL:
+ container = context->gimp->tool_info_list;
+ standard = gimp_tool_info_get_standard (context->gimp);
+ name_loc = &context->tool_name;
+ break;
+
+ case GIMP_CONTEXT_PROP_PAINT_INFO:
+ container = context->gimp->paint_info_list;
+ standard = gimp_paint_info_get_standard (context->gimp);
+ name_loc = &context->paint_name;
+ break;
+
+ case GIMP_CONTEXT_PROP_BRUSH:
+ container = gimp_data_factory_get_container (context->gimp->brush_factory);
+ standard = gimp_brush_get_standard (context);
+ name_loc = &context->brush_name;
+ break;
+
+ case GIMP_CONTEXT_PROP_DYNAMICS:
+ container = gimp_data_factory_get_container (context->gimp->dynamics_factory);
+ standard = gimp_dynamics_get_standard (context);
+ name_loc = &context->dynamics_name;
+ break;
+
+ case GIMP_CONTEXT_PROP_MYBRUSH:
+ container = gimp_data_factory_get_container (context->gimp->mybrush_factory);
+ standard = gimp_mybrush_get_standard (context);
+ name_loc = &context->mybrush_name;
+ break;
+
+ case GIMP_CONTEXT_PROP_PATTERN:
+ container = gimp_data_factory_get_container (context->gimp->pattern_factory);
+ standard = gimp_pattern_get_standard (context);
+ name_loc = &context->pattern_name;
+ break;
+
+ case GIMP_CONTEXT_PROP_GRADIENT:
+ container = gimp_data_factory_get_container (context->gimp->gradient_factory);
+ standard = gimp_gradient_get_standard (context);
+ name_loc = &context->gradient_name;
+ break;
+
+ case GIMP_CONTEXT_PROP_PALETTE:
+ container = gimp_data_factory_get_container (context->gimp->palette_factory);
+ standard = gimp_palette_get_standard (context);
+ name_loc = &context->palette_name;
+ break;
+
+ case GIMP_CONTEXT_PROP_FONT:
+ container = gimp_data_factory_get_container (context->gimp->font_factory);
+ standard = gimp_font_get_standard ();
+ name_loc = &context->font_name;
+ break;
+
+ case GIMP_CONTEXT_PROP_TOOL_PRESET:
+ container = gimp_data_factory_get_container (context->gimp->tool_preset_factory);
+ standard = NULL;
+ name_loc = &context->tool_preset_name;
+ break;
+
+ default:
+ return FALSE;
+ }
+
+ if (gimp_scanner_parse_identifier (scanner, "NULL"))
+ {
+ g_value_set_object (value, NULL);
+ }
+ else if (gimp_scanner_parse_string (scanner, &object_name))
+ {
+ GimpObject *deserialize_obj;
+
+ if (! object_name)
+ object_name = g_strdup ("");
+
+ deserialize_obj = gimp_container_get_child_by_name (container,
+ object_name);
+
+ if (! deserialize_obj)
+ {
+ g_value_set_object (value, standard);
+
+ g_free (*name_loc);
+ *name_loc = g_strdup (object_name);
+ }
+ else
+ {
+ g_value_set_object (value, deserialize_obj);
+ }
+
+ g_free (object_name);
+ }
+ else
+ {
+ *expected = G_TOKEN_STRING;
+ }
+
+ return TRUE;
+}
+
+static GimpConfig *
+gimp_context_duplicate (GimpConfig *config)
+{
+ GimpContext *context = GIMP_CONTEXT (config);
+ GimpContext *new;
+
+ new = GIMP_CONTEXT (parent_config_iface->duplicate (config));
+
+ COPY_NAME (context, new, tool_name);
+ COPY_NAME (context, new, paint_name);
+ COPY_NAME (context, new, brush_name);
+ COPY_NAME (context, new, dynamics_name);
+ COPY_NAME (context, new, mybrush_name);
+ COPY_NAME (context, new, pattern_name);
+ COPY_NAME (context, new, gradient_name);
+ COPY_NAME (context, new, palette_name);
+ COPY_NAME (context, new, font_name);
+ COPY_NAME (context, new, tool_preset_name);
+ COPY_NAME (context, new, buffer_name);
+ COPY_NAME (context, new, imagefile_name);
+ COPY_NAME (context, new, template_name);
+
+ return GIMP_CONFIG (new);
+}
+
+static gboolean
+gimp_context_copy (GimpConfig *src,
+ GimpConfig *dest,
+ GParamFlags flags)
+{
+ GimpContext *src_context = GIMP_CONTEXT (src);
+ GimpContext *dest_context = GIMP_CONTEXT (dest);
+ gboolean success = parent_config_iface->copy (src, dest, flags);
+
+ COPY_NAME (src_context, dest_context, tool_name);
+ COPY_NAME (src_context, dest_context, paint_name);
+ COPY_NAME (src_context, dest_context, brush_name);
+ COPY_NAME (src_context, dest_context, dynamics_name);
+ COPY_NAME (src_context, dest_context, mybrush_name);
+ COPY_NAME (src_context, dest_context, pattern_name);
+ COPY_NAME (src_context, dest_context, gradient_name);
+ COPY_NAME (src_context, dest_context, palette_name);
+ COPY_NAME (src_context, dest_context, font_name);
+ COPY_NAME (src_context, dest_context, tool_preset_name);
+ COPY_NAME (src_context, dest_context, buffer_name);
+ COPY_NAME (src_context, dest_context, imagefile_name);
+ COPY_NAME (src_context, dest_context, template_name);
+
+ return success;
+}
+
+
+/*****************************************************************************/
+/* public functions ********************************************************/
+
+GimpContext *
+gimp_context_new (Gimp *gimp,
+ const gchar *name,
+ GimpContext *template)
+{
+ GimpContext *context;
+
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
+ g_return_val_if_fail (name != NULL, NULL);
+ g_return_val_if_fail (template == NULL || GIMP_IS_CONTEXT (template), NULL);
+
+ context = g_object_new (GIMP_TYPE_CONTEXT,
+ "name", name,
+ "gimp", gimp,
+ NULL);
+
+ if (template)
+ {
+ context->defined_props = template->defined_props;
+
+ gimp_context_copy_properties (template, context,
+ GIMP_CONTEXT_PROP_MASK_ALL);
+ }
+
+ return context;
+}
+
+GimpContext *
+gimp_context_get_parent (GimpContext *context)
+{
+ g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL);
+
+ return context->parent;
+}
+
+static void
+gimp_context_parent_notify (GimpContext *parent,
+ GParamSpec *pspec,
+ GimpContext *context)
+{
+ if (pspec->owner_type == GIMP_TYPE_CONTEXT)
+ {
+ GimpContextPropType prop = pspec->param_id;
+
+ /* copy from parent if the changed property is undefined;
+ * ignore properties that are not context properties, for
+ * example notifications on the context's "gimp" property
+ */
+ if ((prop >= GIMP_CONTEXT_PROP_FIRST) &&
+ (prop <= GIMP_CONTEXT_PROP_LAST) &&
+ ! ((1 << prop) & context->defined_props))
+ {
+ gimp_context_copy_property (parent, context, prop);
+ }
+ }
+}
+
+void
+gimp_context_set_parent (GimpContext *context,
+ GimpContext *parent)
+{
+ g_return_if_fail (GIMP_IS_CONTEXT (context));
+ g_return_if_fail (parent == NULL || GIMP_IS_CONTEXT (parent));
+ g_return_if_fail (parent == NULL || parent->parent != context);
+ g_return_if_fail (context != parent);
+
+ if (context->parent == parent)
+ return;
+
+ if (context->parent)
+ {
+ g_signal_handlers_disconnect_by_func (context->parent,
+ gimp_context_parent_notify,
+ context);
+
+ g_object_remove_weak_pointer (G_OBJECT (context->parent),
+ (gpointer) &context->parent);
+ }
+
+ context->parent = parent;
+
+ if (parent)
+ {
+ g_object_add_weak_pointer (G_OBJECT (context->parent),
+ (gpointer) &context->parent);
+
+ /* copy all undefined properties from the new parent */
+ gimp_context_copy_properties (parent, context,
+ ~context->defined_props &
+ GIMP_CONTEXT_PROP_MASK_ALL);
+
+ g_signal_connect_object (parent, "notify",
+ G_CALLBACK (gimp_context_parent_notify),
+ context,
+ 0);
+ }
+}
+
+
+/* define / undefinine context properties */
+
+void
+gimp_context_define_property (GimpContext *context,
+ GimpContextPropType prop,
+ gboolean defined)
+{
+ GimpContextPropMask mask;
+
+ g_return_if_fail (GIMP_IS_CONTEXT (context));
+ g_return_if_fail ((prop >= GIMP_CONTEXT_PROP_FIRST) &&
+ (prop <= GIMP_CONTEXT_PROP_LAST));
+
+ mask = (1 << prop);
+
+ if (defined)
+ {
+ if (! (context->defined_props & mask))
+ {
+ context->defined_props |= mask;
+ }
+ }
+ else
+ {
+ if (context->defined_props & mask)
+ {
+ context->defined_props &= ~mask;
+
+ if (context->parent)
+ gimp_context_copy_property (context->parent, context, prop);
+ }
+ }
+}
+
+gboolean
+gimp_context_property_defined (GimpContext *context,
+ GimpContextPropType prop)
+{
+ g_return_val_if_fail (GIMP_IS_CONTEXT (context), FALSE);
+
+ return (context->defined_props & (1 << prop)) ? TRUE : FALSE;
+}
+
+void
+gimp_context_define_properties (GimpContext *context,
+ GimpContextPropMask prop_mask,
+ gboolean defined)
+{
+ GimpContextPropType prop;
+
+ g_return_if_fail (GIMP_IS_CONTEXT (context));
+
+ for (prop = GIMP_CONTEXT_PROP_FIRST; prop <= GIMP_CONTEXT_PROP_LAST; prop++)
+ if ((1 << prop) & prop_mask)
+ gimp_context_define_property (context, prop, defined);
+}
+
+
+/* specify which context properties will be serialized */
+
+void
+gimp_context_set_serialize_properties (GimpContext *context,
+ GimpContextPropMask props_mask)
+{
+ g_return_if_fail (GIMP_IS_CONTEXT (context));
+
+ context->serialize_props = props_mask;
+}
+
+GimpContextPropMask
+gimp_context_get_serialize_properties (GimpContext *context)
+{
+ g_return_val_if_fail (GIMP_IS_CONTEXT (context), 0);
+
+ return context->serialize_props;
+}
+
+
+/* copying context properties */
+
+void
+gimp_context_copy_property (GimpContext *src,
+ GimpContext *dest,
+ GimpContextPropType prop)
+{
+ g_return_if_fail (GIMP_IS_CONTEXT (src));
+ g_return_if_fail (GIMP_IS_CONTEXT (dest));
+ g_return_if_fail ((prop >= GIMP_CONTEXT_PROP_FIRST) &&
+ (prop <= GIMP_CONTEXT_PROP_LAST));
+
+ switch (prop)
+ {
+ case GIMP_CONTEXT_PROP_IMAGE:
+ gimp_context_real_set_image (dest, src->image);
+ break;
+
+ case GIMP_CONTEXT_PROP_DISPLAY:
+ gimp_context_real_set_display (dest, src->display);
+ break;
+
+ case GIMP_CONTEXT_PROP_TOOL:
+ gimp_context_real_set_tool (dest, src->tool_info);
+ COPY_NAME (src, dest, tool_name);
+ break;
+
+ case GIMP_CONTEXT_PROP_PAINT_INFO:
+ gimp_context_real_set_paint_info (dest, src->paint_info);
+ COPY_NAME (src, dest, paint_name);
+ break;
+
+ case GIMP_CONTEXT_PROP_FOREGROUND:
+ gimp_context_real_set_foreground (dest, &src->foreground);
+ break;
+
+ case GIMP_CONTEXT_PROP_BACKGROUND:
+ gimp_context_real_set_background (dest, &src->background);
+ break;
+
+ case GIMP_CONTEXT_PROP_OPACITY:
+ gimp_context_real_set_opacity (dest, src->opacity);
+ break;
+
+ case GIMP_CONTEXT_PROP_PAINT_MODE:
+ gimp_context_real_set_paint_mode (dest, src->paint_mode);
+ break;
+
+ case GIMP_CONTEXT_PROP_BRUSH:
+ gimp_context_real_set_brush (dest, src->brush);
+ COPY_NAME (src, dest, brush_name);
+ break;
+
+ case GIMP_CONTEXT_PROP_DYNAMICS:
+ gimp_context_real_set_dynamics (dest, src->dynamics);
+ COPY_NAME (src, dest, dynamics_name);
+ break;
+
+ case GIMP_CONTEXT_PROP_MYBRUSH:
+ gimp_context_real_set_mybrush (dest, src->mybrush);
+ COPY_NAME (src, dest, mybrush_name);
+ break;
+
+ case GIMP_CONTEXT_PROP_PATTERN:
+ gimp_context_real_set_pattern (dest, src->pattern);
+ COPY_NAME (src, dest, pattern_name);
+ break;
+
+ case GIMP_CONTEXT_PROP_GRADIENT:
+ gimp_context_real_set_gradient (dest, src->gradient);
+ COPY_NAME (src, dest, gradient_name);
+ break;
+
+ case GIMP_CONTEXT_PROP_PALETTE:
+ gimp_context_real_set_palette (dest, src->palette);
+ COPY_NAME (src, dest, palette_name);
+ break;
+
+ case GIMP_CONTEXT_PROP_FONT:
+ gimp_context_real_set_font (dest, src->font);
+ COPY_NAME (src, dest, font_name);
+ break;
+
+ case GIMP_CONTEXT_PROP_TOOL_PRESET:
+ gimp_context_real_set_tool_preset (dest, src->tool_preset);
+ COPY_NAME (src, dest, tool_preset_name);
+ break;
+
+ case GIMP_CONTEXT_PROP_BUFFER:
+ gimp_context_real_set_buffer (dest, src->buffer);
+ COPY_NAME (src, dest, buffer_name);
+ break;
+
+ case GIMP_CONTEXT_PROP_IMAGEFILE:
+ gimp_context_real_set_imagefile (dest, src->imagefile);
+ COPY_NAME (src, dest, imagefile_name);
+ break;
+
+ case GIMP_CONTEXT_PROP_TEMPLATE:
+ gimp_context_real_set_template (dest, src->template);
+ COPY_NAME (src, dest, template_name);
+ break;
+
+ default:
+ break;
+ }
+}
+
+void
+gimp_context_copy_properties (GimpContext *src,
+ GimpContext *dest,
+ GimpContextPropMask prop_mask)
+{
+ GimpContextPropType prop;
+
+ g_return_if_fail (GIMP_IS_CONTEXT (src));
+ g_return_if_fail (GIMP_IS_CONTEXT (dest));
+
+ for (prop = GIMP_CONTEXT_PROP_FIRST; prop <= GIMP_CONTEXT_PROP_LAST; prop++)
+ if ((1 << prop) & prop_mask)
+ gimp_context_copy_property (src, dest, prop);
+}
+
+
+/* attribute access functions */
+
+/*****************************************************************************/
+/* manipulate by GType *****************************************************/
+
+GimpContextPropType
+gimp_context_type_to_property (GType type)
+{
+ GimpContextPropType prop;
+
+ for (prop = GIMP_CONTEXT_PROP_FIRST; prop <= GIMP_CONTEXT_PROP_LAST; prop++)
+ {
+ if (g_type_is_a (type, gimp_context_prop_types[prop]))
+ return prop;
+ }
+
+ return -1;
+}
+
+const gchar *
+gimp_context_type_to_prop_name (GType type)
+{
+ GimpContextPropType prop;
+
+ for (prop = GIMP_CONTEXT_PROP_FIRST; prop <= GIMP_CONTEXT_PROP_LAST; prop++)
+ {
+ if (g_type_is_a (type, gimp_context_prop_types[prop]))
+ return gimp_context_prop_names[prop];
+ }
+
+ return NULL;
+}
+
+const gchar *
+gimp_context_type_to_signal_name (GType type)
+{
+ GimpContextPropType prop;
+
+ for (prop = GIMP_CONTEXT_PROP_FIRST; prop <= GIMP_CONTEXT_PROP_LAST; prop++)
+ {
+ if (g_type_is_a (type, gimp_context_prop_types[prop]))
+ return g_signal_name (gimp_context_signals[prop]);
+ }
+
+ return NULL;
+}
+
+GimpObject *
+gimp_context_get_by_type (GimpContext *context,
+ GType type)
+{
+ GimpContextPropType prop;
+ GimpObject *object = NULL;
+
+ g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL);
+
+ prop = gimp_context_type_to_property (type);
+
+ g_return_val_if_fail (prop != -1, NULL);
+
+ g_object_get (context,
+ gimp_context_prop_names[prop], &object,
+ NULL);
+
+ /* g_object_get() refs the object, this function however is a getter,
+ * which usually doesn't ref it's return value
+ */
+ if (object)
+ g_object_unref (object);
+
+ return object;
+}
+
+void
+gimp_context_set_by_type (GimpContext *context,
+ GType type,
+ GimpObject *object)
+{
+ GimpContextPropType prop;
+ GParamSpec *pspec;
+ GValue value = G_VALUE_INIT;
+
+ g_return_if_fail (GIMP_IS_CONTEXT (context));
+ g_return_if_fail (object == NULL || G_IS_OBJECT (object));
+
+ prop = gimp_context_type_to_property (type);
+
+ g_return_if_fail (prop != -1);
+
+ pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (context),
+ gimp_context_prop_names[prop]);
+
+ g_return_if_fail (pspec != NULL);
+
+ g_value_init (&value, pspec->value_type);
+ g_value_set_object (&value, object);
+
+ /* we use gimp_context_set_property() (which in turn only calls
+ * gimp_context_set_foo() functions) instead of the much more obvious
+ * g_object_set(); this avoids g_object_freeze_notify()/thaw_notify()
+ * around the g_object_set() and makes GimpContext callbacks being
+ * called in a much more predictable order. See bug #731279.
+ */
+ gimp_context_set_property (G_OBJECT (context),
+ pspec->param_id,
+ (const GValue *) &value,
+ pspec);
+
+ g_value_unset (&value);
+}
+
+void
+gimp_context_changed_by_type (GimpContext *context,
+ GType type)
+{
+ GimpContextPropType prop;
+ GimpObject *object;
+
+ g_return_if_fail (GIMP_IS_CONTEXT (context));
+
+ prop = gimp_context_type_to_property (type);
+
+ g_return_if_fail (prop != -1);
+
+ object = gimp_context_get_by_type (context, type);
+
+ g_signal_emit (context,
+ gimp_context_signals[prop], 0,
+ object);
+}
+
+
+/*****************************************************************************/
+/* image *******************************************************************/
+
+GimpImage *
+gimp_context_get_image (GimpContext *context)
+{
+ g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL);
+
+ return context->image;
+}
+
+void
+gimp_context_set_image (GimpContext *context,
+ GimpImage *image)
+{
+ g_return_if_fail (GIMP_IS_CONTEXT (context));
+ g_return_if_fail (image == NULL || GIMP_IS_IMAGE (image));
+
+ context_find_defined (context, GIMP_CONTEXT_PROP_IMAGE);
+
+ gimp_context_real_set_image (context, image);
+}
+
+void
+gimp_context_image_changed (GimpContext *context)
+{
+ g_return_if_fail (GIMP_IS_CONTEXT (context));
+
+ g_signal_emit (context,
+ gimp_context_signals[IMAGE_CHANGED], 0,
+ context->image);
+}
+
+static void
+gimp_context_image_removed (GimpContainer *container,
+ GimpImage *image,
+ GimpContext *context)
+{
+ if (context->image == image)
+ gimp_context_real_set_image (context, NULL);
+}
+
+static void
+gimp_context_real_set_image (GimpContext *context,
+ GimpImage *image)
+{
+ if (context->image == image)
+ return;
+
+ context->image = image;
+
+ g_object_notify (G_OBJECT (context), "image");
+ gimp_context_image_changed (context);
+}
+
+
+/*****************************************************************************/
+/* display *****************************************************************/
+
+gpointer
+gimp_context_get_display (GimpContext *context)
+{
+ g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL);
+
+ return context->display;
+}
+
+void
+gimp_context_set_display (GimpContext *context,
+ gpointer display)
+{
+ g_return_if_fail (GIMP_IS_CONTEXT (context));
+ g_return_if_fail (display == NULL || GIMP_IS_OBJECT (display));
+
+ context_find_defined (context, GIMP_CONTEXT_PROP_DISPLAY);
+
+ gimp_context_real_set_display (context, display);
+}
+
+void
+gimp_context_display_changed (GimpContext *context)
+{
+ g_return_if_fail (GIMP_IS_CONTEXT (context));
+
+ g_signal_emit (context,
+ gimp_context_signals[DISPLAY_CHANGED], 0,
+ context->display);
+}
+
+static void
+gimp_context_display_removed (GimpContainer *container,
+ gpointer display,
+ GimpContext *context)
+{
+ if (context->display == display)
+ gimp_context_real_set_display (context, NULL);
+}
+
+static void
+gimp_context_real_set_display (GimpContext *context,
+ gpointer display)
+{
+ GimpObject *old_display;
+
+ if (context->display == display)
+ {
+ /* make sure that setting a display *always* sets the image
+ * to that display's image, even if the display already
+ * matches
+ */
+ if (display)
+ {
+ GimpImage *image;
+
+ g_object_get (display, "image", &image, NULL);
+
+ gimp_context_real_set_image (context, image);
+
+ if (image)
+ g_object_unref (image);
+ }
+
+ return;
+ }
+
+ old_display = context->display;
+
+ context->display = display;
+
+ if (context->display)
+ {
+ GimpImage *image;
+
+ g_object_get (display, "image", &image, NULL);
+
+ gimp_context_real_set_image (context, image);
+
+ if (image)
+ g_object_unref (image);
+ }
+ else if (old_display)
+ {
+ gimp_context_real_set_image (context, NULL);
+ }
+
+ g_object_notify (G_OBJECT (context), "display");
+ gimp_context_display_changed (context);
+}
+
+
+/*****************************************************************************/
+/* tool ********************************************************************/
+
+GimpToolInfo *
+gimp_context_get_tool (GimpContext *context)
+{
+ g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL);
+
+ return context->tool_info;
+}
+
+void
+gimp_context_set_tool (GimpContext *context,
+ GimpToolInfo *tool_info)
+{
+ g_return_if_fail (GIMP_IS_CONTEXT (context));
+ g_return_if_fail (tool_info == NULL || GIMP_IS_TOOL_INFO (tool_info));
+
+ context_find_defined (context, GIMP_CONTEXT_PROP_TOOL);
+
+ gimp_context_real_set_tool (context, tool_info);
+}
+
+void
+gimp_context_tool_changed (GimpContext *context)
+{
+ g_return_if_fail (GIMP_IS_CONTEXT (context));
+
+ g_signal_emit (context,
+ gimp_context_signals[TOOL_CHANGED], 0,
+ context->tool_info);
+}
+
+static void
+gimp_context_tool_dirty (GimpToolInfo *tool_info,
+ GimpContext *context)
+{
+ g_free (context->tool_name);
+ context->tool_name = g_strdup (gimp_object_get_name (tool_info));
+
+ g_signal_emit (context, gimp_context_signals[PROP_NAME_CHANGED], 0,
+ GIMP_CONTEXT_PROP_TOOL);
+}
+
+static void
+gimp_context_tool_list_thaw (GimpContainer *container,
+ GimpContext *context)
+{
+ GimpToolInfo *tool_info;
+
+ if (! context->tool_name)
+ context->tool_name = g_strdup ("gimp-paintbrush-tool");
+
+ tool_info = gimp_context_find_object (context, container,
+ context->tool_name,
+ gimp_tool_info_get_standard (context->gimp));
+
+ gimp_context_real_set_tool (context, tool_info);
+}
+
+static void
+gimp_context_tool_removed (GimpContainer *container,
+ GimpToolInfo *tool_info,
+ GimpContext *context)
+{
+ if (tool_info == context->tool_info)
+ {
+ g_signal_handlers_disconnect_by_func (context->tool_info,
+ gimp_context_tool_dirty,
+ context);
+ g_clear_object (&context->tool_info);
+
+ if (! gimp_container_frozen (container))
+ gimp_context_tool_list_thaw (container, context);
+ }
+}
+
+static void
+gimp_context_real_set_tool (GimpContext *context,
+ GimpToolInfo *tool_info)
+{
+ if (context->tool_info == tool_info)
+ return;
+
+ if (context->tool_name &&
+ tool_info != gimp_tool_info_get_standard (context->gimp))
+ {
+ g_clear_pointer (&context->tool_name, g_free);
+ }
+
+ if (context->tool_info)
+ g_signal_handlers_disconnect_by_func (context->tool_info,
+ gimp_context_tool_dirty,
+ context);
+
+ g_set_object (&context->tool_info, tool_info);
+
+ if (tool_info)
+ {
+ g_signal_connect_object (tool_info, "name-changed",
+ G_CALLBACK (gimp_context_tool_dirty),
+ context,
+ 0);
+
+ if (tool_info != gimp_tool_info_get_standard (context->gimp))
+ context->tool_name = g_strdup (gimp_object_get_name (tool_info));
+
+ if (tool_info->paint_info)
+ gimp_context_real_set_paint_info (context, tool_info->paint_info);
+ }
+
+ g_object_notify (G_OBJECT (context), "tool");
+ gimp_context_tool_changed (context);
+}
+
+
+/*****************************************************************************/
+/* paint info **************************************************************/
+
+GimpPaintInfo *
+gimp_context_get_paint_info (GimpContext *context)
+{
+ g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL);
+
+ return context->paint_info;
+}
+
+void
+gimp_context_set_paint_info (GimpContext *context,
+ GimpPaintInfo *paint_info)
+{
+ g_return_if_fail (GIMP_IS_CONTEXT (context));
+ g_return_if_fail (paint_info == NULL || GIMP_IS_PAINT_INFO (paint_info));
+
+ context_find_defined (context, GIMP_CONTEXT_PROP_PAINT_INFO);
+
+ gimp_context_real_set_paint_info (context, paint_info);
+}
+
+void
+gimp_context_paint_info_changed (GimpContext *context)
+{
+ g_return_if_fail (GIMP_IS_CONTEXT (context));
+
+ g_signal_emit (context,
+ gimp_context_signals[PAINT_INFO_CHANGED], 0,
+ context->paint_info);
+}
+
+static void
+gimp_context_paint_info_dirty (GimpPaintInfo *paint_info,
+ GimpContext *context)
+{
+ g_free (context->paint_name);
+ context->paint_name = g_strdup (gimp_object_get_name (paint_info));
+
+ g_signal_emit (context, gimp_context_signals[PROP_NAME_CHANGED], 0,
+ GIMP_CONTEXT_PROP_PAINT_INFO);
+}
+
+/* the global paint info list is there again after refresh */
+static void
+gimp_context_paint_info_list_thaw (GimpContainer *container,
+ GimpContext *context)
+{
+ GimpPaintInfo *paint_info;
+
+ if (! context->paint_name)
+ context->paint_name = g_strdup ("gimp-paintbrush");
+
+ paint_info = gimp_context_find_object (context, container,
+ context->paint_name,
+ gimp_paint_info_get_standard (context->gimp));
+
+ gimp_context_real_set_paint_info (context, paint_info);
+}
+
+static void
+gimp_context_paint_info_removed (GimpContainer *container,
+ GimpPaintInfo *paint_info,
+ GimpContext *context)
+{
+ if (paint_info == context->paint_info)
+ {
+ g_signal_handlers_disconnect_by_func (context->paint_info,
+ gimp_context_paint_info_dirty,
+ context);
+ g_clear_object (&context->paint_info);
+
+ if (! gimp_container_frozen (container))
+ gimp_context_paint_info_list_thaw (container, context);
+ }
+}
+
+static void
+gimp_context_real_set_paint_info (GimpContext *context,
+ GimpPaintInfo *paint_info)
+{
+ if (context->paint_info == paint_info)
+ return;
+
+ if (context->paint_name &&
+ paint_info != gimp_paint_info_get_standard (context->gimp))
+ {
+ g_clear_pointer (&context->paint_name, g_free);
+ }
+
+ if (context->paint_info)
+ g_signal_handlers_disconnect_by_func (context->paint_info,
+ gimp_context_paint_info_dirty,
+ context);
+
+ g_set_object (&context->paint_info, paint_info);
+
+ if (paint_info)
+ {
+ g_signal_connect_object (paint_info, "name-changed",
+ G_CALLBACK (gimp_context_paint_info_dirty),
+ context,
+ 0);
+
+ if (paint_info != gimp_paint_info_get_standard (context->gimp))
+ context->paint_name = g_strdup (gimp_object_get_name (paint_info));
+ }
+
+ g_object_notify (G_OBJECT (context), "paint-info");
+ gimp_context_paint_info_changed (context);
+}
+
+
+/*****************************************************************************/
+/* foreground color ********************************************************/
+
+void
+gimp_context_get_foreground (GimpContext *context,
+ GimpRGB *color)
+{
+ g_return_if_fail (GIMP_IS_CONTEXT (context));
+ g_return_if_fail (color != NULL);
+
+ *color = context->foreground;
+}
+
+void
+gimp_context_set_foreground (GimpContext *context,
+ const GimpRGB *color)
+{
+ g_return_if_fail (GIMP_IS_CONTEXT (context));
+ g_return_if_fail (color != NULL);
+
+ context_find_defined (context, GIMP_CONTEXT_PROP_FOREGROUND);
+
+ gimp_context_real_set_foreground (context, color);
+}
+
+void
+gimp_context_foreground_changed (GimpContext *context)
+{
+ g_return_if_fail (GIMP_IS_CONTEXT (context));
+
+ g_signal_emit (context,
+ gimp_context_signals[FOREGROUND_CHANGED], 0,
+ &context->foreground);
+}
+
+static void
+gimp_context_real_set_foreground (GimpContext *context,
+ const GimpRGB *color)
+{
+ if (gimp_rgba_distance (&context->foreground, color) < RGBA_EPSILON)
+ return;
+
+ context->foreground = *color;
+ gimp_rgb_set_alpha (&context->foreground, GIMP_OPACITY_OPAQUE);
+
+ g_object_notify (G_OBJECT (context), "foreground");
+ gimp_context_foreground_changed (context);
+}
+
+
+/*****************************************************************************/
+/* background color ********************************************************/
+
+void
+gimp_context_get_background (GimpContext *context,
+ GimpRGB *color)
+{
+ g_return_if_fail (GIMP_IS_CONTEXT (context));
+
+ g_return_if_fail (color != NULL);
+
+ *color = context->background;
+}
+
+void
+gimp_context_set_background (GimpContext *context,
+ const GimpRGB *color)
+{
+ g_return_if_fail (GIMP_IS_CONTEXT (context));
+ g_return_if_fail (color != NULL);
+
+ context_find_defined (context, GIMP_CONTEXT_PROP_BACKGROUND);
+
+ gimp_context_real_set_background (context, color);
+}
+
+void
+gimp_context_background_changed (GimpContext *context)
+{
+ g_return_if_fail (GIMP_IS_CONTEXT (context));
+
+ g_signal_emit (context,
+ gimp_context_signals[BACKGROUND_CHANGED], 0,
+ &context->background);
+}
+
+static void
+gimp_context_real_set_background (GimpContext *context,
+ const GimpRGB *color)
+{
+ if (gimp_rgba_distance (&context->background, color) < RGBA_EPSILON)
+ return;
+
+ context->background = *color;
+ gimp_rgb_set_alpha (&context->background, GIMP_OPACITY_OPAQUE);
+
+ g_object_notify (G_OBJECT (context), "background");
+ gimp_context_background_changed (context);
+}
+
+
+/*****************************************************************************/
+/* color utility functions *************************************************/
+
+void
+gimp_context_set_default_colors (GimpContext *context)
+{
+ GimpContext *bg_context;
+ GimpRGB fg;
+ GimpRGB bg;
+
+ g_return_if_fail (GIMP_IS_CONTEXT (context));
+
+ bg_context = context;
+
+ context_find_defined (context, GIMP_CONTEXT_PROP_FOREGROUND);
+ context_find_defined (bg_context, GIMP_CONTEXT_PROP_BACKGROUND);
+
+ gimp_rgba_set (&fg, 0.0, 0.0, 0.0, GIMP_OPACITY_OPAQUE);
+ gimp_rgba_set (&bg, 1.0, 1.0, 1.0, GIMP_OPACITY_OPAQUE);
+
+ gimp_context_real_set_foreground (context, &fg);
+ gimp_context_real_set_background (bg_context, &bg);
+}
+
+void
+gimp_context_swap_colors (GimpContext *context)
+{
+ GimpContext *bg_context;
+ GimpRGB fg;
+ GimpRGB bg;
+
+ g_return_if_fail (GIMP_IS_CONTEXT (context));
+
+ bg_context = context;
+
+ context_find_defined (context, GIMP_CONTEXT_PROP_FOREGROUND);
+ context_find_defined (bg_context, GIMP_CONTEXT_PROP_BACKGROUND);
+
+ gimp_context_get_foreground (context, &fg);
+ gimp_context_get_background (bg_context, &bg);
+
+ gimp_context_real_set_foreground (context, &bg);
+ gimp_context_real_set_background (bg_context, &fg);
+}
+
+
+/*****************************************************************************/
+/* opacity *****************************************************************/
+
+gdouble
+gimp_context_get_opacity (GimpContext *context)
+{
+ g_return_val_if_fail (GIMP_IS_CONTEXT (context), GIMP_OPACITY_OPAQUE);
+
+ return context->opacity;
+}
+
+void
+gimp_context_set_opacity (GimpContext *context,
+ gdouble opacity)
+{
+ g_return_if_fail (GIMP_IS_CONTEXT (context));
+
+ context_find_defined (context, GIMP_CONTEXT_PROP_OPACITY);
+
+ gimp_context_real_set_opacity (context, opacity);
+}
+
+void
+gimp_context_opacity_changed (GimpContext *context)
+{
+ g_return_if_fail (GIMP_IS_CONTEXT (context));
+
+ g_signal_emit (context,
+ gimp_context_signals[OPACITY_CHANGED], 0,
+ context->opacity);
+}
+
+static void
+gimp_context_real_set_opacity (GimpContext *context,
+ gdouble opacity)
+{
+ if (context->opacity == opacity)
+ return;
+
+ context->opacity = opacity;
+
+ g_object_notify (G_OBJECT (context), "opacity");
+ gimp_context_opacity_changed (context);
+}
+
+
+/*****************************************************************************/
+/* paint mode **************************************************************/
+
+GimpLayerMode
+gimp_context_get_paint_mode (GimpContext *context)
+{
+ g_return_val_if_fail (GIMP_IS_CONTEXT (context), GIMP_LAYER_MODE_NORMAL);
+
+ return context->paint_mode;
+}
+
+void
+gimp_context_set_paint_mode (GimpContext *context,
+ GimpLayerMode paint_mode)
+{
+ g_return_if_fail (GIMP_IS_CONTEXT (context));
+
+ context_find_defined (context, GIMP_CONTEXT_PROP_PAINT_MODE);
+
+ gimp_context_real_set_paint_mode (context, paint_mode);
+}
+
+void
+gimp_context_paint_mode_changed (GimpContext *context)
+{
+ g_return_if_fail (GIMP_IS_CONTEXT (context));
+
+ g_signal_emit (context,
+ gimp_context_signals[PAINT_MODE_CHANGED], 0,
+ context->paint_mode);
+}
+
+static void
+gimp_context_real_set_paint_mode (GimpContext *context,
+ GimpLayerMode paint_mode)
+{
+ if (context->paint_mode == paint_mode)
+ return;
+
+ context->paint_mode = paint_mode;
+
+ g_object_notify (G_OBJECT (context), "paint-mode");
+ gimp_context_paint_mode_changed (context);
+}
+
+
+/*****************************************************************************/
+/* brush *******************************************************************/
+
+GimpBrush *
+gimp_context_get_brush (GimpContext *context)
+{
+ g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL);
+
+ return context->brush;
+}
+
+void
+gimp_context_set_brush (GimpContext *context,
+ GimpBrush *brush)
+{
+ g_return_if_fail (GIMP_IS_CONTEXT (context));
+ g_return_if_fail (brush == NULL || GIMP_IS_BRUSH (brush));
+
+ context_find_defined (context, GIMP_CONTEXT_PROP_BRUSH);
+
+ gimp_context_real_set_brush (context, brush);
+}
+
+void
+gimp_context_brush_changed (GimpContext *context)
+{
+ g_return_if_fail (GIMP_IS_CONTEXT (context));
+
+ g_signal_emit (context,
+ gimp_context_signals[BRUSH_CHANGED], 0,
+ context->brush);
+}
+
+static void
+gimp_context_brush_dirty (GimpBrush *brush,
+ GimpContext *context)
+{
+ g_free (context->brush_name);
+ context->brush_name = g_strdup (gimp_object_get_name (brush));
+
+ g_signal_emit (context, gimp_context_signals[PROP_NAME_CHANGED], 0,
+ GIMP_CONTEXT_PROP_BRUSH);
+}
+
+static void
+gimp_context_brush_list_thaw (GimpContainer *container,
+ GimpContext *context)
+{
+ GimpBrush *brush;
+
+ if (! context->brush_name)
+ context->brush_name = g_strdup (context->gimp->config->default_brush);
+
+ brush = gimp_context_find_object (context, container,
+ context->brush_name,
+ gimp_brush_get_standard (context));
+
+ gimp_context_real_set_brush (context, brush);
+}
+
+/* the active brush disappeared */
+static void
+gimp_context_brush_removed (GimpContainer *container,
+ GimpBrush *brush,
+ GimpContext *context)
+{
+ if (brush == context->brush)
+ {
+ g_signal_handlers_disconnect_by_func (context->brush,
+ gimp_context_brush_dirty,
+ context);
+ g_clear_object (&context->brush);
+
+ if (! gimp_container_frozen (container))
+ gimp_context_brush_list_thaw (container, context);
+ }
+}
+
+static void
+gimp_context_real_set_brush (GimpContext *context,
+ GimpBrush *brush)
+{
+ if (context->brush == brush)
+ return;
+
+ if (context->brush_name &&
+ brush != GIMP_BRUSH (gimp_brush_get_standard (context)))
+ {
+ g_clear_pointer (&context->brush_name, g_free);
+ }
+
+ if (context->brush)
+ g_signal_handlers_disconnect_by_func (context->brush,
+ gimp_context_brush_dirty,
+ context);
+
+ g_set_object (&context->brush, brush);
+
+ if (brush)
+ {
+ g_signal_connect_object (brush, "name-changed",
+ G_CALLBACK (gimp_context_brush_dirty),
+ context,
+ 0);
+
+ if (brush != GIMP_BRUSH (gimp_brush_get_standard (context)))
+ context->brush_name = g_strdup (gimp_object_get_name (brush));
+ }
+
+ g_object_notify (G_OBJECT (context), "brush");
+ gimp_context_brush_changed (context);
+}
+
+
+/*****************************************************************************/
+/* dynamics *****************************************************************/
+
+GimpDynamics *
+gimp_context_get_dynamics (GimpContext *context)
+{
+ g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL);
+
+ return context->dynamics;
+}
+
+void
+gimp_context_set_dynamics (GimpContext *context,
+ GimpDynamics *dynamics)
+{
+ g_return_if_fail (GIMP_IS_CONTEXT (context));
+ g_return_if_fail (dynamics == NULL || GIMP_IS_DYNAMICS (dynamics));
+
+ context_find_defined (context, GIMP_CONTEXT_PROP_DYNAMICS);
+
+ gimp_context_real_set_dynamics (context, dynamics);
+}
+
+void
+gimp_context_dynamics_changed (GimpContext *context)
+{
+ g_return_if_fail (GIMP_IS_CONTEXT (context));
+
+ g_signal_emit (context,
+ gimp_context_signals[DYNAMICS_CHANGED], 0,
+ context->dynamics);
+}
+
+static void
+gimp_context_dynamics_dirty (GimpDynamics *dynamics,
+ GimpContext *context)
+{
+ g_free (context->dynamics_name);
+ context->dynamics_name = g_strdup (gimp_object_get_name (dynamics));
+
+ g_signal_emit (context, gimp_context_signals[PROP_NAME_CHANGED], 0,
+ GIMP_CONTEXT_PROP_DYNAMICS);
+}
+
+static void
+gimp_context_dynamics_removed (GimpContainer *container,
+ GimpDynamics *dynamics,
+ GimpContext *context)
+{
+ if (dynamics == context->dynamics)
+ {
+ g_signal_handlers_disconnect_by_func (context->dynamics,
+ gimp_context_dynamics_dirty,
+ context);
+ g_clear_object (&context->dynamics);
+
+ if (! gimp_container_frozen (container))
+ gimp_context_dynamics_list_thaw (container, context);
+ }
+}
+
+static void
+gimp_context_dynamics_list_thaw (GimpContainer *container,
+ GimpContext *context)
+{
+ GimpDynamics *dynamics;
+
+ if (! context->dynamics_name)
+ context->dynamics_name = g_strdup (context->gimp->config->default_dynamics);
+
+ dynamics = gimp_context_find_object (context, container,
+ context->dynamics_name,
+ gimp_dynamics_get_standard (context));
+
+ gimp_context_real_set_dynamics (context, dynamics);
+}
+
+static void
+gimp_context_real_set_dynamics (GimpContext *context,
+ GimpDynamics *dynamics)
+{
+ if (context->dynamics == dynamics)
+ return;
+
+ if (context->dynamics_name &&
+ dynamics != GIMP_DYNAMICS (gimp_dynamics_get_standard (context)))
+ {
+ g_clear_pointer (&context->dynamics_name, g_free);
+ }
+
+ if (context->dynamics)
+ g_signal_handlers_disconnect_by_func (context->dynamics,
+ gimp_context_dynamics_dirty,
+ context);
+
+ g_set_object (&context->dynamics, dynamics);
+
+ if (dynamics)
+ {
+ g_signal_connect_object (dynamics, "name-changed",
+ G_CALLBACK (gimp_context_dynamics_dirty),
+ context,
+ 0);
+
+ if (dynamics != GIMP_DYNAMICS (gimp_dynamics_get_standard (context)))
+ context->dynamics_name = g_strdup (gimp_object_get_name (dynamics));
+ }
+
+ g_object_notify (G_OBJECT (context), "dynamics");
+ gimp_context_dynamics_changed (context);
+}
+
+
+/*****************************************************************************/
+/* mybrush *****************************************************************/
+
+GimpMybrush *
+gimp_context_get_mybrush (GimpContext *context)
+{
+ g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL);
+
+ return context->mybrush;
+}
+
+void
+gimp_context_set_mybrush (GimpContext *context,
+ GimpMybrush *brush)
+{
+ g_return_if_fail (GIMP_IS_CONTEXT (context));
+ g_return_if_fail (brush == NULL || GIMP_IS_MYBRUSH (brush));
+
+ context_find_defined (context, GIMP_CONTEXT_PROP_MYBRUSH);
+
+ gimp_context_real_set_mybrush (context, brush);
+}
+
+void
+gimp_context_mybrush_changed (GimpContext *context)
+{
+ g_return_if_fail (GIMP_IS_CONTEXT (context));
+
+ g_signal_emit (context,
+ gimp_context_signals[MYBRUSH_CHANGED], 0,
+ context->mybrush);
+}
+
+static void
+gimp_context_mybrush_dirty (GimpMybrush *brush,
+ GimpContext *context)
+{
+ g_free (context->mybrush_name);
+ context->mybrush_name = g_strdup (gimp_object_get_name (brush));
+
+ g_signal_emit (context, gimp_context_signals[PROP_NAME_CHANGED], 0,
+ GIMP_CONTEXT_PROP_MYBRUSH);
+}
+
+static void
+gimp_context_mybrush_list_thaw (GimpContainer *container,
+ GimpContext *context)
+{
+ GimpMybrush *brush;
+
+ if (! context->mybrush_name)
+ context->mybrush_name = g_strdup (context->gimp->config->default_mypaint_brush);
+
+ brush = gimp_context_find_object (context, container,
+ context->mybrush_name,
+ gimp_mybrush_get_standard (context));
+
+ gimp_context_real_set_mybrush (context, brush);
+}
+
+static void
+gimp_context_mybrush_removed (GimpContainer *container,
+ GimpMybrush *brush,
+ GimpContext *context)
+{
+ if (brush == context->mybrush)
+ {
+ g_signal_handlers_disconnect_by_func (context->mybrush,
+ gimp_context_mybrush_dirty,
+ context);
+ g_clear_object (&context->mybrush);
+
+ if (! gimp_container_frozen (container))
+ gimp_context_mybrush_list_thaw (container, context);
+ }
+}
+
+static void
+gimp_context_real_set_mybrush (GimpContext *context,
+ GimpMybrush *brush)
+{
+ if (context->mybrush == brush)
+ return;
+
+ if (context->mybrush_name &&
+ brush != GIMP_MYBRUSH (gimp_mybrush_get_standard (context)))
+ {
+ g_clear_pointer (&context->mybrush_name, g_free);
+ }
+
+ if (context->mybrush)
+ g_signal_handlers_disconnect_by_func (context->mybrush,
+ gimp_context_mybrush_dirty,
+ context);
+
+ g_set_object (&context->mybrush, brush);
+
+ if (brush)
+ {
+ g_signal_connect_object (brush, "name-changed",
+ G_CALLBACK (gimp_context_mybrush_dirty),
+ context,
+ 0);
+
+ if (brush != GIMP_MYBRUSH (gimp_mybrush_get_standard (context)))
+ context->mybrush_name = g_strdup (gimp_object_get_name (brush));
+ }
+
+ g_object_notify (G_OBJECT (context), "mybrush");
+ gimp_context_mybrush_changed (context);
+}
+
+
+/*****************************************************************************/
+/* pattern *****************************************************************/
+
+GimpPattern *
+gimp_context_get_pattern (GimpContext *context)
+{
+ g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL);
+
+ return context->pattern;
+}
+
+void
+gimp_context_set_pattern (GimpContext *context,
+ GimpPattern *pattern)
+{
+ g_return_if_fail (GIMP_IS_CONTEXT (context));
+ g_return_if_fail (pattern == NULL || GIMP_IS_PATTERN (pattern));
+
+ context_find_defined (context, GIMP_CONTEXT_PROP_PATTERN);
+
+ gimp_context_real_set_pattern (context, pattern);
+}
+
+void
+gimp_context_pattern_changed (GimpContext *context)
+{
+ g_return_if_fail (GIMP_IS_CONTEXT (context));
+
+ g_signal_emit (context,
+ gimp_context_signals[PATTERN_CHANGED], 0,
+ context->pattern);
+}
+
+static void
+gimp_context_pattern_dirty (GimpPattern *pattern,
+ GimpContext *context)
+{
+ g_free (context->pattern_name);
+ context->pattern_name = g_strdup (gimp_object_get_name (pattern));
+
+ g_signal_emit (context, gimp_context_signals[PROP_NAME_CHANGED], 0,
+ GIMP_CONTEXT_PROP_PATTERN);
+}
+
+static void
+gimp_context_pattern_list_thaw (GimpContainer *container,
+ GimpContext *context)
+{
+ GimpPattern *pattern;
+
+ if (! context->pattern_name)
+ context->pattern_name = g_strdup (context->gimp->config->default_pattern);
+
+ pattern = gimp_context_find_object (context, container,
+ context->pattern_name,
+ gimp_pattern_get_standard (context));
+
+ gimp_context_real_set_pattern (context, pattern);
+}
+
+static void
+gimp_context_pattern_removed (GimpContainer *container,
+ GimpPattern *pattern,
+ GimpContext *context)
+{
+ if (pattern == context->pattern)
+ {
+ g_signal_handlers_disconnect_by_func (context->pattern,
+ gimp_context_pattern_dirty,
+ context);
+ g_clear_object (&context->pattern);
+
+ if (! gimp_container_frozen (container))
+ gimp_context_pattern_list_thaw (container, context);
+ }
+}
+
+static void
+gimp_context_real_set_pattern (GimpContext *context,
+ GimpPattern *pattern)
+{
+ if (context->pattern == pattern)
+ return;
+
+ if (context->pattern_name &&
+ pattern != GIMP_PATTERN (gimp_pattern_get_standard (context)))
+ {
+ g_clear_pointer (&context->pattern_name, g_free);
+ }
+
+ if (context->pattern)
+ g_signal_handlers_disconnect_by_func (context->pattern,
+ gimp_context_pattern_dirty,
+ context);
+
+ g_set_object (&context->pattern, pattern);
+
+ if (pattern)
+ {
+ g_signal_connect_object (pattern, "name-changed",
+ G_CALLBACK (gimp_context_pattern_dirty),
+ context,
+ 0);
+
+ if (pattern != GIMP_PATTERN (gimp_pattern_get_standard (context)))
+ context->pattern_name = g_strdup (gimp_object_get_name (pattern));
+ }
+
+ g_object_notify (G_OBJECT (context), "pattern");
+ gimp_context_pattern_changed (context);
+}
+
+
+/*****************************************************************************/
+/* gradient ****************************************************************/
+
+GimpGradient *
+gimp_context_get_gradient (GimpContext *context)
+{
+ g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL);
+
+ return context->gradient;
+}
+
+void
+gimp_context_set_gradient (GimpContext *context,
+ GimpGradient *gradient)
+{
+ g_return_if_fail (GIMP_IS_CONTEXT (context));
+ g_return_if_fail (gradient == NULL || GIMP_IS_GRADIENT (gradient));
+
+ context_find_defined (context, GIMP_CONTEXT_PROP_GRADIENT);
+
+ gimp_context_real_set_gradient (context, gradient);
+}
+
+void
+gimp_context_gradient_changed (GimpContext *context)
+{
+ g_return_if_fail (GIMP_IS_CONTEXT (context));
+
+ g_signal_emit (context,
+ gimp_context_signals[GRADIENT_CHANGED], 0,
+ context->gradient);
+}
+
+static void
+gimp_context_gradient_dirty (GimpGradient *gradient,
+ GimpContext *context)
+{
+ g_free (context->gradient_name);
+ context->gradient_name = g_strdup (gimp_object_get_name (gradient));
+
+ g_signal_emit (context, gimp_context_signals[PROP_NAME_CHANGED], 0,
+ GIMP_CONTEXT_PROP_GRADIENT);
+}
+
+static void
+gimp_context_gradient_list_thaw (GimpContainer *container,
+ GimpContext *context)
+{
+ GimpGradient *gradient;
+
+ if (! context->gradient_name)
+ context->gradient_name = g_strdup (context->gimp->config->default_gradient);
+
+ gradient = gimp_context_find_object (context, container,
+ context->gradient_name,
+ gimp_gradient_get_standard (context));
+
+ gimp_context_real_set_gradient (context, gradient);
+}
+
+static void
+gimp_context_gradient_removed (GimpContainer *container,
+ GimpGradient *gradient,
+ GimpContext *context)
+{
+ if (gradient == context->gradient)
+ {
+ g_signal_handlers_disconnect_by_func (context->gradient,
+ gimp_context_gradient_dirty,
+ context);
+ g_clear_object (&context->gradient);
+
+ if (! gimp_container_frozen (container))
+ gimp_context_gradient_list_thaw (container, context);
+ }
+}
+
+static void
+gimp_context_real_set_gradient (GimpContext *context,
+ GimpGradient *gradient)
+{
+ if (context->gradient == gradient)
+ return;
+
+ if (context->gradient_name &&
+ gradient != GIMP_GRADIENT (gimp_gradient_get_standard (context)))
+ {
+ g_clear_pointer (&context->gradient_name, g_free);
+ }
+
+ if (context->gradient)
+ g_signal_handlers_disconnect_by_func (context->gradient,
+ gimp_context_gradient_dirty,
+ context);
+
+ g_set_object (&context->gradient, gradient);
+
+ if (gradient)
+ {
+ g_signal_connect_object (gradient, "name-changed",
+ G_CALLBACK (gimp_context_gradient_dirty),
+ context,
+ 0);
+
+ if (gradient != GIMP_GRADIENT (gimp_gradient_get_standard (context)))
+ context->gradient_name = g_strdup (gimp_object_get_name (gradient));
+ }
+
+ g_object_notify (G_OBJECT (context), "gradient");
+ gimp_context_gradient_changed (context);
+}
+
+
+/*****************************************************************************/
+/* palette *****************************************************************/
+
+GimpPalette *
+gimp_context_get_palette (GimpContext *context)
+{
+ g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL);
+
+ return context->palette;
+}
+
+void
+gimp_context_set_palette (GimpContext *context,
+ GimpPalette *palette)
+{
+ g_return_if_fail (GIMP_IS_CONTEXT (context));
+ g_return_if_fail (palette == NULL || GIMP_IS_PALETTE (palette));
+
+ context_find_defined (context, GIMP_CONTEXT_PROP_PALETTE);
+
+ gimp_context_real_set_palette (context, palette);
+}
+
+void
+gimp_context_palette_changed (GimpContext *context)
+{
+ g_return_if_fail (GIMP_IS_CONTEXT (context));
+
+ g_signal_emit (context,
+ gimp_context_signals[PALETTE_CHANGED], 0,
+ context->palette);
+}
+
+static void
+gimp_context_palette_dirty (GimpPalette *palette,
+ GimpContext *context)
+{
+ g_free (context->palette_name);
+ context->palette_name = g_strdup (gimp_object_get_name (palette));
+
+ g_signal_emit (context, gimp_context_signals[PROP_NAME_CHANGED], 0,
+ GIMP_CONTEXT_PROP_PALETTE);
+}
+
+static void
+gimp_context_palette_list_thaw (GimpContainer *container,
+ GimpContext *context)
+{
+ GimpPalette *palette;
+
+ if (! context->palette_name)
+ context->palette_name = g_strdup (context->gimp->config->default_palette);
+
+ palette = gimp_context_find_object (context, container,
+ context->palette_name,
+ gimp_palette_get_standard (context));
+
+ gimp_context_real_set_palette (context, palette);
+}
+
+static void
+gimp_context_palette_removed (GimpContainer *container,
+ GimpPalette *palette,
+ GimpContext *context)
+{
+ if (palette == context->palette)
+ {
+ g_signal_handlers_disconnect_by_func (context->palette,
+ gimp_context_palette_dirty,
+ context);
+ g_clear_object (&context->palette);
+
+ if (! gimp_container_frozen (container))
+ gimp_context_palette_list_thaw (container, context);
+ }
+}
+
+static void
+gimp_context_real_set_palette (GimpContext *context,
+ GimpPalette *palette)
+{
+ if (context->palette == palette)
+ return;
+
+ if (context->palette_name &&
+ palette != GIMP_PALETTE (gimp_palette_get_standard (context)))
+ {
+ g_clear_pointer (&context->palette_name, g_free);
+ }
+
+ if (context->palette)
+ g_signal_handlers_disconnect_by_func (context->palette,
+ gimp_context_palette_dirty,
+ context);
+
+ g_set_object (&context->palette, palette);
+
+ if (palette)
+ {
+ g_signal_connect_object (palette, "name-changed",
+ G_CALLBACK (gimp_context_palette_dirty),
+ context,
+ 0);
+
+ if (palette != GIMP_PALETTE (gimp_palette_get_standard (context)))
+ context->palette_name = g_strdup (gimp_object_get_name (palette));
+ }
+
+ g_object_notify (G_OBJECT (context), "palette");
+ gimp_context_palette_changed (context);
+}
+
+
+/*****************************************************************************/
+/* font *****************************************************************/
+
+GimpFont *
+gimp_context_get_font (GimpContext *context)
+{
+ g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL);
+
+ return context->font;
+}
+
+void
+gimp_context_set_font (GimpContext *context,
+ GimpFont *font)
+{
+ g_return_if_fail (GIMP_IS_CONTEXT (context));
+ g_return_if_fail (font == NULL || GIMP_IS_FONT (font));
+
+ context_find_defined (context, GIMP_CONTEXT_PROP_FONT);
+
+ gimp_context_real_set_font (context, font);
+}
+
+const gchar *
+gimp_context_get_font_name (GimpContext *context)
+{
+ g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL);
+
+ return context->font_name;
+}
+
+void
+gimp_context_set_font_name (GimpContext *context,
+ const gchar *name)
+{
+ GimpContainer *container;
+ GimpObject *font;
+
+ g_return_if_fail (GIMP_IS_CONTEXT (context));
+
+ container = gimp_data_factory_get_container (context->gimp->font_factory);
+ font = gimp_container_get_child_by_name (container, name);
+
+ if (font)
+ {
+ gimp_context_set_font (context, GIMP_FONT (font));
+ }
+ else
+ {
+ /* No font with this name exists, use the standard font, but
+ * keep the intended name around
+ */
+ gimp_context_set_font (context, GIMP_FONT (gimp_font_get_standard ()));
+
+ g_free (context->font_name);
+ context->font_name = g_strdup (name);
+ }
+}
+
+void
+gimp_context_font_changed (GimpContext *context)
+{
+ g_return_if_fail (GIMP_IS_CONTEXT (context));
+
+ g_signal_emit (context,
+ gimp_context_signals[FONT_CHANGED], 0,
+ context->font);
+}
+
+static void
+gimp_context_font_dirty (GimpFont *font,
+ GimpContext *context)
+{
+ g_free (context->font_name);
+ context->font_name = g_strdup (gimp_object_get_name (font));
+
+ g_signal_emit (context, gimp_context_signals[PROP_NAME_CHANGED], 0,
+ GIMP_CONTEXT_PROP_FONT);
+}
+
+static void
+gimp_context_font_list_thaw (GimpContainer *container,
+ GimpContext *context)
+{
+ GimpFont *font;
+
+ if (! context->font_name)
+ context->font_name = g_strdup (context->gimp->config->default_font);
+
+ font = gimp_context_find_object (context, container,
+ context->font_name,
+ gimp_font_get_standard ());
+
+ gimp_context_real_set_font (context, font);
+}
+
+static void
+gimp_context_font_removed (GimpContainer *container,
+ GimpFont *font,
+ GimpContext *context)
+{
+ if (font == context->font)
+ {
+ g_signal_handlers_disconnect_by_func (context->font,
+ gimp_context_font_dirty,
+ context);
+ g_clear_object (&context->font);
+
+ if (! gimp_container_frozen (container))
+ gimp_context_font_list_thaw (container, context);
+ }
+}
+
+static void
+gimp_context_real_set_font (GimpContext *context,
+ GimpFont *font)
+{
+ if (context->font == font)
+ return;
+
+ if (context->font_name &&
+ font != GIMP_FONT (gimp_font_get_standard ()))
+ {
+ g_clear_pointer (&context->font_name, g_free);
+ }
+
+ if (context->font)
+ g_signal_handlers_disconnect_by_func (context->font,
+ gimp_context_font_dirty,
+ context);
+
+ g_set_object (&context->font, font);
+
+ if (font)
+ {
+ g_signal_connect_object (font, "name-changed",
+ G_CALLBACK (gimp_context_font_dirty),
+ context,
+ 0);
+
+ if (font != GIMP_FONT (gimp_font_get_standard ()))
+ context->font_name = g_strdup (gimp_object_get_name (font));
+ }
+
+ g_object_notify (G_OBJECT (context), "font");
+ gimp_context_font_changed (context);
+}
+
+
+/********************************************************************************/
+/* tool preset *****************************************************************/
+
+GimpToolPreset *
+gimp_context_get_tool_preset (GimpContext *context)
+{
+ g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL);
+
+ return context->tool_preset;
+}
+
+void
+gimp_context_set_tool_preset (GimpContext *context,
+ GimpToolPreset *tool_preset)
+{
+ g_return_if_fail (GIMP_IS_CONTEXT (context));
+ g_return_if_fail (tool_preset == NULL || GIMP_IS_TOOL_PRESET (tool_preset));
+
+ context_find_defined (context, GIMP_CONTEXT_PROP_TOOL_PRESET);
+
+ gimp_context_real_set_tool_preset (context, tool_preset);
+}
+
+void
+gimp_context_tool_preset_changed (GimpContext *context)
+{
+ g_return_if_fail (GIMP_IS_CONTEXT (context));
+
+ g_signal_emit (context,
+ gimp_context_signals[TOOL_PRESET_CHANGED], 0,
+ context->tool_preset);
+}
+
+static void
+gimp_context_tool_preset_dirty (GimpToolPreset *tool_preset,
+ GimpContext *context)
+{
+ g_free (context->tool_preset_name);
+ context->tool_preset_name = g_strdup (gimp_object_get_name (tool_preset));
+
+ g_signal_emit (context, gimp_context_signals[PROP_NAME_CHANGED], 0,
+ GIMP_CONTEXT_PROP_TOOL_PRESET);
+}
+
+static void
+gimp_context_tool_preset_removed (GimpContainer *container,
+ GimpToolPreset *tool_preset,
+ GimpContext *context)
+{
+ if (tool_preset == context->tool_preset)
+ {
+ g_signal_handlers_disconnect_by_func (context->tool_preset,
+ gimp_context_tool_preset_dirty,
+ context);
+ g_clear_object (&context->tool_preset);
+
+ if (! gimp_container_frozen (container))
+ gimp_context_tool_preset_list_thaw (container, context);
+ }
+}
+
+static void
+gimp_context_tool_preset_list_thaw (GimpContainer *container,
+ GimpContext *context)
+{
+ GimpToolPreset *tool_preset;
+
+ tool_preset = gimp_context_find_object (context, container,
+ context->tool_preset_name,
+ NULL);
+
+ gimp_context_real_set_tool_preset (context, tool_preset);
+}
+
+static void
+gimp_context_real_set_tool_preset (GimpContext *context,
+ GimpToolPreset *tool_preset)
+{
+ if (context->tool_preset == tool_preset)
+ return;
+
+ if (context->tool_preset_name)
+ {
+ g_clear_pointer (&context->tool_preset_name, g_free);
+ }
+
+ if (context->tool_preset)
+ g_signal_handlers_disconnect_by_func (context->tool_preset,
+ gimp_context_tool_preset_dirty,
+ context);
+
+ g_set_object (&context->tool_preset, tool_preset);
+
+ if (tool_preset)
+ {
+ g_signal_connect_object (tool_preset, "name-changed",
+ G_CALLBACK (gimp_context_tool_preset_dirty),
+ context,
+ 0);
+
+ context->tool_preset_name = g_strdup (gimp_object_get_name (tool_preset));
+ }
+
+ g_object_notify (G_OBJECT (context), "tool-preset");
+ gimp_context_tool_preset_changed (context);
+}
+
+
+/*****************************************************************************/
+/* buffer ******************************************************************/
+
+GimpBuffer *
+gimp_context_get_buffer (GimpContext *context)
+{
+ g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL);
+
+ return context->buffer;
+}
+
+void
+gimp_context_set_buffer (GimpContext *context,
+ GimpBuffer *buffer)
+{
+ g_return_if_fail (GIMP_IS_CONTEXT (context));
+ g_return_if_fail (buffer == NULL || GIMP_IS_BUFFER (buffer));
+
+ context_find_defined (context, GIMP_CONTEXT_PROP_BUFFER);
+
+ gimp_context_real_set_buffer (context, buffer);
+}
+
+void
+gimp_context_buffer_changed (GimpContext *context)
+{
+ g_return_if_fail (GIMP_IS_CONTEXT (context));
+
+ g_signal_emit (context,
+ gimp_context_signals[BUFFER_CHANGED], 0,
+ context->buffer);
+}
+
+static void
+gimp_context_buffer_dirty (GimpBuffer *buffer,
+ GimpContext *context)
+{
+ g_free (context->buffer_name);
+ context->buffer_name = g_strdup (gimp_object_get_name (buffer));
+
+ g_signal_emit (context, gimp_context_signals[PROP_NAME_CHANGED], 0,
+ GIMP_CONTEXT_PROP_BUFFER);
+}
+
+static void
+gimp_context_buffer_list_thaw (GimpContainer *container,
+ GimpContext *context)
+{
+ GimpBuffer *buffer;
+
+ buffer = gimp_context_find_object (context, container,
+ context->buffer_name,
+ NULL);
+
+ if (buffer)
+ {
+ gimp_context_real_set_buffer (context, buffer);
+ }
+ else
+ {
+ g_object_notify (G_OBJECT (context), "buffer");
+ gimp_context_buffer_changed (context);
+ }
+}
+
+static void
+gimp_context_buffer_removed (GimpContainer *container,
+ GimpBuffer *buffer,
+ GimpContext *context)
+{
+ if (buffer == context->buffer)
+ {
+ g_signal_handlers_disconnect_by_func (context->buffer,
+ gimp_context_buffer_dirty,
+ context);
+ g_clear_object (&context->buffer);
+
+ if (! gimp_container_frozen (container))
+ gimp_context_buffer_list_thaw (container, context);
+ }
+}
+
+static void
+gimp_context_real_set_buffer (GimpContext *context,
+ GimpBuffer *buffer)
+{
+ if (context->buffer == buffer)
+ return;
+
+ if (context->buffer_name)
+ {
+ g_clear_pointer (&context->buffer_name, g_free);
+ }
+
+ if (context->buffer)
+ g_signal_handlers_disconnect_by_func (context->buffer,
+ gimp_context_buffer_dirty,
+ context);
+
+ g_set_object (&context->buffer, buffer);
+
+ if (buffer)
+ {
+ g_signal_connect_object (buffer, "name-changed",
+ G_CALLBACK (gimp_context_buffer_dirty),
+ context,
+ 0);
+
+ context->buffer_name = g_strdup (gimp_object_get_name (buffer));
+ }
+
+ g_object_notify (G_OBJECT (context), "buffer");
+ gimp_context_buffer_changed (context);
+}
+
+
+/*****************************************************************************/
+/* imagefile ***************************************************************/
+
+GimpImagefile *
+gimp_context_get_imagefile (GimpContext *context)
+{
+ g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL);
+
+ return context->imagefile;
+}
+
+void
+gimp_context_set_imagefile (GimpContext *context,
+ GimpImagefile *imagefile)
+{
+ g_return_if_fail (GIMP_IS_CONTEXT (context));
+ g_return_if_fail (imagefile == NULL || GIMP_IS_IMAGEFILE (imagefile));
+
+ context_find_defined (context, GIMP_CONTEXT_PROP_IMAGEFILE);
+
+ gimp_context_real_set_imagefile (context, imagefile);
+}
+
+void
+gimp_context_imagefile_changed (GimpContext *context)
+{
+ g_return_if_fail (GIMP_IS_CONTEXT (context));
+
+ g_signal_emit (context,
+ gimp_context_signals[IMAGEFILE_CHANGED], 0,
+ context->imagefile);
+}
+
+static void
+gimp_context_imagefile_dirty (GimpImagefile *imagefile,
+ GimpContext *context)
+{
+ g_free (context->imagefile_name);
+ context->imagefile_name = g_strdup (gimp_object_get_name (imagefile));
+
+ g_signal_emit (context, gimp_context_signals[PROP_NAME_CHANGED], 0,
+ GIMP_CONTEXT_PROP_IMAGEFILE);
+}
+
+static void
+gimp_context_imagefile_list_thaw (GimpContainer *container,
+ GimpContext *context)
+{
+ GimpImagefile *imagefile;
+
+ imagefile = gimp_context_find_object (context, container,
+ context->imagefile_name,
+ NULL);
+
+ if (imagefile)
+ {
+ gimp_context_real_set_imagefile (context, imagefile);
+ }
+ else
+ {
+ g_object_notify (G_OBJECT (context), "imagefile");
+ gimp_context_imagefile_changed (context);
+ }
+}
+
+static void
+gimp_context_imagefile_removed (GimpContainer *container,
+ GimpImagefile *imagefile,
+ GimpContext *context)
+{
+ if (imagefile == context->imagefile)
+ {
+ g_signal_handlers_disconnect_by_func (context->imagefile,
+ gimp_context_imagefile_dirty,
+ context);
+ g_clear_object (&context->imagefile);
+
+ if (! gimp_container_frozen (container))
+ gimp_context_imagefile_list_thaw (container, context);
+ }
+}
+
+static void
+gimp_context_real_set_imagefile (GimpContext *context,
+ GimpImagefile *imagefile)
+{
+ if (context->imagefile == imagefile)
+ return;
+
+ if (context->imagefile_name)
+ {
+ g_clear_pointer (&context->imagefile_name, g_free);
+ }
+
+ if (context->imagefile)
+ g_signal_handlers_disconnect_by_func (context->imagefile,
+ gimp_context_imagefile_dirty,
+ context);
+
+ g_set_object (&context->imagefile, imagefile);
+
+ if (imagefile)
+ {
+ g_signal_connect_object (imagefile, "name-changed",
+ G_CALLBACK (gimp_context_imagefile_dirty),
+ context,
+ 0);
+
+ context->imagefile_name = g_strdup (gimp_object_get_name (imagefile));
+ }
+
+ g_object_notify (G_OBJECT (context), "imagefile");
+ gimp_context_imagefile_changed (context);
+}
+
+
+/*****************************************************************************/
+/* template ***************************************************************/
+
+GimpTemplate *
+gimp_context_get_template (GimpContext *context)
+{
+ g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL);
+
+ return context->template;
+}
+
+void
+gimp_context_set_template (GimpContext *context,
+ GimpTemplate *template)
+{
+ g_return_if_fail (GIMP_IS_CONTEXT (context));
+ g_return_if_fail (template == NULL || GIMP_IS_TEMPLATE (template));
+
+ context_find_defined (context, GIMP_CONTEXT_PROP_TEMPLATE);
+
+ gimp_context_real_set_template (context, template);
+}
+
+void
+gimp_context_template_changed (GimpContext *context)
+{
+ g_return_if_fail (GIMP_IS_CONTEXT (context));
+
+ g_signal_emit (context,
+ gimp_context_signals[TEMPLATE_CHANGED], 0,
+ context->template);
+}
+
+static void
+gimp_context_template_dirty (GimpTemplate *template,
+ GimpContext *context)
+{
+ g_free (context->template_name);
+ context->template_name = g_strdup (gimp_object_get_name (template));
+
+ g_signal_emit (context, gimp_context_signals[PROP_NAME_CHANGED], 0,
+ GIMP_CONTEXT_PROP_TEMPLATE);
+}
+
+static void
+gimp_context_template_list_thaw (GimpContainer *container,
+ GimpContext *context)
+{
+ GimpTemplate *template;
+
+ template = gimp_context_find_object (context, container,
+ context->template_name,
+ NULL);
+
+ if (template)
+ {
+ gimp_context_real_set_template (context, template);
+ }
+ else
+ {
+ g_object_notify (G_OBJECT (context), "template");
+ gimp_context_template_changed (context);
+ }
+}
+
+static void
+gimp_context_template_removed (GimpContainer *container,
+ GimpTemplate *template,
+ GimpContext *context)
+{
+ if (template == context->template)
+ {
+ g_signal_handlers_disconnect_by_func (context->template,
+ gimp_context_template_dirty,
+ context);
+ g_clear_object (&context->template);
+
+ if (! gimp_container_frozen (container))
+ gimp_context_template_list_thaw (container, context);
+ }
+}
+
+static void
+gimp_context_real_set_template (GimpContext *context,
+ GimpTemplate *template)
+{
+ if (context->template == template)
+ return;
+
+ if (context->template_name)
+ {
+ g_clear_pointer (&context->template_name, g_free);
+ }
+
+ if (context->template)
+ g_signal_handlers_disconnect_by_func (context->template,
+ gimp_context_template_dirty,
+ context);
+
+ g_set_object (&context->template, template);
+
+ if (template)
+ {
+ g_signal_connect_object (template, "name-changed",
+ G_CALLBACK (gimp_context_template_dirty),
+ context,
+ 0);
+
+ context->template_name = g_strdup (gimp_object_get_name (template));
+ }
+
+ g_object_notify (G_OBJECT (context), "template");
+ gimp_context_template_changed (context);
+}
+
+
+/*****************************************************************************/
+/* utility functions *******************************************************/
+
+static gpointer
+gimp_context_find_object (GimpContext *context,
+ GimpContainer *container,
+ const gchar *object_name,
+ gpointer standard_object)
+{
+ GimpObject *object = NULL;
+
+ if (object_name)
+ object = gimp_container_get_child_by_name (container, object_name);
+
+ if (! object && ! gimp_container_is_empty (container))
+ object = gimp_container_get_child_by_index (container, 0);
+
+ if (! object)
+ object = standard_object;
+
+ return object;
+}
diff --git a/app/core/gimpcontext.h b/app/core/gimpcontext.h
new file mode 100644
index 0000000..7f15a4a
--- /dev/null
+++ b/app/core/gimpcontext.h
@@ -0,0 +1,360 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpcontext.h
+ * Copyright (C) 1999-2010 Michael Natterer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_CONTEXT_H__
+#define __GIMP_CONTEXT_H__
+
+
+#include "gimpviewable.h"
+
+
+#define GIMP_TYPE_CONTEXT (gimp_context_get_type ())
+#define GIMP_CONTEXT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CONTEXT, GimpContext))
+#define GIMP_CONTEXT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST (klass, GIMP_TYPE_CONTEXT, GimpContextClass))
+#define GIMP_IS_CONTEXT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CONTEXT))
+#define GIMP_IS_CONTEXT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CONTEXT))
+#define GIMP_CONTEXT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS (obj, GIMP_TYPE_CONTEXT, GimpContextClass))
+
+
+typedef struct _GimpContextClass GimpContextClass;
+
+/**
+ * GimpContext:
+ *
+ * Holds state such as the active image, active display, active brush,
+ * active foreground and background color, and so on. There can many
+ * instances of contexts. The user context is what the user sees and
+ * interacts with but there can also be contexts for docks and for
+ * plug-ins.
+ */
+struct _GimpContext
+{
+ GimpViewable parent_instance;
+
+ Gimp *gimp;
+
+ GimpContext *parent;
+
+ guint32 defined_props;
+ guint32 serialize_props;
+
+ GimpImage *image;
+ gpointer display;
+
+ GimpToolInfo *tool_info;
+ gchar *tool_name;
+
+ GimpPaintInfo *paint_info;
+ gchar *paint_name;
+
+ GimpRGB foreground;
+ GimpRGB background;
+
+ gdouble opacity;
+ GimpLayerMode paint_mode;
+
+ GimpBrush *brush;
+ gchar *brush_name;
+
+ GimpDynamics *dynamics;
+ gchar *dynamics_name;
+
+ GimpMybrush *mybrush;
+ gchar *mybrush_name;
+
+ GimpPattern *pattern;
+ gchar *pattern_name;
+
+ GimpGradient *gradient;
+ gchar *gradient_name;
+
+ GimpPalette *palette;
+ gchar *palette_name;
+
+ GimpFont *font;
+ gchar *font_name;
+
+ GimpToolPreset *tool_preset;
+ gchar *tool_preset_name;
+
+ GimpBuffer *buffer;
+ gchar *buffer_name;
+
+ GimpImagefile *imagefile;
+ gchar *imagefile_name;
+
+ GimpTemplate *template;
+ gchar *template_name;
+};
+
+struct _GimpContextClass
+{
+ GimpViewableClass parent_class;
+
+ void (* image_changed) (GimpContext *context,
+ GimpImage *image);
+ void (* display_changed) (GimpContext *context,
+ gpointer display);
+
+ void (* tool_changed) (GimpContext *context,
+ GimpToolInfo *tool_info);
+ void (* paint_info_changed) (GimpContext *context,
+ GimpPaintInfo *paint_info);
+
+ void (* foreground_changed) (GimpContext *context,
+ GimpRGB *color);
+ void (* background_changed) (GimpContext *context,
+ GimpRGB *color);
+ void (* opacity_changed) (GimpContext *context,
+ gdouble opacity);
+ void (* paint_mode_changed) (GimpContext *context,
+ GimpLayerMode paint_mode);
+ void (* brush_changed) (GimpContext *context,
+ GimpBrush *brush);
+ void (* dynamics_changed) (GimpContext *context,
+ GimpDynamics *dynamics);
+ void (* mybrush_changed) (GimpContext *context,
+ GimpMybrush *brush);
+ void (* pattern_changed) (GimpContext *context,
+ GimpPattern *pattern);
+ void (* gradient_changed) (GimpContext *context,
+ GimpGradient *gradient);
+ void (* palette_changed) (GimpContext *context,
+ GimpPalette *palette);
+ void (* font_changed) (GimpContext *context,
+ GimpFont *font);
+ void (* tool_preset_changed)(GimpContext *context,
+ GimpToolPreset *tool_preset);
+ void (* buffer_changed) (GimpContext *context,
+ GimpBuffer *buffer);
+ void (* imagefile_changed) (GimpContext *context,
+ GimpImagefile *imagefile);
+ void (* template_changed) (GimpContext *context,
+ GimpTemplate *template);
+
+ void (* prop_name_changed) (GimpContext *context,
+ GimpContextPropType prop);
+};
+
+
+GType gimp_context_get_type (void) G_GNUC_CONST;
+
+GimpContext * gimp_context_new (Gimp *gimp,
+ const gchar *name,
+ GimpContext *template);
+
+GimpContext * gimp_context_get_parent (GimpContext *context);
+void gimp_context_set_parent (GimpContext *context,
+ GimpContext *parent);
+
+/* define / undefinine context properties
+ *
+ * the value of an undefined property will be taken from the parent context.
+ */
+void gimp_context_define_property (GimpContext *context,
+ GimpContextPropType prop,
+ gboolean defined);
+
+gboolean gimp_context_property_defined (GimpContext *context,
+ GimpContextPropType prop);
+
+void gimp_context_define_properties (GimpContext *context,
+ GimpContextPropMask props_mask,
+ gboolean defined);
+
+
+/* specify which context properties will be serialized
+ */
+void gimp_context_set_serialize_properties (GimpContext *context,
+ GimpContextPropMask props_mask);
+
+GimpContextPropMask
+ gimp_context_get_serialize_properties (GimpContext *context);
+
+
+/* copying context properties
+ */
+void gimp_context_copy_property (GimpContext *src,
+ GimpContext *dest,
+ GimpContextPropType prop);
+
+void gimp_context_copy_properties (GimpContext *src,
+ GimpContext *dest,
+ GimpContextPropMask props_mask);
+
+
+/* manipulate by GType */
+GimpContextPropType gimp_context_type_to_property (GType type);
+const gchar * gimp_context_type_to_prop_name (GType type);
+const gchar * gimp_context_type_to_signal_name (GType type);
+
+GimpObject * gimp_context_get_by_type (GimpContext *context,
+ GType type);
+void gimp_context_set_by_type (GimpContext *context,
+ GType type,
+ GimpObject *object);
+void gimp_context_changed_by_type (GimpContext *context,
+ GType type);
+
+
+/* image */
+GimpImage * gimp_context_get_image (GimpContext *context);
+void gimp_context_set_image (GimpContext *context,
+ GimpImage *image);
+void gimp_context_image_changed (GimpContext *context);
+
+
+/* display */
+gpointer gimp_context_get_display (GimpContext *context);
+void gimp_context_set_display (GimpContext *context,
+ gpointer display);
+void gimp_context_display_changed (GimpContext *context);
+
+
+/* tool */
+GimpToolInfo * gimp_context_get_tool (GimpContext *context);
+void gimp_context_set_tool (GimpContext *context,
+ GimpToolInfo *tool_info);
+void gimp_context_tool_changed (GimpContext *context);
+
+
+/* paint info */
+GimpPaintInfo * gimp_context_get_paint_info (GimpContext *context);
+void gimp_context_set_paint_info (GimpContext *context,
+ GimpPaintInfo *paint_info);
+void gimp_context_paint_info_changed (GimpContext *context);
+
+
+/* foreground color */
+void gimp_context_get_foreground (GimpContext *context,
+ GimpRGB *color);
+void gimp_context_set_foreground (GimpContext *context,
+ const GimpRGB *color);
+void gimp_context_foreground_changed (GimpContext *context);
+
+
+/* background color */
+void gimp_context_get_background (GimpContext *context,
+ GimpRGB *color);
+void gimp_context_set_background (GimpContext *context,
+ const GimpRGB *color);
+void gimp_context_background_changed (GimpContext *context);
+
+
+/* color utility functions */
+void gimp_context_set_default_colors (GimpContext *context);
+void gimp_context_swap_colors (GimpContext *context);
+
+
+/* opacity */
+gdouble gimp_context_get_opacity (GimpContext *context);
+void gimp_context_set_opacity (GimpContext *context,
+ gdouble opacity);
+void gimp_context_opacity_changed (GimpContext *context);
+
+
+/* paint mode */
+GimpLayerMode gimp_context_get_paint_mode (GimpContext *context);
+void gimp_context_set_paint_mode (GimpContext *context,
+ GimpLayerMode paint_mode);
+void gimp_context_paint_mode_changed (GimpContext *context);
+
+
+/* brush */
+GimpBrush * gimp_context_get_brush (GimpContext *context);
+void gimp_context_set_brush (GimpContext *context,
+ GimpBrush *brush);
+void gimp_context_brush_changed (GimpContext *context);
+
+
+/* dynamics */
+GimpDynamics * gimp_context_get_dynamics (GimpContext *context);
+void gimp_context_set_dynamics (GimpContext *context,
+ GimpDynamics *dynamics);
+void gimp_context_dynamics_changed (GimpContext *context);
+
+
+/* mybrush */
+GimpMybrush * gimp_context_get_mybrush (GimpContext *context);
+void gimp_context_set_mybrush (GimpContext *context,
+ GimpMybrush *brush);
+void gimp_context_mybrush_changed (GimpContext *context);
+
+
+/* pattern */
+GimpPattern * gimp_context_get_pattern (GimpContext *context);
+void gimp_context_set_pattern (GimpContext *context,
+ GimpPattern *pattern);
+void gimp_context_pattern_changed (GimpContext *context);
+
+
+/* gradient */
+GimpGradient * gimp_context_get_gradient (GimpContext *context);
+void gimp_context_set_gradient (GimpContext *context,
+ GimpGradient *gradient);
+void gimp_context_gradient_changed (GimpContext *context);
+
+
+/* palette */
+GimpPalette * gimp_context_get_palette (GimpContext *context);
+void gimp_context_set_palette (GimpContext *context,
+ GimpPalette *palette);
+void gimp_context_palette_changed (GimpContext *context);
+
+
+/* font */
+GimpFont * gimp_context_get_font (GimpContext *context);
+void gimp_context_set_font (GimpContext *context,
+ GimpFont *font);
+const gchar * gimp_context_get_font_name (GimpContext *context);
+void gimp_context_set_font_name (GimpContext *context,
+ const gchar *name);
+void gimp_context_font_changed (GimpContext *context);
+
+
+/* tool_preset */
+GimpToolPreset * gimp_context_get_tool_preset (GimpContext *context);
+void gimp_context_set_tool_preset (GimpContext *context,
+ GimpToolPreset *tool_preset);
+void gimp_context_tool_preset_changed (GimpContext *context);
+
+
+/* buffer */
+GimpBuffer * gimp_context_get_buffer (GimpContext *context);
+void gimp_context_set_buffer (GimpContext *context,
+ GimpBuffer *palette);
+void gimp_context_buffer_changed (GimpContext *context);
+
+
+/* imagefile */
+GimpImagefile * gimp_context_get_imagefile (GimpContext *context);
+void gimp_context_set_imagefile (GimpContext *context,
+ GimpImagefile *imagefile);
+void gimp_context_imagefile_changed (GimpContext *context);
+
+
+/* template */
+GimpTemplate * gimp_context_get_template (GimpContext *context);
+void gimp_context_set_template (GimpContext *context,
+ GimpTemplate *template);
+void gimp_context_template_changed (GimpContext *context);
+
+
+#endif /* __GIMP_CONTEXT_H__ */
diff --git a/app/core/gimpcoords-interpolate.c b/app/core/gimpcoords-interpolate.c
new file mode 100644
index 0000000..f667e57
--- /dev/null
+++ b/app/core/gimpcoords-interpolate.c
@@ -0,0 +1,371 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpcoords-interpolate.c
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <glib-object.h>
+
+#include "libgimpmath/gimpmath.h"
+
+#include "core-types.h"
+
+#include "gimpcoords.h"
+#include "gimpcoords-interpolate.h"
+
+
+/* Local helper functions declarations*/
+static void gimp_coords_interpolate_bezier_internal (const GimpCoords bezier_pt[4],
+ const gdouble start_t,
+ const gdouble end_t,
+ const gdouble precision,
+ GArray *ret_coords,
+ GArray *ret_params,
+ gint depth);
+static gdouble gimp_coords_get_catmull_spline_point (const gdouble t,
+ const gdouble p0,
+ const gdouble p1,
+ const gdouble p2,
+ const gdouble p3);
+
+/* Functions for bezier subdivision */
+
+void
+gimp_coords_interpolate_bezier (const GimpCoords bezier_pt[4],
+ const gdouble precision,
+ GArray *ret_coords,
+ GArray *ret_params)
+{
+ g_return_if_fail (bezier_pt != NULL);
+ g_return_if_fail (precision > 0.0);
+ g_return_if_fail (ret_coords != NULL);
+
+ gimp_coords_interpolate_bezier_internal (bezier_pt,
+ 0.0, 1.0,
+ precision,
+ ret_coords, ret_params, 10);
+}
+
+/* Recursive subdivision helper function */
+static void
+gimp_coords_interpolate_bezier_internal (const GimpCoords bezier_pt[4],
+ const gdouble start_t,
+ const gdouble end_t,
+ const gdouble precision,
+ GArray *ret_coords,
+ GArray *ret_params,
+ gint depth)
+{
+ /*
+ * bezier_pt has to contain four GimpCoords with the four control points
+ * of the bezier segment. We subdivide it at the parameter 0.5.
+ */
+
+ GimpCoords subdivided[8];
+ gdouble middle_t = (start_t + end_t) / 2;
+
+ subdivided[0] = bezier_pt[0];
+ subdivided[6] = bezier_pt[3];
+
+ /* if (!depth) g_printerr ("Hit recursion depth limit!\n"); */
+
+ gimp_coords_average (&bezier_pt[0], &bezier_pt[1], &subdivided[1]);
+ gimp_coords_average (&bezier_pt[1], &bezier_pt[2], &subdivided[7]);
+ gimp_coords_average (&bezier_pt[2], &bezier_pt[3], &subdivided[5]);
+
+ gimp_coords_average (&subdivided[1], &subdivided[7], &subdivided[2]);
+ gimp_coords_average (&subdivided[7], &subdivided[5], &subdivided[4]);
+ gimp_coords_average (&subdivided[2], &subdivided[4], &subdivided[3]);
+
+ /*
+ * We now have the coordinates of the two bezier segments in
+ * subdivided [0-3] and subdivided [3-6]
+ */
+
+ /*
+ * Here we need to check, if we have sufficiently subdivided, i.e.
+ * if the stroke is sufficiently close to a straight line.
+ */
+
+ if (! depth ||
+ gimp_coords_bezier_is_straight (subdivided, precision)) /* 1st half */
+ {
+ g_array_append_vals (ret_coords, subdivided, 3);
+
+ if (ret_params)
+ {
+ gdouble params[3];
+
+ params[0] = start_t;
+ params[1] = (2 * start_t + middle_t) / 3;
+ params[2] = (start_t + 2 * middle_t) / 3;
+
+ g_array_append_vals (ret_params, params, 3);
+ }
+ }
+ else
+ {
+ gimp_coords_interpolate_bezier_internal (subdivided,
+ start_t, (start_t + end_t) / 2,
+ precision,
+ ret_coords, ret_params,
+ depth - 1);
+ }
+
+ if (! depth ||
+ gimp_coords_bezier_is_straight (subdivided + 3, precision)) /* 2nd half */
+ {
+ g_array_append_vals (ret_coords, subdivided + 3, 3);
+
+ if (ret_params)
+ {
+ gdouble params[3];
+
+ params[0] = middle_t;
+ params[1] = (2 * middle_t + end_t) / 3;
+ params[2] = (middle_t + 2 * end_t) / 3;
+
+ g_array_append_vals (ret_params, params, 3);
+ }
+ }
+ else
+ {
+ gimp_coords_interpolate_bezier_internal (subdivided + 3,
+ (start_t + end_t) / 2, end_t,
+ precision,
+ ret_coords, ret_params,
+ depth - 1);
+ }
+}
+
+
+/*
+ * Returns the position and/or velocity of a Bezier curve at time 't'.
+ */
+
+void
+gimp_coords_interpolate_bezier_at (const GimpCoords bezier_pt[4],
+ gdouble t,
+ GimpCoords *position,
+ GimpCoords *velocity)
+{
+ gdouble u = 1.0 - t;
+
+ g_return_if_fail (bezier_pt != NULL);
+
+ if (position)
+ {
+ GimpCoords a;
+ GimpCoords b;
+
+ gimp_coords_mix ( u * u * u, &bezier_pt[0],
+ 3.0 * u * u * t, &bezier_pt[1],
+ &a);
+ gimp_coords_mix (3.0 * u * t * t, &bezier_pt[2],
+ t * t * t, &bezier_pt[3],
+ &b);
+
+ gimp_coords_add (&a, &b, position);
+ }
+
+ if (velocity)
+ {
+ GimpCoords a;
+ GimpCoords b;
+
+ gimp_coords_mix (-3.0 * u * u, &bezier_pt[0],
+ 3.0 * (u - 2.0 * t) * u, &bezier_pt[1],
+ &a);
+ gimp_coords_mix (-3.0 * (t - 2.0 * u) * t, &bezier_pt[2],
+ 3.0 * t * t, &bezier_pt[3],
+ &b);
+
+ gimp_coords_add (&a, &b, velocity);
+ }
+}
+
+/*
+ * a helper function that determines if a bezier segment is "straight
+ * enough" to be approximated by a line.
+ *
+ * To be more exact, it also checks for the control points to be distributed
+ * evenly along the line. This makes it easier to reconstruct parameters for
+ * a given point along the segment.
+ *
+ * Needs four GimpCoords in an array.
+ */
+
+gboolean
+gimp_coords_bezier_is_straight (const GimpCoords bezier_pt[4],
+ gdouble precision)
+{
+ GimpCoords pt1, pt2;
+
+ g_return_val_if_fail (bezier_pt != NULL, FALSE);
+ g_return_val_if_fail (precision > 0.0, FALSE);
+
+ /* calculate the "ideal" positions for the control points */
+
+ gimp_coords_mix (2.0 / 3.0, &bezier_pt[0],
+ 1.0 / 3.0, &bezier_pt[3],
+ &pt1);
+ gimp_coords_mix (1.0 / 3.0, &bezier_pt[0],
+ 2.0 / 3.0, &bezier_pt[3],
+ &pt2);
+
+ /* calculate the deviation of the actual control points */
+
+ return (gimp_coords_manhattan_dist (&bezier_pt[1], &pt1) < precision &&
+ gimp_coords_manhattan_dist (&bezier_pt[2], &pt2) < precision);
+}
+
+
+/* Functions for catmull-rom interpolation */
+
+void
+gimp_coords_interpolate_catmull (const GimpCoords catmull_pt[4],
+ gdouble precision,
+ GArray *ret_coords,
+ GArray *ret_params)
+{
+ gdouble delta_x, delta_y;
+ gdouble distance;
+ gdouble dir_step;
+ gdouble delta_dir;
+ gint num_points;
+ gint n;
+
+ GimpCoords past_coords;
+ GimpCoords start_coords;
+ GimpCoords end_coords;
+ GimpCoords future_coords;
+
+ g_return_if_fail (catmull_pt != NULL);
+ g_return_if_fail (precision > 0.0);
+ g_return_if_fail (ret_coords != NULL);
+
+ delta_x = catmull_pt[2].x - catmull_pt[1].x;
+ delta_y = catmull_pt[2].y - catmull_pt[1].y;
+
+ /* Catmull-Rom interpolation requires 4 points.
+ * Two endpoints plus one more at each end.
+ */
+
+ past_coords = catmull_pt[0];
+ start_coords = catmull_pt[1];
+ end_coords = catmull_pt[2];
+ future_coords = catmull_pt[3];
+
+ distance = sqrt (SQR (delta_x) + SQR (delta_y));
+
+ num_points = distance / precision;
+
+ delta_dir = end_coords.direction - start_coords.direction;
+
+ if (delta_dir <= -0.5)
+ delta_dir += 1.0;
+ else if (delta_dir >= 0.5)
+ delta_dir -= 1.0;
+
+ dir_step = delta_dir / num_points;
+
+ for (n = 1; n <= num_points; n++)
+ {
+ GimpCoords coords = past_coords; /* Make sure we carry over things
+ * we do not interpolate */
+ gdouble velocity;
+ gdouble pressure;
+ gdouble p = (gdouble) n / num_points;
+
+ coords.x =
+ gimp_coords_get_catmull_spline_point (p,
+ past_coords.x,
+ start_coords.x,
+ end_coords.x,
+ future_coords.x);
+
+ coords.y =
+ gimp_coords_get_catmull_spline_point (p,
+ past_coords.y,
+ start_coords.y,
+ end_coords.y,
+ future_coords.y);
+
+ pressure =
+ gimp_coords_get_catmull_spline_point (p,
+ past_coords.pressure,
+ start_coords.pressure,
+ end_coords.pressure,
+ future_coords.pressure);
+ coords.pressure = CLAMP (pressure, 0.0, 1.0);
+
+ coords.xtilt =
+ gimp_coords_get_catmull_spline_point (p,
+ past_coords.xtilt,
+ start_coords.xtilt,
+ end_coords.xtilt,
+ future_coords.xtilt);
+ coords.ytilt =
+ gimp_coords_get_catmull_spline_point (p,
+ past_coords.ytilt,
+ start_coords.ytilt,
+ end_coords.ytilt,
+ future_coords.ytilt);
+
+ coords.wheel =
+ gimp_coords_get_catmull_spline_point (p,
+ past_coords.wheel,
+ start_coords.wheel,
+ end_coords.wheel,
+ future_coords.wheel);
+
+ velocity = gimp_coords_get_catmull_spline_point (p,
+ past_coords.velocity,
+ start_coords.velocity,
+ end_coords.velocity,
+ future_coords.velocity);
+ coords.velocity = CLAMP (velocity, 0.0, 1.0);
+
+ coords.direction = start_coords.direction + dir_step * n;
+
+ coords.direction = coords.direction - floor (coords.direction);
+
+ coords.xscale = end_coords.xscale;
+ coords.yscale = end_coords.yscale;
+ coords.angle = end_coords.angle;
+ coords.reflect = end_coords.reflect;
+
+ g_array_append_val (ret_coords, coords);
+
+ if (ret_params)
+ g_array_append_val (ret_params, p);
+ }
+}
+
+static gdouble
+gimp_coords_get_catmull_spline_point (const gdouble t,
+ const gdouble p0,
+ const gdouble p1,
+ const gdouble p2,
+ const gdouble p3)
+{
+ return ((((-t + 2.0) * t - 1.0) * t / 2.0) * p0 +
+ ((((3.0 * t - 5.0) * t) * t + 2.0) / 2.0) * p1 +
+ (((-3.0 * t + 4.0) * t + 1.0) * t / 2.0) * p2 +
+ (((t - 1) * t * t) / 2.0) * p3);
+}
diff --git a/app/core/gimpcoords-interpolate.h b/app/core/gimpcoords-interpolate.h
new file mode 100644
index 0000000..d729cd4
--- /dev/null
+++ b/app/core/gimpcoords-interpolate.h
@@ -0,0 +1,41 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpcoords-interpolate.h
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_COORDS_INTERPOLATE_H__
+#define __GIMP_COORDS_INTERPOLATE_H__
+
+void gimp_coords_interpolate_bezier (const GimpCoords bezier_pt[4],
+ gdouble precision,
+ GArray *ret_coords,
+ GArray *ret_params);
+
+void gimp_coords_interpolate_bezier_at (const GimpCoords bezier_pt[4],
+ gdouble t,
+ GimpCoords *position,
+ GimpCoords *velocity);
+
+gboolean gimp_coords_bezier_is_straight (const GimpCoords bezier_pt[4],
+ gdouble precision);
+
+void gimp_coords_interpolate_catmull (const GimpCoords catmull_pt[4],
+ gdouble precision,
+ GArray *ret_coords,
+ GArray *ret_params);
+
+#endif /* __GIMP_COORDS_INTERPOLATE_H__ */
diff --git a/app/core/gimpcoords.c b/app/core/gimpcoords.c
new file mode 100644
index 0000000..306f7f2
--- /dev/null
+++ b/app/core/gimpcoords.c
@@ -0,0 +1,248 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpcoords.c
+ * Copyright (C) 2002 Simon Budig <simon@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <glib-object.h>
+
+#include "libgimpmath/gimpmath.h"
+
+#include "core-types.h"
+
+#include "gimpcoords.h"
+
+
+#define INPUT_RESOLUTION 256
+
+
+/* amul * a + bmul * b = ret_val */
+
+void
+gimp_coords_mix (const gdouble amul,
+ const GimpCoords *a,
+ const gdouble bmul,
+ const GimpCoords *b,
+ GimpCoords *ret_val)
+{
+ if (b)
+ {
+ ret_val->x = amul * a->x + bmul * b->x;
+ ret_val->y = amul * a->y + bmul * b->y;
+ ret_val->pressure = amul * a->pressure + bmul * b->pressure;
+ ret_val->xtilt = amul * a->xtilt + bmul * b->xtilt;
+ ret_val->ytilt = amul * a->ytilt + bmul * b->ytilt;
+ ret_val->wheel = amul * a->wheel + bmul * b->wheel;
+ ret_val->velocity = amul * a->velocity + bmul * b->velocity;
+ ret_val->direction = amul * a->direction + bmul * b->direction;
+ ret_val->extended = b->extended || a->extended;
+ }
+ else
+ {
+ ret_val->x = amul * a->x;
+ ret_val->y = amul * a->y;
+ ret_val->pressure = amul * a->pressure;
+ ret_val->xtilt = amul * a->xtilt;
+ ret_val->ytilt = amul * a->ytilt;
+ ret_val->wheel = amul * a->wheel;
+ ret_val->velocity = amul * a->velocity;
+ ret_val->direction = amul * a->direction;
+ ret_val->extended = a->extended;
+ }
+}
+
+
+/* (a+b)/2 = ret_average */
+
+void
+gimp_coords_average (const GimpCoords *a,
+ const GimpCoords *b,
+ GimpCoords *ret_average)
+{
+ gimp_coords_mix (0.5, a, 0.5, b, ret_average);
+}
+
+
+/* a + b = ret_add */
+
+void
+gimp_coords_add (const GimpCoords *a,
+ const GimpCoords *b,
+ GimpCoords *ret_add)
+{
+ gimp_coords_mix (1.0, a, 1.0, b, ret_add);
+}
+
+
+/* a - b = ret_difference */
+
+void
+gimp_coords_difference (const GimpCoords *a,
+ const GimpCoords *b,
+ GimpCoords *ret_difference)
+{
+ gimp_coords_mix (1.0, a, -1.0, b, ret_difference);
+}
+
+
+/* a * f = ret_product */
+
+void
+gimp_coords_scale (const gdouble f,
+ const GimpCoords *a,
+ GimpCoords *ret_product)
+{
+ gimp_coords_mix (f, a, 0.0, NULL, ret_product);
+}
+
+
+/* local helper for measuring the scalarproduct of two gimpcoords. */
+
+gdouble
+gimp_coords_scalarprod (const GimpCoords *a,
+ const GimpCoords *b)
+{
+ return (a->x * b->x +
+ a->y * b->y +
+ a->pressure * b->pressure +
+ a->xtilt * b->xtilt +
+ a->ytilt * b->ytilt +
+ a->wheel * b->wheel +
+ a->velocity * b->velocity +
+ a->direction * b->direction);
+}
+
+
+/*
+ * The "length" of the gimpcoord.
+ * Applies a metric that increases the weight on the
+ * pressure/xtilt/ytilt/wheel to ensure proper interpolation
+ */
+
+gdouble
+gimp_coords_length_squared (const GimpCoords *a)
+{
+ GimpCoords upscaled_a;
+
+ upscaled_a.x = a->x;
+ upscaled_a.y = a->y;
+ upscaled_a.pressure = a->pressure * INPUT_RESOLUTION;
+ upscaled_a.xtilt = a->xtilt * INPUT_RESOLUTION;
+ upscaled_a.ytilt = a->ytilt * INPUT_RESOLUTION;
+ upscaled_a.wheel = a->wheel * INPUT_RESOLUTION;
+ upscaled_a.velocity = a->velocity * INPUT_RESOLUTION;
+ upscaled_a.direction = a->direction * INPUT_RESOLUTION;
+
+ return gimp_coords_scalarprod (&upscaled_a, &upscaled_a);
+}
+
+
+gdouble
+gimp_coords_length (const GimpCoords *a)
+{
+ return sqrt (gimp_coords_length_squared (a));
+}
+
+/*
+ * Distance via manhattan metric, an upper bound for the eucledian metric.
+ * used for e.g. bezier approximation
+ */
+
+gdouble
+gimp_coords_manhattan_dist (const GimpCoords *a,
+ const GimpCoords *b)
+{
+ gdouble dist = 0;
+
+ dist += ABS (a->pressure - b->pressure);
+ dist += ABS (a->xtilt - b->xtilt);
+ dist += ABS (a->ytilt - b->ytilt);
+ dist += ABS (a->wheel - b->wheel);
+ dist += ABS (a->velocity - b->velocity);
+ dist += ABS (a->direction - b->direction);
+
+ dist *= INPUT_RESOLUTION;
+
+ dist += ABS (a->x - b->x);
+ dist += ABS (a->y - b->y);
+
+ return dist;
+}
+
+gboolean
+gimp_coords_equal (const GimpCoords *a,
+ const GimpCoords *b)
+{
+ return (a->x == b->x &&
+ a->y == b->y &&
+ a->pressure == b->pressure &&
+ a->xtilt == b->xtilt &&
+ a->ytilt == b->ytilt &&
+ a->wheel == b->wheel &&
+ a->velocity == b->velocity &&
+ a->direction == b->direction);
+
+ /* Extended attribute was omitted from this comparison deliberately,
+ * it describes the events origin, not its value
+ */
+}
+
+/* helper for calculating direction of two gimpcoords. */
+
+gdouble
+gimp_coords_direction (const GimpCoords *a,
+ const GimpCoords *b)
+{
+ gdouble direction;
+ gdouble delta_x, delta_y;
+
+ delta_x = a->x - b->x;
+ delta_y = a->y - b->y;
+
+ if ((delta_x == 0) && (delta_y == 0))
+ {
+ direction = a->direction;
+ }
+ else if (delta_x == 0)
+ {
+ if (delta_y > 0)
+ direction = 0.25;
+ else
+ direction = 0.75;
+ }
+ else if (delta_y == 0)
+ {
+ if (delta_x < 0)
+ direction = 0.0;
+ else
+ direction = 0.5;
+ }
+ else
+ {
+ direction = atan ((- 1.0 * delta_y) / delta_x) / (2 * G_PI);
+
+ if (delta_x > 0.0)
+ direction = direction + 0.5;
+
+ if (direction < 0.0)
+ direction = direction + 1.0;
+ }
+
+ return direction;
+}
diff --git a/app/core/gimpcoords.h b/app/core/gimpcoords.h
new file mode 100644
index 0000000..38297f2
--- /dev/null
+++ b/app/core/gimpcoords.h
@@ -0,0 +1,57 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpcoords.h
+ * Copyright (C) 2002 Simon Budig <simon@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_COORDS_H__
+#define __GIMP_COORDS_H__
+
+
+void gimp_coords_mix (const gdouble amul,
+ const GimpCoords *a,
+ const gdouble bmul,
+ const GimpCoords *b,
+ GimpCoords *ret_val);
+void gimp_coords_average (const GimpCoords *a,
+ const GimpCoords *b,
+ GimpCoords *ret_average);
+void gimp_coords_add (const GimpCoords *a,
+ const GimpCoords *b,
+ GimpCoords *ret_add);
+void gimp_coords_difference (const GimpCoords *a,
+ const GimpCoords *b,
+ GimpCoords *difference);
+void gimp_coords_scale (const gdouble f,
+ const GimpCoords *a,
+ GimpCoords *ret_product);
+
+gdouble gimp_coords_scalarprod (const GimpCoords *a,
+ const GimpCoords *b);
+gdouble gimp_coords_length (const GimpCoords *a);
+gdouble gimp_coords_length_squared (const GimpCoords *a);
+gdouble gimp_coords_manhattan_dist (const GimpCoords *a,
+ const GimpCoords *b);
+
+gboolean gimp_coords_equal (const GimpCoords *a,
+ const GimpCoords *b);
+
+gdouble gimp_coords_direction (const GimpCoords *a,
+ const GimpCoords *b);
+
+
+#endif /* __GIMP_COORDS_H__ */
diff --git a/app/core/gimpcurve-load.c b/app/core/gimpcurve-load.c
new file mode 100644
index 0000000..9397d4a
--- /dev/null
+++ b/app/core/gimpcurve-load.c
@@ -0,0 +1,54 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpconfig/gimpconfig.h"
+
+#include "core-types.h"
+
+#include "gimpcurve.h"
+#include "gimpcurve-load.h"
+
+
+GList *
+gimp_curve_load (GFile *file,
+ GInputStream *input,
+ GError **error)
+{
+ GimpCurve *curve;
+
+ g_return_val_if_fail (G_IS_FILE (file), NULL);
+ g_return_val_if_fail (G_IS_INPUT_STREAM (input), NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ curve = g_object_new (GIMP_TYPE_CURVE, NULL);
+
+ if (gimp_config_deserialize_stream (GIMP_CONFIG (curve),
+ input,
+ NULL, error))
+ {
+ return g_list_prepend (NULL, curve);
+ }
+
+ g_object_unref (curve);
+
+ return NULL;
+}
diff --git a/app/core/gimpcurve-load.h b/app/core/gimpcurve-load.h
new file mode 100644
index 0000000..fc7ffa3
--- /dev/null
+++ b/app/core/gimpcurve-load.h
@@ -0,0 +1,30 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_CURVE_LOAD_H__
+#define __GIMP_CURVE_LOAD_H__
+
+
+#define GIMP_CURVE_FILE_EXTENSION ".curve"
+
+
+GList * gimp_curve_load (GFile *file,
+ GInputStream *input,
+ GError **error);
+
+
+#endif /* __GIMP_CURVE_LOAD_H__ */
diff --git a/app/core/gimpcurve-map.c b/app/core/gimpcurve-map.c
new file mode 100644
index 0000000..bd39b27
--- /dev/null
+++ b/app/core/gimpcurve-map.c
@@ -0,0 +1,253 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpmath/gimpmath.h"
+
+#include "core-types.h"
+
+#include "gimpcurve.h"
+#include "gimpcurve-map.h"
+
+
+#if defined (HAVE_ISFINITE)
+#define FINITE(x) isfinite(x)
+#elif defined (HAVE_FINITE)
+#define FINITE(x) finite(x)
+#elif defined (G_OS_WIN32)
+#define FINITE(x) _finite(x)
+#else
+#error "no FINITE() implementation available?!"
+#endif
+
+
+enum
+{
+ CURVE_NONE = 0,
+ CURVE_COLORS = 1 << 0,
+ CURVE_RED = 1 << 1,
+ CURVE_GREEN = 1 << 2,
+ CURVE_BLUE = 1 << 3,
+ CURVE_ALPHA = 1 << 4
+};
+
+static guint gimp_curve_get_apply_mask (GimpCurve *curve_colors,
+ GimpCurve *curve_red,
+ GimpCurve *curve_green,
+ GimpCurve *curve_blue,
+ GimpCurve *curve_alpha);
+static inline gdouble gimp_curve_map_value_inline (GimpCurve *curve,
+ gdouble value);
+
+
+gdouble
+gimp_curve_map_value (GimpCurve *curve,
+ gdouble value)
+{
+ g_return_val_if_fail (GIMP_IS_CURVE (curve), 0.0);
+
+ return gimp_curve_map_value_inline (curve, value);
+}
+
+void
+gimp_curve_map_pixels (GimpCurve *curve_colors,
+ GimpCurve *curve_red,
+ GimpCurve *curve_green,
+ GimpCurve *curve_blue,
+ GimpCurve *curve_alpha,
+ gfloat *src,
+ gfloat *dest,
+ glong samples)
+{
+ g_return_if_fail (GIMP_IS_CURVE (curve_colors));
+ g_return_if_fail (GIMP_IS_CURVE (curve_red));
+ g_return_if_fail (GIMP_IS_CURVE (curve_green));
+ g_return_if_fail (GIMP_IS_CURVE (curve_blue));
+ g_return_if_fail (GIMP_IS_CURVE (curve_alpha));
+
+ switch (gimp_curve_get_apply_mask (curve_colors,
+ curve_red,
+ curve_green,
+ curve_blue,
+ curve_alpha))
+ {
+ case CURVE_NONE:
+ memcpy (dest, src, samples * 4 * sizeof (gfloat));
+ break;
+
+ case CURVE_COLORS:
+ while (samples--)
+ {
+ dest[0] = gimp_curve_map_value_inline (curve_colors, src[0]);
+ dest[1] = gimp_curve_map_value_inline (curve_colors, src[1]);
+ dest[2] = gimp_curve_map_value_inline (curve_colors, src[2]);
+ /* don't apply the colors curve to the alpha channel */
+ dest[3] = src[3];
+
+ src += 4;
+ dest += 4;
+ }
+ break;
+
+ case CURVE_RED:
+ while (samples--)
+ {
+ dest[0] = gimp_curve_map_value_inline (curve_red, src[0]);
+ dest[1] = src[1];
+ dest[2] = src[2];
+ dest[3] = src[3];
+
+ src += 4;
+ dest += 4;
+ }
+ break;
+
+ case CURVE_GREEN:
+ while (samples--)
+ {
+ dest[0] = src[0];
+ dest[1] = gimp_curve_map_value_inline (curve_green, src[1]);
+ dest[2] = src[2];
+ dest[3] = src[3];
+
+ src += 4;
+ dest += 4;
+ }
+ break;
+
+ case CURVE_BLUE:
+ while (samples--)
+ {
+ dest[0] = src[0];
+ dest[1] = src[1];
+ dest[2] = gimp_curve_map_value_inline (curve_blue, src[2]);
+ dest[3] = src[3];
+
+ src += 4;
+ dest += 4;
+ }
+ break;
+
+ case CURVE_ALPHA:
+ while (samples--)
+ {
+ dest[0] = src[0];
+ dest[1] = src[1];
+ dest[2] = src[2];
+ dest[3] = gimp_curve_map_value_inline (curve_alpha, src[3]);
+
+ src += 4;
+ dest += 4;
+ }
+ break;
+
+ case (CURVE_RED | CURVE_GREEN | CURVE_BLUE):
+ while (samples--)
+ {
+ dest[0] = gimp_curve_map_value_inline (curve_red, src[0]);
+ dest[1] = gimp_curve_map_value_inline (curve_green, src[1]);
+ dest[2] = gimp_curve_map_value_inline (curve_blue, src[2]);
+ dest[3] = src[3];
+
+ src += 4;
+ dest += 4;
+ }
+ break;
+
+ default:
+ while (samples--)
+ {
+ dest[0] = gimp_curve_map_value_inline (curve_colors,
+ gimp_curve_map_value_inline (curve_red,
+ src[0]));
+ dest[1] = gimp_curve_map_value_inline (curve_colors,
+ gimp_curve_map_value_inline (curve_green,
+ src[1]));
+ dest[2] = gimp_curve_map_value_inline (curve_colors,
+ gimp_curve_map_value_inline (curve_blue,
+ src[2]));
+ /* don't apply the colors curve to the alpha channel */
+ dest[3] = gimp_curve_map_value_inline (curve_alpha, src[3]);
+
+ src += 4;
+ dest += 4;
+ }
+ break;
+ }
+}
+
+static guint
+gimp_curve_get_apply_mask (GimpCurve *curve_colors,
+ GimpCurve *curve_red,
+ GimpCurve *curve_green,
+ GimpCurve *curve_blue,
+ GimpCurve *curve_alpha)
+{
+ return ((gimp_curve_is_identity (curve_colors) ? 0 : CURVE_COLORS) |
+ (gimp_curve_is_identity (curve_red) ? 0 : CURVE_RED) |
+ (gimp_curve_is_identity (curve_green) ? 0 : CURVE_GREEN) |
+ (gimp_curve_is_identity (curve_blue) ? 0 : CURVE_BLUE) |
+ (gimp_curve_is_identity (curve_alpha) ? 0 : CURVE_ALPHA));
+}
+
+static inline gdouble
+gimp_curve_map_value_inline (GimpCurve *curve,
+ gdouble value)
+{
+ if (curve->identity)
+ {
+ if (FINITE (value))
+ return CLAMP (value, 0.0, 1.0);
+
+ return 0.0;
+ }
+
+ /* check for known values first, so broken values like NaN
+ * delivered by broken drivers don't run into the interpolation
+ * code
+ */
+ if (value > 0.0 && value < 1.0) /* interpolate the curve */
+ {
+ gdouble f;
+ gint index;
+
+ /* map value to the sample space */
+ value = value * (curve->n_samples - 1);
+
+ /* determine the indices of the closest sample points */
+ index = (gint) value;
+
+ /* calculate the position between the sample points */
+ f = value - index;
+
+ return (1.0 - f) * curve->samples[index] + f * curve->samples[index + 1];
+ }
+ else if (value >= 1.0)
+ {
+ return curve->samples[curve->n_samples - 1];
+ }
+ else
+ {
+ return curve->samples[0];
+ }
+}
diff --git a/app/core/gimpcurve-map.h b/app/core/gimpcurve-map.h
new file mode 100644
index 0000000..185e2fc
--- /dev/null
+++ b/app/core/gimpcurve-map.h
@@ -0,0 +1,34 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_CURVE_MAP_H__
+#define __GIMP_CURVE_MAP_H__
+
+
+gdouble gimp_curve_map_value (GimpCurve *curve,
+ gdouble value);
+void gimp_curve_map_pixels (GimpCurve *curve_colors,
+ GimpCurve *curve_red,
+ GimpCurve *curve_green,
+ GimpCurve *curve_blue,
+ GimpCurve *curve_alpha,
+ gfloat *src,
+ gfloat *dest,
+ glong samples);
+
+
+#endif /* __GIMP_CURVE_MAP_H__ */
diff --git a/app/core/gimpcurve-save.c b/app/core/gimpcurve-save.c
new file mode 100644
index 0000000..2791b13
--- /dev/null
+++ b/app/core/gimpcurve-save.c
@@ -0,0 +1,44 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpconfig/gimpconfig.h"
+
+#include "core-types.h"
+
+#include "gimpcurve.h"
+#include "gimpcurve-save.h"
+
+
+gboolean
+gimp_curve_save (GimpData *data,
+ GOutputStream *output,
+ GError **error)
+{
+ g_return_val_if_fail (GIMP_IS_CURVE (data), FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ return gimp_config_serialize_to_stream (GIMP_CONFIG (data),
+ output,
+ "GIMP curve file",
+ "end of GIMP curve file",
+ NULL, error);
+}
diff --git a/app/core/gimpcurve-save.h b/app/core/gimpcurve-save.h
new file mode 100644
index 0000000..98d504f
--- /dev/null
+++ b/app/core/gimpcurve-save.h
@@ -0,0 +1,28 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_CURVE_SAVE_H__
+#define __GIMP_CURVE_SAVE_H__
+
+
+/* don't call this function directly, use gimp_data_save() instead */
+gboolean gimp_curve_save (GimpData *data,
+ GOutputStream *output,
+ GError **error);
+
+
+#endif /* __GIMP_CURVE_SAVE_H__ */
diff --git a/app/core/gimpcurve.c b/app/core/gimpcurve.c
new file mode 100644
index 0000000..bde20b2
--- /dev/null
+++ b/app/core/gimpcurve.c
@@ -0,0 +1,1289 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <string.h> /* memcmp */
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpbase/gimpbase.h"
+#include "libgimpmath/gimpmath.h"
+#include "libgimpconfig/gimpconfig.h"
+
+#include "core-types.h"
+
+#include "gimpcurve.h"
+#include "gimpcurve-load.h"
+#include "gimpcurve-save.h"
+#include "gimpparamspecs.h"
+
+#include "gimp-intl.h"
+
+
+#define EPSILON 1e-6
+
+
+enum
+{
+ PROP_0,
+ PROP_CURVE_TYPE,
+ PROP_N_POINTS,
+ PROP_POINTS,
+ PROP_POINT_TYPES,
+ PROP_N_SAMPLES,
+ PROP_SAMPLES
+};
+
+
+/* local function prototypes */
+
+static void gimp_curve_config_iface_init (GimpConfigInterface *iface);
+
+static void gimp_curve_finalize (GObject *object);
+static void gimp_curve_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_curve_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static gint64 gimp_curve_get_memsize (GimpObject *object,
+ gint64 *gui_size);
+
+static void gimp_curve_get_preview_size (GimpViewable *viewable,
+ gint size,
+ gboolean popup,
+ gboolean dot_for_dot,
+ gint *width,
+ gint *height);
+static gboolean gimp_curve_get_popup_size (GimpViewable *viewable,
+ gint width,
+ gint height,
+ gboolean dot_for_dot,
+ gint *popup_width,
+ gint *popup_height);
+static GimpTempBuf * gimp_curve_get_new_preview (GimpViewable *viewable,
+ GimpContext *context,
+ gint width,
+ gint height);
+static gchar * gimp_curve_get_description (GimpViewable *viewable,
+ gchar **tooltip);
+
+static void gimp_curve_dirty (GimpData *data);
+static const gchar * gimp_curve_get_extension (GimpData *data);
+static void gimp_curve_data_copy (GimpData *data,
+ GimpData *src_data);
+
+static gboolean gimp_curve_serialize (GimpConfig *config,
+ GimpConfigWriter *writer,
+ gpointer data);
+static gboolean gimp_curve_deserialize (GimpConfig *config,
+ GScanner *scanner,
+ gint nest_level,
+ gpointer data);
+static gboolean gimp_curve_equal (GimpConfig *a,
+ GimpConfig *b);
+static void _gimp_curve_reset (GimpConfig *config);
+static gboolean gimp_curve_config_copy (GimpConfig *src,
+ GimpConfig *dest,
+ GParamFlags flags);
+
+static void gimp_curve_calculate (GimpCurve *curve);
+static void gimp_curve_plot (GimpCurve *curve,
+ gint p1,
+ gint p2,
+ gint p3,
+ gint p4);
+
+
+G_DEFINE_TYPE_WITH_CODE (GimpCurve, gimp_curve, GIMP_TYPE_DATA,
+ G_IMPLEMENT_INTERFACE (GIMP_TYPE_CONFIG,
+ gimp_curve_config_iface_init))
+
+#define parent_class gimp_curve_parent_class
+
+
+static void
+gimp_curve_class_init (GimpCurveClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpObjectClass *gimp_object_class = GIMP_OBJECT_CLASS (klass);
+ GimpViewableClass *viewable_class = GIMP_VIEWABLE_CLASS (klass);
+ GimpDataClass *data_class = GIMP_DATA_CLASS (klass);
+ GParamSpec *array_spec;
+
+ object_class->finalize = gimp_curve_finalize;
+ object_class->set_property = gimp_curve_set_property;
+ object_class->get_property = gimp_curve_get_property;
+
+ gimp_object_class->get_memsize = gimp_curve_get_memsize;
+
+ viewable_class->default_icon_name = "FIXME icon name";
+ viewable_class->get_preview_size = gimp_curve_get_preview_size;
+ viewable_class->get_popup_size = gimp_curve_get_popup_size;
+ viewable_class->get_new_preview = gimp_curve_get_new_preview;
+ viewable_class->get_description = gimp_curve_get_description;
+
+ data_class->dirty = gimp_curve_dirty;
+ data_class->save = gimp_curve_save;
+ data_class->get_extension = gimp_curve_get_extension;
+ data_class->copy = gimp_curve_data_copy;
+
+ GIMP_CONFIG_PROP_ENUM (object_class, PROP_CURVE_TYPE,
+ "curve-type",
+ "Curve Type",
+ "The curve type",
+ GIMP_TYPE_CURVE_TYPE,
+ GIMP_CURVE_SMOOTH, 0);
+
+ GIMP_CONFIG_PROP_INT (object_class, PROP_N_POINTS,
+ "n-points",
+ "Number of Points",
+ "The number of points",
+ 0, G_MAXINT, 0,
+ /* for backward compatibility */
+ GIMP_CONFIG_PARAM_IGNORE);
+
+ array_spec = g_param_spec_double ("point", NULL, NULL,
+ -1.0, 1.0, 0.0, GIMP_PARAM_READWRITE);
+ g_object_class_install_property (object_class, PROP_POINTS,
+ gimp_param_spec_value_array ("points",
+ NULL, NULL,
+ array_spec,
+ GIMP_PARAM_STATIC_STRINGS |
+ GIMP_CONFIG_PARAM_FLAGS));
+
+ array_spec = g_param_spec_enum ("point-type", NULL, NULL,
+ GIMP_TYPE_CURVE_POINT_TYPE,
+ GIMP_CURVE_POINT_SMOOTH,
+ GIMP_PARAM_READWRITE);
+ g_object_class_install_property (object_class, PROP_POINT_TYPES,
+ gimp_param_spec_value_array ("point-types",
+ NULL, NULL,
+ array_spec,
+ GIMP_PARAM_STATIC_STRINGS |
+ GIMP_CONFIG_PARAM_FLAGS));
+
+ GIMP_CONFIG_PROP_INT (object_class, PROP_N_SAMPLES,
+ "n-samples",
+ "Number of Samples",
+ "The number of samples",
+ 256, 256, 256, 0);
+
+ array_spec = g_param_spec_double ("sample", NULL, NULL,
+ 0.0, 1.0, 0.0, GIMP_PARAM_READWRITE);
+ g_object_class_install_property (object_class, PROP_SAMPLES,
+ gimp_param_spec_value_array ("samples",
+ NULL, NULL,
+ array_spec,
+ GIMP_PARAM_STATIC_STRINGS |
+ GIMP_CONFIG_PARAM_FLAGS));
+}
+
+static void
+gimp_curve_config_iface_init (GimpConfigInterface *iface)
+{
+ iface->serialize = gimp_curve_serialize;
+ iface->deserialize = gimp_curve_deserialize;
+ iface->equal = gimp_curve_equal;
+ iface->reset = _gimp_curve_reset;
+ iface->copy = gimp_curve_config_copy;
+}
+
+static void
+gimp_curve_init (GimpCurve *curve)
+{
+ curve->n_points = 0;
+ curve->points = NULL;
+ curve->n_samples = 0;
+ curve->samples = NULL;
+ curve->identity = FALSE;
+}
+
+static void
+gimp_curve_finalize (GObject *object)
+{
+ GimpCurve *curve = GIMP_CURVE (object);
+
+ g_clear_pointer (&curve->points, g_free);
+ curve->n_points = 0;
+
+ g_clear_pointer (&curve->samples, g_free);
+ curve->n_samples = 0;
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gimp_curve_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpCurve *curve = GIMP_CURVE (object);
+
+ switch (property_id)
+ {
+ case PROP_CURVE_TYPE:
+ gimp_curve_set_curve_type (curve, g_value_get_enum (value));
+ break;
+
+ case PROP_N_POINTS:
+ /* ignored */
+ break;
+
+ case PROP_POINTS:
+ {
+ GimpValueArray *array = g_value_get_boxed (value);
+ GimpCurvePoint *points;
+ gint length;
+ gint n_points;
+ gint i;
+
+ if (! array)
+ {
+ gimp_curve_clear_points (curve);
+
+ break;
+ }
+
+ length = gimp_value_array_length (array) / 2;
+
+ n_points = 0;
+ points = g_new0 (GimpCurvePoint, length);
+
+ for (i = 0; i < length; i++)
+ {
+ GValue *x = gimp_value_array_index (array, i * 2);
+ GValue *y = gimp_value_array_index (array, i * 2 + 1);
+
+ /* for backward compatibility */
+ if (g_value_get_double (x) < 0.0)
+ continue;
+
+ points[n_points].x = CLAMP (g_value_get_double (x), 0.0, 1.0);
+ points[n_points].y = CLAMP (g_value_get_double (y), 0.0, 1.0);
+
+ if (n_points > 0)
+ {
+ points[n_points].x = MAX (points[n_points].x,
+ points[n_points - 1].x);
+ }
+
+ if (n_points < curve->n_points)
+ points[n_points].type = curve->points[n_points].type;
+ else
+ points[n_points].type = GIMP_CURVE_POINT_SMOOTH;
+
+ n_points++;
+ }
+
+ g_free (curve->points);
+
+ curve->n_points = n_points;
+ curve->points = points;
+
+ g_object_notify (object, "n-points");
+ g_object_notify (object, "point-types");
+ }
+ break;
+
+ case PROP_POINT_TYPES:
+ {
+ GimpValueArray *array = g_value_get_boxed (value);
+ GimpCurvePoint *points;
+ gint length;
+ gdouble x = 0.0;
+ gdouble y = 0.0;
+ gint i;
+
+ if (! array)
+ {
+ gimp_curve_clear_points (curve);
+
+ break;
+ }
+
+ length = gimp_value_array_length (array);
+
+ points = g_new0 (GimpCurvePoint, length);
+
+ for (i = 0; i < length; i++)
+ {
+ GValue *type = gimp_value_array_index (array, i);
+
+ points[i].type = g_value_get_enum (type);
+
+ if (i < curve->n_points)
+ {
+ x = curve->points[i].x;
+ y = curve->points[i].y;
+ }
+
+ points[i].x = x;
+ points[i].y = y;
+ }
+
+ g_free (curve->points);
+
+ curve->n_points = length;
+ curve->points = points;
+
+ g_object_notify (object, "n-points");
+ g_object_notify (object, "points");
+ }
+ break;
+
+ case PROP_N_SAMPLES:
+ gimp_curve_set_n_samples (curve, g_value_get_int (value));
+ break;
+
+ case PROP_SAMPLES:
+ {
+ GimpValueArray *array = g_value_get_boxed (value);
+ gint length;
+ gint i;
+
+ if (! array)
+ break;
+
+ length = gimp_value_array_length (array);
+
+ for (i = 0; i < curve->n_samples && i < length; i++)
+ {
+ GValue *v = gimp_value_array_index (array, i);
+
+ curve->samples[i] = CLAMP (g_value_get_double (v), 0.0, 1.0);
+ }
+ }
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_curve_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpCurve *curve = GIMP_CURVE (object);
+
+ switch (property_id)
+ {
+ case PROP_CURVE_TYPE:
+ g_value_set_enum (value, curve->curve_type);
+ break;
+
+ case PROP_N_POINTS:
+ g_value_set_int (value, curve->n_points);
+ break;
+
+ case PROP_POINTS:
+ {
+ GimpValueArray *array = gimp_value_array_new (curve->n_points * 2);
+ GValue v = G_VALUE_INIT;
+ gint i;
+
+ g_value_init (&v, G_TYPE_DOUBLE);
+
+ for (i = 0; i < curve->n_points; i++)
+ {
+ g_value_set_double (&v, curve->points[i].x);
+ gimp_value_array_append (array, &v);
+
+ g_value_set_double (&v, curve->points[i].y);
+ gimp_value_array_append (array, &v);
+ }
+
+ g_value_unset (&v);
+
+ g_value_take_boxed (value, array);
+ }
+ break;
+
+ case PROP_POINT_TYPES:
+ {
+ GimpValueArray *array = gimp_value_array_new (curve->n_points);
+ GValue v = G_VALUE_INIT;
+ gint i;
+
+ g_value_init (&v, GIMP_TYPE_CURVE_POINT_TYPE);
+
+ for (i = 0; i < curve->n_points; i++)
+ {
+ g_value_set_enum (&v, curve->points[i].type);
+ gimp_value_array_append (array, &v);
+ }
+
+ g_value_unset (&v);
+
+ g_value_take_boxed (value, array);
+ }
+ break;
+
+ case PROP_N_SAMPLES:
+ g_value_set_int (value, curve->n_samples);
+ break;
+
+ case PROP_SAMPLES:
+ {
+ GimpValueArray *array = gimp_value_array_new (curve->n_samples);
+ GValue v = G_VALUE_INIT;
+ gint i;
+
+ g_value_init (&v, G_TYPE_DOUBLE);
+
+ for (i = 0; i < curve->n_samples; i++)
+ {
+ g_value_set_double (&v, curve->samples[i]);
+ gimp_value_array_append (array, &v);
+ }
+
+ g_value_unset (&v);
+
+ g_value_take_boxed (value, array);
+ }
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static gint64
+gimp_curve_get_memsize (GimpObject *object,
+ gint64 *gui_size)
+{
+ GimpCurve *curve = GIMP_CURVE (object);
+ gint64 memsize = 0;
+
+ memsize += curve->n_points * sizeof (GimpCurvePoint);
+ memsize += curve->n_samples * sizeof (gdouble);
+
+ return memsize + GIMP_OBJECT_CLASS (parent_class)->get_memsize (object,
+ gui_size);
+}
+
+static void
+gimp_curve_get_preview_size (GimpViewable *viewable,
+ gint size,
+ gboolean popup,
+ gboolean dot_for_dot,
+ gint *width,
+ gint *height)
+{
+ *width = size;
+ *height = size;
+}
+
+static gboolean
+gimp_curve_get_popup_size (GimpViewable *viewable,
+ gint width,
+ gint height,
+ gboolean dot_for_dot,
+ gint *popup_width,
+ gint *popup_height)
+{
+ *popup_width = width * 2;
+ *popup_height = height * 2;
+
+ return TRUE;
+}
+
+static GimpTempBuf *
+gimp_curve_get_new_preview (GimpViewable *viewable,
+ GimpContext *context,
+ gint width,
+ gint height)
+{
+ return NULL;
+}
+
+static gchar *
+gimp_curve_get_description (GimpViewable *viewable,
+ gchar **tooltip)
+{
+ GimpCurve *curve = GIMP_CURVE (viewable);
+
+ return g_strdup_printf ("%s", gimp_object_get_name (curve));
+}
+
+static void
+gimp_curve_dirty (GimpData *data)
+{
+ GimpCurve *curve = GIMP_CURVE (data);
+
+ curve->identity = FALSE;
+
+ gimp_curve_calculate (curve);
+
+ GIMP_DATA_CLASS (parent_class)->dirty (data);
+}
+
+static const gchar *
+gimp_curve_get_extension (GimpData *data)
+{
+ return GIMP_CURVE_FILE_EXTENSION;
+}
+
+static void
+gimp_curve_data_copy (GimpData *data,
+ GimpData *src_data)
+{
+ gimp_data_freeze (data);
+
+ gimp_config_copy (GIMP_CONFIG (src_data),
+ GIMP_CONFIG (data), 0);
+
+ gimp_data_thaw (data);
+}
+
+static gboolean
+gimp_curve_serialize (GimpConfig *config,
+ GimpConfigWriter *writer,
+ gpointer data)
+{
+ return gimp_config_serialize_properties (config, writer);
+}
+
+static gboolean
+gimp_curve_deserialize (GimpConfig *config,
+ GScanner *scanner,
+ gint nest_level,
+ gpointer data)
+{
+ gboolean success;
+
+ success = gimp_config_deserialize_properties (config, scanner, nest_level);
+
+ GIMP_CURVE (config)->identity = FALSE;
+
+ return success;
+}
+
+static gboolean
+gimp_curve_equal (GimpConfig *a,
+ GimpConfig *b)
+{
+ GimpCurve *a_curve = GIMP_CURVE (a);
+ GimpCurve *b_curve = GIMP_CURVE (b);
+
+ if (a_curve->curve_type != b_curve->curve_type)
+ return FALSE;
+
+ if (a_curve->n_points != b_curve->n_points ||
+ memcmp (a_curve->points, b_curve->points,
+ sizeof (GimpCurvePoint) * a_curve->n_points))
+ {
+ return FALSE;
+ }
+
+ if (a_curve->n_samples != b_curve->n_samples ||
+ memcmp (a_curve->samples, b_curve->samples,
+ sizeof (gdouble) * a_curve->n_samples))
+ {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void
+_gimp_curve_reset (GimpConfig *config)
+{
+ gimp_curve_reset (GIMP_CURVE (config), TRUE);
+}
+
+static gboolean
+gimp_curve_config_copy (GimpConfig *src,
+ GimpConfig *dest,
+ GParamFlags flags)
+{
+ GimpCurve *src_curve = GIMP_CURVE (src);
+ GimpCurve *dest_curve = GIMP_CURVE (dest);
+
+ /* make sure the curve type is copied *before* the points, so that we don't
+ * overwrite the copied points when changing the type
+ */
+ dest_curve->curve_type = src_curve->curve_type;
+
+ gimp_config_sync (G_OBJECT (src), G_OBJECT (dest), flags);
+
+ dest_curve->identity = src_curve->identity;
+
+ gimp_data_dirty (GIMP_DATA (dest));
+
+ return TRUE;
+}
+
+
+/* public functions */
+
+GimpData *
+gimp_curve_new (const gchar *name)
+{
+ g_return_val_if_fail (name != NULL, NULL);
+ g_return_val_if_fail (*name != '\0', NULL);
+
+ return g_object_new (GIMP_TYPE_CURVE,
+ "name", name,
+ NULL);
+}
+
+GimpData *
+gimp_curve_get_standard (void)
+{
+ static GimpData *standard_curve = NULL;
+
+ if (! standard_curve)
+ {
+ standard_curve = gimp_curve_new ("Standard");
+
+ gimp_data_clean (standard_curve);
+ gimp_data_make_internal (standard_curve,
+ "gimp-curve-standard");
+
+ g_object_ref (standard_curve);
+ }
+
+ return standard_curve;
+}
+
+void
+gimp_curve_reset (GimpCurve *curve,
+ gboolean reset_type)
+{
+ gint i;
+
+ g_return_if_fail (GIMP_IS_CURVE (curve));
+
+ g_object_freeze_notify (G_OBJECT (curve));
+
+ for (i = 0; i < curve->n_samples; i++)
+ curve->samples[i] = (gdouble) i / (gdouble) (curve->n_samples - 1);
+
+ g_object_notify (G_OBJECT (curve), "samples");
+
+ g_free (curve->points);
+
+ curve->n_points = 2;
+ curve->points = g_new0 (GimpCurvePoint, 2);
+
+ curve->points[0].x = 0.0;
+ curve->points[0].y = 0.0;
+ curve->points[0].type = GIMP_CURVE_POINT_SMOOTH;
+
+ curve->points[1].x = 1.0;
+ curve->points[1].y = 1.0;
+ curve->points[1].type = GIMP_CURVE_POINT_SMOOTH;
+
+ g_object_notify (G_OBJECT (curve), "n-points");
+ g_object_notify (G_OBJECT (curve), "points");
+ g_object_notify (G_OBJECT (curve), "point-types");
+
+ if (reset_type)
+ {
+ curve->curve_type = GIMP_CURVE_SMOOTH;
+ g_object_notify (G_OBJECT (curve), "curve-type");
+ }
+
+ curve->identity = TRUE;
+
+ g_object_thaw_notify (G_OBJECT (curve));
+
+ gimp_data_dirty (GIMP_DATA (curve));
+}
+
+void
+gimp_curve_set_curve_type (GimpCurve *curve,
+ GimpCurveType curve_type)
+{
+ g_return_if_fail (GIMP_IS_CURVE (curve));
+
+ if (curve->curve_type != curve_type)
+ {
+ gimp_data_freeze (GIMP_DATA (curve));
+
+ g_object_freeze_notify (G_OBJECT (curve));
+
+ curve->curve_type = curve_type;
+
+ if (curve_type == GIMP_CURVE_SMOOTH)
+ {
+ gint i;
+
+ g_free (curve->points);
+
+ /* pick some points from the curve and make them control
+ * points
+ */
+ curve->n_points = 9;
+ curve->points = g_new0 (GimpCurvePoint, 9);
+
+ for (i = 0; i < curve->n_points; i++)
+ {
+ gint sample = i * (curve->n_samples - 1) / (curve->n_points - 1);
+
+ curve->points[i].x = (gdouble) sample /
+ (gdouble) (curve->n_samples - 1);
+ curve->points[i].y = curve->samples[sample];
+ curve->points[i].type = GIMP_CURVE_POINT_SMOOTH;
+ }
+
+ g_object_notify (G_OBJECT (curve), "n-points");
+ g_object_notify (G_OBJECT (curve), "points");
+ g_object_notify (G_OBJECT (curve), "point-types");
+ }
+ else
+ {
+ gimp_curve_clear_points (curve);
+ }
+
+ g_object_notify (G_OBJECT (curve), "curve-type");
+
+ g_object_thaw_notify (G_OBJECT (curve));
+
+ gimp_data_thaw (GIMP_DATA (curve));
+ }
+}
+
+GimpCurveType
+gimp_curve_get_curve_type (GimpCurve *curve)
+{
+ g_return_val_if_fail (GIMP_IS_CURVE (curve), GIMP_CURVE_SMOOTH);
+
+ return curve->curve_type;
+}
+
+gint
+gimp_curve_get_n_points (GimpCurve *curve)
+{
+ g_return_val_if_fail (GIMP_IS_CURVE (curve), 0);
+
+ return curve->n_points;
+}
+
+void
+gimp_curve_set_n_samples (GimpCurve *curve,
+ gint n_samples)
+{
+ g_return_if_fail (GIMP_IS_CURVE (curve));
+ g_return_if_fail (n_samples >= 256);
+ g_return_if_fail (n_samples <= 4096);
+
+ if (n_samples != curve->n_samples)
+ {
+ gint i;
+
+ g_object_freeze_notify (G_OBJECT (curve));
+
+ curve->n_samples = n_samples;
+ g_object_notify (G_OBJECT (curve), "n-samples");
+
+ curve->samples = g_renew (gdouble, curve->samples, curve->n_samples);
+
+ for (i = 0; i < curve->n_samples; i++)
+ curve->samples[i] = (gdouble) i / (gdouble) (curve->n_samples - 1);
+
+ g_object_notify (G_OBJECT (curve), "samples");
+
+ if (curve->curve_type == GIMP_CURVE_FREE)
+ curve->identity = TRUE;
+
+ g_object_thaw_notify (G_OBJECT (curve));
+ }
+}
+
+gint
+gimp_curve_get_n_samples (GimpCurve *curve)
+{
+ g_return_val_if_fail (GIMP_IS_CURVE (curve), 0);
+
+ return curve->n_samples;
+}
+
+gint
+gimp_curve_get_point_at (GimpCurve *curve,
+ gdouble x)
+{
+ gint closest_point = -1;
+ gdouble distance = EPSILON;
+ gint i;
+
+ g_return_val_if_fail (GIMP_IS_CURVE (curve), -1);
+
+ for (i = 0; i < curve->n_points; i++)
+ {
+ gdouble point_distance;
+
+ point_distance = fabs (x - curve->points[i].x);
+
+ if (point_distance <= distance)
+ {
+ closest_point = i;
+ distance = point_distance;
+ }
+ }
+
+ return closest_point;
+}
+
+gint
+gimp_curve_get_closest_point (GimpCurve *curve,
+ gdouble x,
+ gdouble y,
+ gdouble max_distance)
+{
+ gint closest_point = -1;
+ gdouble distance2 = G_MAXDOUBLE;
+ gint i;
+
+ g_return_val_if_fail (GIMP_IS_CURVE (curve), -1);
+
+ if (max_distance >= 0.0)
+ distance2 = SQR (max_distance);
+
+ for (i = curve->n_points - 1; i >= 0; i--)
+ {
+ gdouble point_distance2;
+
+ point_distance2 = SQR (x - curve->points[i].x) +
+ SQR (y - curve->points[i].y);
+
+ if (point_distance2 <= distance2)
+ {
+ closest_point = i;
+ distance2 = point_distance2;
+ }
+ }
+
+ return closest_point;
+}
+
+gint
+gimp_curve_add_point (GimpCurve *curve,
+ gdouble x,
+ gdouble y)
+{
+ GimpCurvePoint *points;
+ gint point;
+
+ g_return_val_if_fail (GIMP_IS_CURVE (curve), -1);
+
+ if (curve->curve_type == GIMP_CURVE_FREE)
+ return -1;
+
+ x = CLAMP (x, 0.0, 1.0);
+ y = CLAMP (y, 0.0, 1.0);
+
+ for (point = 0; point < curve->n_points; point++)
+ {
+ if (curve->points[point].x > x)
+ break;
+ }
+
+ points = g_new0 (GimpCurvePoint, curve->n_points + 1);
+
+ memcpy (points, curve->points,
+ point * sizeof (GimpCurvePoint));
+ memcpy (points + point + 1, curve->points + point,
+ (curve->n_points - point) * sizeof (GimpCurvePoint));
+
+ points[point].x = x;
+ points[point].y = y;
+ points[point].type = GIMP_CURVE_POINT_SMOOTH;
+
+ g_free (curve->points);
+
+ curve->n_points++;
+ curve->points = points;
+
+ g_object_notify (G_OBJECT (curve), "n-points");
+ g_object_notify (G_OBJECT (curve), "points");
+ g_object_notify (G_OBJECT (curve), "point-types");
+
+ gimp_data_dirty (GIMP_DATA (curve));
+
+ return point;
+}
+
+void
+gimp_curve_delete_point (GimpCurve *curve,
+ gint point)
+{
+ GimpCurvePoint *points;
+
+ g_return_if_fail (GIMP_IS_CURVE (curve));
+ g_return_if_fail (point >= 0 && point < curve->n_points);
+
+ points = g_new0 (GimpCurvePoint, curve->n_points - 1);
+
+ memcpy (points, curve->points,
+ point * sizeof (GimpCurvePoint));
+ memcpy (points + point, curve->points + point + 1,
+ (curve->n_points - point - 1) * sizeof (GimpCurvePoint));
+
+ g_free (curve->points);
+
+ curve->n_points--;
+ curve->points = points;
+
+ g_object_notify (G_OBJECT (curve), "n-points");
+ g_object_notify (G_OBJECT (curve), "points");
+ g_object_notify (G_OBJECT (curve), "point-types");
+
+ gimp_data_dirty (GIMP_DATA (curve));
+}
+
+void
+gimp_curve_set_point (GimpCurve *curve,
+ gint point,
+ gdouble x,
+ gdouble y)
+{
+ g_return_if_fail (GIMP_IS_CURVE (curve));
+ g_return_if_fail (point >= 0 && point < curve->n_points);
+
+ curve->points[point].x = CLAMP (x, 0.0, 1.0);
+ curve->points[point].y = CLAMP (y, 0.0, 1.0);
+
+ if (point > 0)
+ curve->points[point].x = MAX (x, curve->points[point - 1].x);
+
+ if (point < curve->n_points - 1)
+ curve->points[point].x = MIN (x, curve->points[point + 1].x);
+
+ g_object_notify (G_OBJECT (curve), "points");
+
+ gimp_data_dirty (GIMP_DATA (curve));
+}
+
+void
+gimp_curve_move_point (GimpCurve *curve,
+ gint point,
+ gdouble y)
+{
+ g_return_if_fail (GIMP_IS_CURVE (curve));
+ g_return_if_fail (point >= 0 && point < curve->n_points);
+
+ curve->points[point].y = CLAMP (y, 0.0, 1.0);
+
+ g_object_notify (G_OBJECT (curve), "points");
+
+ gimp_data_dirty (GIMP_DATA (curve));
+}
+
+void
+gimp_curve_get_point (GimpCurve *curve,
+ gint point,
+ gdouble *x,
+ gdouble *y)
+{
+ g_return_if_fail (GIMP_IS_CURVE (curve));
+ g_return_if_fail (point >= 0 && point < curve->n_points);
+
+ if (x) *x = curve->points[point].x;
+ if (y) *y = curve->points[point].y;
+}
+
+void
+gimp_curve_set_point_type (GimpCurve *curve,
+ gint point,
+ GimpCurvePointType type)
+{
+ g_return_if_fail (GIMP_IS_CURVE (curve));
+ g_return_if_fail (point >= 0 && point < curve->n_points);
+
+ curve->points[point].type = type;
+
+ g_object_notify (G_OBJECT (curve), "point-types");
+
+ gimp_data_dirty (GIMP_DATA (curve));
+}
+
+GimpCurvePointType
+gimp_curve_get_point_type (GimpCurve *curve,
+ gint point)
+{
+ g_return_val_if_fail (GIMP_IS_CURVE (curve), GIMP_CURVE_POINT_SMOOTH);
+ g_return_val_if_fail (point >= 0 && point < curve->n_points, GIMP_CURVE_POINT_SMOOTH);
+
+ return curve->points[point].type;
+}
+
+void
+gimp_curve_clear_points (GimpCurve *curve)
+{
+ g_return_if_fail (GIMP_IS_CURVE (curve));
+
+ if (curve->points)
+ {
+ g_clear_pointer (&curve->points, g_free);
+ curve->n_points = 0;
+
+ g_object_notify (G_OBJECT (curve), "n-points");
+ g_object_notify (G_OBJECT (curve), "points");
+ g_object_notify (G_OBJECT (curve), "point-types");
+
+ gimp_data_dirty (GIMP_DATA (curve));
+ }
+}
+
+void
+gimp_curve_set_curve (GimpCurve *curve,
+ gdouble x,
+ gdouble y)
+{
+ g_return_if_fail (GIMP_IS_CURVE (curve));
+ g_return_if_fail (x >= 0 && x <= 1.0);
+ g_return_if_fail (y >= 0 && y <= 1.0);
+
+ if (curve->curve_type == GIMP_CURVE_SMOOTH)
+ return;
+
+ curve->samples[ROUND (x * (gdouble) (curve->n_samples - 1))] = y;
+
+ g_object_notify (G_OBJECT (curve), "samples");
+
+ gimp_data_dirty (GIMP_DATA (curve));
+}
+
+/**
+ * gimp_curve_is_identity:
+ * @curve: a #GimpCurve object
+ *
+ * If this function returns %TRUE, then the curve maps each value to
+ * itself. If it returns %FALSE, then this assumption can not be made.
+ *
+ * Return value: %TRUE if the curve is an identity mapping, %FALSE otherwise.
+ **/
+gboolean
+gimp_curve_is_identity (GimpCurve *curve)
+{
+ g_return_val_if_fail (GIMP_IS_CURVE (curve), FALSE);
+
+ return curve->identity;
+}
+
+void
+gimp_curve_get_uchar (GimpCurve *curve,
+ gint n_samples,
+ guchar *samples)
+{
+ gint i;
+
+ g_return_if_fail (GIMP_IS_CURVE (curve));
+ /* FIXME: support n_samples != curve->n_samples */
+ g_return_if_fail (n_samples == curve->n_samples);
+ g_return_if_fail (samples != NULL);
+
+ for (i = 0; i < curve->n_samples; i++)
+ samples[i] = curve->samples[i] * 255.999;
+}
+
+
+/* private functions */
+
+static void
+gimp_curve_calculate (GimpCurve *curve)
+{
+ gint i;
+ gint p1, p2, p3, p4;
+
+ if (gimp_data_is_frozen (GIMP_DATA (curve)))
+ return;
+
+ switch (curve->curve_type)
+ {
+ case GIMP_CURVE_SMOOTH:
+ /* Initialize boundary curve points */
+ if (curve->n_points > 0)
+ {
+ GimpCurvePoint point;
+ gint boundary;
+
+ point = curve->points[0];
+ boundary = ROUND (point.x * (gdouble) (curve->n_samples - 1));
+
+ for (i = 0; i < boundary; i++)
+ curve->samples[i] = point.y;
+
+ point = curve->points[curve->n_points - 1];
+ boundary = ROUND (point.x * (gdouble) (curve->n_samples - 1));
+
+ for (i = boundary; i < curve->n_samples; i++)
+ curve->samples[i] = point.y;
+ }
+
+ for (i = 0; i < curve->n_points - 1; i++)
+ {
+ p1 = MAX (i - 1, 0);
+ p2 = i;
+ p3 = i + 1;
+ p4 = MIN (i + 2, curve->n_points - 1);
+
+ if (curve->points[p2].type == GIMP_CURVE_POINT_CORNER)
+ p1 = p2;
+
+ if (curve->points[p3].type == GIMP_CURVE_POINT_CORNER)
+ p4 = p3;
+
+ gimp_curve_plot (curve, p1, p2, p3, p4);
+ }
+
+ /* ensure that the control points are used exactly */
+ for (i = 0; i < curve->n_points; i++)
+ {
+ gdouble x = curve->points[i].x;
+ gdouble y = curve->points[i].y;
+
+ curve->samples[ROUND (x * (gdouble) (curve->n_samples - 1))] = y;
+ }
+
+ g_object_notify (G_OBJECT (curve), "samples");
+ break;
+
+ case GIMP_CURVE_FREE:
+ break;
+ }
+}
+
+/*
+ * This function calculates the curve values between the control points
+ * p2 and p3, taking the potentially existing neighbors p1 and p4 into
+ * account.
+ *
+ * This function uses a cubic bezier curve for the individual segments and
+ * calculates the necessary intermediate control points depending on the
+ * neighbor curve control points.
+ */
+static void
+gimp_curve_plot (GimpCurve *curve,
+ gint p1,
+ gint p2,
+ gint p3,
+ gint p4)
+{
+ gint i;
+ gdouble x0, x3;
+ gdouble y0, y1, y2, y3;
+ gdouble dx, dy;
+ gdouble slope;
+
+ /* the outer control points for the bezier curve. */
+ x0 = curve->points[p2].x;
+ y0 = curve->points[p2].y;
+ x3 = curve->points[p3].x;
+ y3 = curve->points[p3].y;
+
+ /*
+ * the x values of the inner control points are fixed at
+ * x1 = 2/3*x0 + 1/3*x3 and x2 = 1/3*x0 + 2/3*x3
+ * this ensures that the x values increase linearly with the
+ * parameter t and enables us to skip the calculation of the x
+ * values altogether - just calculate y(t) evenly spaced.
+ */
+
+ dx = x3 - x0;
+ dy = y3 - y0;
+
+ if (dx <= EPSILON)
+ {
+ gint index;
+
+ index = ROUND (x0 * (gdouble) (curve->n_samples - 1));
+
+ curve->samples[index] = y3;
+
+ return;
+ }
+
+ if (p1 == p2 && p3 == p4)
+ {
+ /* No information about the neighbors,
+ * calculate y1 and y2 to get a straight line
+ */
+ y1 = y0 + dy / 3.0;
+ y2 = y0 + dy * 2.0 / 3.0;
+ }
+ else if (p1 == p2 && p3 != p4)
+ {
+ /* only the right neighbor is available. Make the tangent at the
+ * right endpoint parallel to the line between the left endpoint
+ * and the right neighbor. Then point the tangent at the left towards
+ * the control handle of the right tangent, to ensure that the curve
+ * does not have an inflection point.
+ */
+ slope = (curve->points[p4].y - y0) / (curve->points[p4].x - x0);
+
+ y2 = y3 - slope * dx / 3.0;
+ y1 = y0 + (y2 - y0) / 2.0;
+ }
+ else if (p1 != p2 && p3 == p4)
+ {
+ /* see previous case */
+ slope = (y3 - curve->points[p1].y) / (x3 - curve->points[p1].x);
+
+ y1 = y0 + slope * dx / 3.0;
+ y2 = y3 + (y1 - y3) / 2.0;
+ }
+ else /* (p1 != p2 && p3 != p4) */
+ {
+ /* Both neighbors are available. Make the tangents at the endpoints
+ * parallel to the line between the opposite endpoint and the adjacent
+ * neighbor.
+ */
+ slope = (y3 - curve->points[p1].y) / (x3 - curve->points[p1].x);
+
+ y1 = y0 + slope * dx / 3.0;
+
+ slope = (curve->points[p4].y - y0) / (curve->points[p4].x - x0);
+
+ y2 = y3 - slope * dx / 3.0;
+ }
+
+ /*
+ * finally calculate the y(t) values for the given bezier values. We can
+ * use homogeneously distributed values for t, since x(t) increases linearly.
+ */
+ for (i = 0; i <= ROUND (dx * (gdouble) (curve->n_samples - 1)); i++)
+ {
+ gdouble y, t;
+ gint index;
+
+ t = i / dx / (gdouble) (curve->n_samples - 1);
+ y = y0 * (1-t) * (1-t) * (1-t) +
+ 3 * y1 * (1-t) * (1-t) * t +
+ 3 * y2 * (1-t) * t * t +
+ y3 * t * t * t;
+
+ index = i + ROUND (x0 * (gdouble) (curve->n_samples - 1));
+
+ if (index < curve->n_samples)
+ curve->samples[index] = CLAMP (y, 0.0, 1.0);
+ }
+}
diff --git a/app/core/gimpcurve.h b/app/core/gimpcurve.h
new file mode 100644
index 0000000..71654aa
--- /dev/null
+++ b/app/core/gimpcurve.h
@@ -0,0 +1,124 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_CURVE_H__
+#define __GIMP_CURVE_H__
+
+
+#include "gimpdata.h"
+
+
+#define GIMP_TYPE_CURVE (gimp_curve_get_type ())
+#define GIMP_CURVE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CURVE, GimpCurve))
+#define GIMP_CURVE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CURVE, GimpCurveClass))
+#define GIMP_IS_CURVE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CURVE))
+#define GIMP_IS_CURVE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CURVE))
+#define GIMP_CURVE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_CURVE, GimpCurveClass))
+
+
+typedef struct _GimpCurvePoint GimpCurvePoint;
+typedef struct _GimpCurveClass GimpCurveClass;
+
+struct _GimpCurvePoint
+{
+ gdouble x;
+ gdouble y;
+
+ GimpCurvePointType type;
+};
+
+struct _GimpCurve
+{
+ GimpData parent_instance;
+
+ GimpCurveType curve_type;
+
+ gint n_points;
+ GimpCurvePoint *points;
+
+ gint n_samples;
+ gdouble *samples;
+
+ gboolean identity; /* whether the curve is an identity mapping */
+};
+
+struct _GimpCurveClass
+{
+ GimpDataClass parent_class;
+};
+
+
+GType gimp_curve_get_type (void) G_GNUC_CONST;
+
+GimpData * gimp_curve_new (const gchar *name);
+GimpData * gimp_curve_get_standard (void);
+
+void gimp_curve_reset (GimpCurve *curve,
+ gboolean reset_type);
+
+void gimp_curve_set_curve_type (GimpCurve *curve,
+ GimpCurveType curve_type);
+GimpCurveType gimp_curve_get_curve_type (GimpCurve *curve);
+
+gint gimp_curve_get_n_points (GimpCurve *curve);
+
+void gimp_curve_set_n_samples (GimpCurve *curve,
+ gint n_samples);
+gint gimp_curve_get_n_samples (GimpCurve *curve);
+
+gint gimp_curve_get_point_at (GimpCurve *curve,
+ gdouble x);
+gint gimp_curve_get_closest_point (GimpCurve *curve,
+ gdouble x,
+ gdouble y,
+ gdouble max_distance);
+
+gint gimp_curve_add_point (GimpCurve *curve,
+ gdouble x,
+ gdouble y);
+void gimp_curve_delete_point (GimpCurve *curve,
+ gint point);
+void gimp_curve_set_point (GimpCurve *curve,
+ gint point,
+ gdouble x,
+ gdouble y);
+void gimp_curve_move_point (GimpCurve *curve,
+ gint point,
+ gdouble y);
+void gimp_curve_get_point (GimpCurve *curve,
+ gint point,
+ gdouble *x,
+ gdouble *y);
+void gimp_curve_set_point_type (GimpCurve *curve,
+ gint point,
+ GimpCurvePointType type);
+GimpCurvePointType gimp_curve_get_point_type (GimpCurve *curve,
+ gint point);
+void gimp_curve_clear_points (GimpCurve *curve);
+
+void gimp_curve_set_curve (GimpCurve *curve,
+ gdouble x,
+ gdouble y);
+
+gboolean gimp_curve_is_identity (GimpCurve *curve);
+
+void gimp_curve_get_uchar (GimpCurve *curve,
+ gint n_samples,
+ guchar *samples);
+
+
+#endif /* __GIMP_CURVE_H__ */
diff --git a/app/core/gimpdashpattern.c b/app/core/gimpdashpattern.c
new file mode 100644
index 0000000..e9fb9fc
--- /dev/null
+++ b/app/core/gimpdashpattern.c
@@ -0,0 +1,355 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-1999 Spencer Kimball and Peter Mattis
+ *
+ * gimpdashpattern.c
+ * Copyright (C) 2003 Simon Budig
+ * Copyright (C) 2005 Sven Neumann
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gio/gio.h>
+
+#include "libgimpbase/gimpbase.h"
+
+#include "core-types.h"
+
+#include "gimpdashpattern.h"
+
+
+GType
+gimp_dash_pattern_get_type (void)
+{
+ static GType type = 0;
+
+ if (! type)
+ type = g_boxed_type_register_static ("GimpDashPattern",
+ (GBoxedCopyFunc) gimp_dash_pattern_copy,
+ (GBoxedFreeFunc) gimp_dash_pattern_free);
+
+ return type;
+}
+
+GArray *
+gimp_dash_pattern_new_from_preset (GimpDashPreset preset)
+{
+ GArray *pattern;
+ gdouble dash;
+ gint i;
+
+ pattern = g_array_new (FALSE, FALSE, sizeof (gdouble));
+
+ switch (preset)
+ {
+ case GIMP_DASH_CUSTOM:
+ g_warning ("GIMP_DASH_CUSTOM passed to gimp_dash_pattern_from_preset()");
+ break;
+
+ case GIMP_DASH_LINE:
+ break;
+
+ case GIMP_DASH_LONG_DASH:
+ dash = 9.0; g_array_append_val (pattern, dash);
+ dash = 3.0; g_array_append_val (pattern, dash);
+ break;
+
+ case GIMP_DASH_MEDIUM_DASH:
+ dash = 6.0; g_array_append_val (pattern, dash);
+ dash = 6.0; g_array_append_val (pattern, dash);
+ break;
+
+ case GIMP_DASH_SHORT_DASH:
+ dash = 3.0; g_array_append_val (pattern, dash);
+ dash = 9.0; g_array_append_val (pattern, dash);
+ break;
+
+ case GIMP_DASH_SPARSE_DOTS:
+ for (i = 0; i < 2; i++)
+ {
+ dash = 1.0; g_array_append_val (pattern, dash);
+ dash = 5.0; g_array_append_val (pattern, dash);
+ }
+ break;
+
+ case GIMP_DASH_NORMAL_DOTS:
+ for (i = 0; i < 3; i++)
+ {
+ dash = 1.0; g_array_append_val (pattern, dash);
+ dash = 3.0; g_array_append_val (pattern, dash);
+ }
+ break;
+
+ case GIMP_DASH_DENSE_DOTS:
+ for (i = 0; i < 12; i++)
+ {
+ dash = 1.0; g_array_append_val (pattern, dash);
+ }
+ break;
+
+ case GIMP_DASH_STIPPLES:
+ for (i = 0; i < 24; i++)
+ {
+ dash = 0.5; g_array_append_val (pattern, dash);
+ }
+ break;
+
+ case GIMP_DASH_DASH_DOT:
+ dash = 7.0; g_array_append_val (pattern, dash);
+ dash = 2.0; g_array_append_val (pattern, dash);
+ dash = 1.0; g_array_append_val (pattern, dash);
+ dash = 2.0; g_array_append_val (pattern, dash);
+ break;
+
+ case GIMP_DASH_DASH_DOT_DOT:
+ dash = 7.0; g_array_append_val (pattern, dash);
+ for (i=0; i < 5; i++)
+ {
+ dash = 1.0; g_array_append_val (pattern, dash);
+ }
+ break;
+ }
+
+ if (pattern->len < 2)
+ {
+ gimp_dash_pattern_free (pattern);
+ return NULL;
+ }
+
+ return pattern;
+}
+
+GArray *
+gimp_dash_pattern_new_from_segments (const gboolean *segments,
+ gint n_segments,
+ gdouble dash_length)
+{
+ GArray *pattern;
+ gint i;
+ gint count;
+ gboolean state;
+
+ g_return_val_if_fail (segments != NULL || n_segments == 0, NULL);
+
+ pattern = g_array_new (FALSE, FALSE, sizeof (gdouble));
+
+ for (i = 0, count = 0, state = TRUE; i <= n_segments; i++)
+ {
+ if (i < n_segments && segments[i] == state)
+ {
+ count++;
+ }
+ else
+ {
+ gdouble l = (dash_length * count) / n_segments;
+
+ g_array_append_val (pattern, l);
+
+ count = 1;
+ state = ! state;
+ }
+ }
+
+ if (pattern->len < 2)
+ {
+ gimp_dash_pattern_free (pattern);
+ return NULL;
+ }
+
+ return pattern;
+}
+
+void
+gimp_dash_pattern_fill_segments (GArray *pattern,
+ gboolean *segments,
+ gint n_segments)
+{
+ gdouble factor;
+ gdouble sum;
+ gint i, j;
+ gboolean paint;
+
+ g_return_if_fail (segments != NULL || n_segments == 0);
+
+ if (pattern == NULL || pattern->len <= 1)
+ {
+ for (i = 0; i < n_segments; i++)
+ segments[i] = TRUE;
+
+ return;
+ }
+
+ for (i = 0, sum = 0; i < pattern->len ; i++)
+ {
+ sum += g_array_index (pattern, gdouble, i);
+ }
+
+ factor = ((gdouble) n_segments) / sum;
+
+ j = 0;
+ sum = g_array_index (pattern, gdouble, j) * factor;
+ paint = TRUE;
+
+ for (i = 0; i < n_segments ; i++)
+ {
+ while (j < pattern->len && sum <= i)
+ {
+ paint = ! paint;
+ j++;
+ sum += g_array_index (pattern, gdouble, j) * factor;
+ }
+
+ segments[i] = paint;
+ }
+}
+
+GArray *
+gimp_dash_pattern_from_value_array (GimpValueArray *value_array)
+{
+ if (value_array == NULL || gimp_value_array_length (value_array) == 0)
+ {
+ return NULL;
+ }
+ else
+ {
+ GArray *pattern;
+ gint length;
+ gint i;
+
+ length = gimp_value_array_length (value_array);
+
+ pattern = g_array_sized_new (FALSE, FALSE, sizeof (gdouble), length);
+
+ for (i = 0; i < length; i++)
+ {
+ GValue *item = gimp_value_array_index (value_array, i);
+ gdouble val;
+
+ g_return_val_if_fail (G_VALUE_HOLDS_DOUBLE (item), NULL);
+
+ val = g_value_get_double (item);
+
+ g_array_append_val (pattern, val);
+ }
+
+ return pattern;
+ }
+}
+
+GimpValueArray *
+gimp_dash_pattern_to_value_array (GArray *pattern)
+{
+ if (pattern != NULL && pattern->len > 0)
+ {
+ GimpValueArray *value_array = gimp_value_array_new (pattern->len);
+ GValue item = G_VALUE_INIT;
+ gint i;
+
+ g_value_init (&item, G_TYPE_DOUBLE);
+
+ for (i = 0; i < pattern->len; i++)
+ {
+ g_value_set_double (&item, g_array_index (pattern, gdouble, i));
+ gimp_value_array_append (value_array, &item);
+ }
+
+ g_value_unset (&item);
+
+ return value_array;
+ }
+
+ return NULL;
+}
+
+GArray *
+gimp_dash_pattern_from_double_array (gint n_dashes,
+ const gdouble *dashes)
+{
+ if (n_dashes > 0 && dashes != NULL)
+ {
+ GArray *pattern;
+ gint i;
+
+ pattern = g_array_new (FALSE, FALSE, sizeof (gdouble));
+
+ for (i = 0; i < n_dashes; i++)
+ {
+ if (dashes[i] >= 0.0)
+ {
+ g_array_append_val (pattern, dashes[i]);
+ }
+ else
+ {
+ g_array_free (pattern, TRUE);
+ return NULL;
+ }
+ }
+
+ return pattern;
+ }
+
+ return NULL;
+}
+
+gdouble *
+gimp_dash_pattern_to_double_array (GArray *pattern,
+ gint *n_dashes)
+{
+ if (pattern != NULL && pattern->len > 0)
+ {
+ gdouble *dashes;
+ gint i;
+
+ dashes = g_new0 (gdouble, pattern->len);
+
+ for (i = 0; i < pattern->len; i++)
+ {
+ dashes[i] = g_array_index (pattern, gdouble, i);
+ }
+
+ if (n_dashes)
+ *n_dashes = pattern->len;
+
+ return dashes;
+ }
+
+ return NULL;
+}
+
+GArray *
+gimp_dash_pattern_copy (GArray *pattern)
+{
+ if (pattern)
+ {
+ GArray *copy;
+ gint i;
+
+ copy = g_array_sized_new (FALSE, FALSE, sizeof (gdouble), pattern->len);
+
+ for (i = 0; i < pattern->len; i++)
+ g_array_append_val (copy, g_array_index (pattern, gdouble, i));
+
+ return copy;
+ }
+
+ return NULL;
+}
+
+void
+gimp_dash_pattern_free (GArray *pattern)
+{
+ if (pattern)
+ g_array_free (pattern, TRUE);
+}
diff --git a/app/core/gimpdashpattern.h b/app/core/gimpdashpattern.h
new file mode 100644
index 0000000..005c516
--- /dev/null
+++ b/app/core/gimpdashpattern.h
@@ -0,0 +1,53 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-1999 Spencer Kimball and Peter Mattis
+ *
+ * gimpdashpattern.h
+ * Copyright (C) 2003 Simon Budig
+ * Copyright (C) 2005 Sven Neumann
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_DASH_PATTERN_H__
+#define __GIMP_DASH_PATTERN_H__
+
+
+#define GIMP_TYPE_DASH_PATTERN (gimp_dash_pattern_get_type ())
+#define GIMP_VALUE_HOLDS_DASH_PATTERN(value) (G_TYPE_CHECK_VALUE_TYPE ((value), GIMP_TYPE_DASH_PATTERN))
+
+
+GType gimp_dash_pattern_get_type (void) G_GNUC_CONST;
+
+GArray * gimp_dash_pattern_new_from_preset (GimpDashPreset preset);
+GArray * gimp_dash_pattern_new_from_segments (const gboolean *segments,
+ gint n_segments,
+ gdouble dash_length);
+
+void gimp_dash_pattern_fill_segments (GArray *pattern,
+ gboolean *segments,
+ gint n_segments);
+
+GArray * gimp_dash_pattern_from_value_array (GimpValueArray *value_array);
+GimpValueArray * gimp_dash_pattern_to_value_array (GArray *pattern);
+
+GArray * gimp_dash_pattern_from_double_array (gint n_dashes,
+ const gdouble *dashes);
+gdouble * gimp_dash_pattern_to_double_array (GArray *pattern,
+ gint *n_dashes);
+
+GArray * gimp_dash_pattern_copy (GArray *pattern);
+void gimp_dash_pattern_free (GArray *pattern);
+
+
+#endif /* __GIMP_DASH_PATTERN_H__ */
diff --git a/app/core/gimpdata.c b/app/core/gimpdata.c
new file mode 100644
index 0000000..e4640f3
--- /dev/null
+++ b/app/core/gimpdata.c
@@ -0,0 +1,1245 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpdata.c
+ * Copyright (C) 2001 Michael Natterer <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpbase/gimpbase.h"
+
+#include "core-types.h"
+
+#include "gimp-memsize.h"
+#include "gimpdata.h"
+#include "gimpmarshal.h"
+#include "gimptag.h"
+#include "gimptagged.h"
+
+#include "gimp-intl.h"
+
+
+enum
+{
+ DIRTY,
+ LAST_SIGNAL
+};
+
+enum
+{
+ PROP_0,
+ PROP_FILE,
+ PROP_WRITABLE,
+ PROP_DELETABLE,
+ PROP_MIME_TYPE
+};
+
+
+struct _GimpDataPrivate
+{
+ GFile *file;
+ GQuark mime_type;
+ guint writable : 1;
+ guint deletable : 1;
+ guint dirty : 1;
+ guint internal : 1;
+ gint freeze_count;
+ gint64 mtime;
+
+ /* Identifies the GimpData object across sessions. Used when there
+ * is not a filename associated with the object.
+ */
+ gchar *identifier;
+
+ GList *tags;
+};
+
+#define GIMP_DATA_GET_PRIVATE(obj) (((GimpData *) (obj))->priv)
+
+
+static void gimp_data_tagged_iface_init (GimpTaggedInterface *iface);
+
+static void gimp_data_constructed (GObject *object);
+static void gimp_data_finalize (GObject *object);
+static void gimp_data_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_data_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static void gimp_data_name_changed (GimpObject *object);
+static gint64 gimp_data_get_memsize (GimpObject *object,
+ gint64 *gui_size);
+
+static gboolean gimp_data_is_name_editable (GimpViewable *viewable);
+
+static void gimp_data_real_dirty (GimpData *data);
+static GimpData * gimp_data_real_duplicate (GimpData *data);
+static gint gimp_data_real_compare (GimpData *data1,
+ GimpData *data2);
+
+static gboolean gimp_data_add_tag (GimpTagged *tagged,
+ GimpTag *tag);
+static gboolean gimp_data_remove_tag (GimpTagged *tagged,
+ GimpTag *tag);
+static GList * gimp_data_get_tags (GimpTagged *tagged);
+static gchar * gimp_data_get_identifier (GimpTagged *tagged);
+static gchar * gimp_data_get_checksum (GimpTagged *tagged);
+
+
+G_DEFINE_TYPE_WITH_CODE (GimpData, gimp_data, GIMP_TYPE_VIEWABLE,
+ G_ADD_PRIVATE (GimpData)
+ G_IMPLEMENT_INTERFACE (GIMP_TYPE_TAGGED,
+ gimp_data_tagged_iface_init))
+
+#define parent_class gimp_data_parent_class
+
+static guint data_signals[LAST_SIGNAL] = { 0 };
+
+
+static void
+gimp_data_class_init (GimpDataClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpObjectClass *gimp_object_class = GIMP_OBJECT_CLASS (klass);
+ GimpViewableClass *viewable_class = GIMP_VIEWABLE_CLASS (klass);
+
+ parent_class = g_type_class_peek_parent (klass);
+
+ data_signals[DIRTY] =
+ g_signal_new ("dirty",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpDataClass, dirty),
+ NULL, NULL,
+ gimp_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ object_class->constructed = gimp_data_constructed;
+ object_class->finalize = gimp_data_finalize;
+ object_class->set_property = gimp_data_set_property;
+ object_class->get_property = gimp_data_get_property;
+
+ gimp_object_class->name_changed = gimp_data_name_changed;
+ gimp_object_class->get_memsize = gimp_data_get_memsize;
+
+ viewable_class->name_editable = TRUE;
+ viewable_class->is_name_editable = gimp_data_is_name_editable;
+
+ klass->dirty = gimp_data_real_dirty;
+ klass->save = NULL;
+ klass->get_extension = NULL;
+ klass->copy = NULL;
+ klass->duplicate = gimp_data_real_duplicate;
+ klass->compare = gimp_data_real_compare;
+
+ g_object_class_install_property (object_class, PROP_FILE,
+ g_param_spec_object ("file", NULL, NULL,
+ G_TYPE_FILE,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_WRITABLE,
+ g_param_spec_boolean ("writable", NULL, NULL,
+ FALSE,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_DELETABLE,
+ g_param_spec_boolean ("deletable", NULL, NULL,
+ FALSE,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_MIME_TYPE,
+ g_param_spec_string ("mime-type", NULL, NULL,
+ NULL,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+}
+
+static void
+gimp_data_tagged_iface_init (GimpTaggedInterface *iface)
+{
+ iface->add_tag = gimp_data_add_tag;
+ iface->remove_tag = gimp_data_remove_tag;
+ iface->get_tags = gimp_data_get_tags;
+ iface->get_identifier = gimp_data_get_identifier;
+ iface->get_checksum = gimp_data_get_checksum;
+}
+
+static void
+gimp_data_init (GimpData *data)
+{
+ GimpDataPrivate *private = gimp_data_get_instance_private (data);
+
+ data->priv = private;
+
+ private->writable = TRUE;
+ private->deletable = TRUE;
+ private->dirty = TRUE;
+
+ /* freeze the data object during construction */
+ gimp_data_freeze (data);
+}
+
+static void
+gimp_data_constructed (GObject *object)
+{
+ GimpDataPrivate *private = GIMP_DATA_GET_PRIVATE (object);
+
+ G_OBJECT_CLASS (parent_class)->constructed (object);
+
+ if (! GIMP_DATA_GET_CLASS (object)->save)
+ private->writable = FALSE;
+
+ gimp_data_thaw (GIMP_DATA (object));
+}
+
+static void
+gimp_data_finalize (GObject *object)
+{
+ GimpDataPrivate *private = GIMP_DATA_GET_PRIVATE (object);
+
+ g_clear_object (&private->file);
+
+ if (private->tags)
+ {
+ g_list_free_full (private->tags, (GDestroyNotify) g_object_unref);
+ private->tags = NULL;
+ }
+
+ g_clear_pointer (&private->identifier, g_free);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gimp_data_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpData *data = GIMP_DATA (object);
+ GimpDataPrivate *private = GIMP_DATA_GET_PRIVATE (data);
+
+ switch (property_id)
+ {
+ case PROP_FILE:
+ gimp_data_set_file (data,
+ g_value_get_object (value),
+ private->writable,
+ private->deletable);
+ break;
+
+ case PROP_WRITABLE:
+ private->writable = g_value_get_boolean (value);
+ break;
+
+ case PROP_DELETABLE:
+ private->deletable = g_value_get_boolean (value);
+ break;
+
+ case PROP_MIME_TYPE:
+ if (g_value_get_string (value))
+ private->mime_type = g_quark_from_string (g_value_get_string (value));
+ else
+ private->mime_type = 0;
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_data_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpDataPrivate *private = GIMP_DATA_GET_PRIVATE (object);
+
+ switch (property_id)
+ {
+ case PROP_FILE:
+ g_value_set_object (value, private->file);
+ break;
+
+ case PROP_WRITABLE:
+ g_value_set_boolean (value, private->writable);
+ break;
+
+ case PROP_DELETABLE:
+ g_value_set_boolean (value, private->deletable);
+ break;
+
+ case PROP_MIME_TYPE:
+ g_value_set_string (value, g_quark_to_string (private->mime_type));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_data_name_changed (GimpObject *object)
+{
+ GimpDataPrivate *private = GIMP_DATA_GET_PRIVATE (object);
+
+ private->dirty = TRUE;
+
+ if (GIMP_OBJECT_CLASS (parent_class)->name_changed)
+ GIMP_OBJECT_CLASS (parent_class)->name_changed (object);
+}
+
+static gint64
+gimp_data_get_memsize (GimpObject *object,
+ gint64 *gui_size)
+{
+ GimpDataPrivate *private = GIMP_DATA_GET_PRIVATE (object);
+ gint64 memsize = 0;
+
+ memsize += gimp_g_object_get_memsize (G_OBJECT (private->file));
+
+ return memsize + GIMP_OBJECT_CLASS (parent_class)->get_memsize (object,
+ gui_size);
+}
+
+static gboolean
+gimp_data_is_name_editable (GimpViewable *viewable)
+{
+ return gimp_data_is_writable (GIMP_DATA (viewable)) &&
+ ! gimp_data_is_internal (GIMP_DATA (viewable));
+}
+
+static void
+gimp_data_real_dirty (GimpData *data)
+{
+ gimp_viewable_invalidate_preview (GIMP_VIEWABLE (data));
+
+ /* Emit the "name-changed" to signal general dirtiness, our name
+ * changed implementation will also set the "dirty" flag to TRUE.
+ */
+ gimp_object_name_changed (GIMP_OBJECT (data));
+}
+
+static GimpData *
+gimp_data_real_duplicate (GimpData *data)
+{
+ if (GIMP_DATA_GET_CLASS (data)->copy)
+ {
+ GimpData *new = g_object_new (G_OBJECT_TYPE (data), NULL);
+
+ gimp_data_copy (new, data);
+
+ return new;
+ }
+
+ return NULL;
+}
+
+static gint
+gimp_data_real_compare (GimpData *data1,
+ GimpData *data2)
+{
+ GimpDataPrivate *private1 = GIMP_DATA_GET_PRIVATE (data1);
+ GimpDataPrivate *private2 = GIMP_DATA_GET_PRIVATE (data2);
+
+ /* move the internal objects (like the FG -> BG) gradient) to the top */
+ if (private1->internal != private2->internal)
+ return private1->internal ? -1 : 1;
+
+ /* keep user-deletable objects above system resource files */
+ if (private1->deletable != private2->deletable)
+ return private1->deletable ? -1 : 1;
+
+ return gimp_object_name_collate ((GimpObject *) data1,
+ (GimpObject *) data2);
+}
+
+static gboolean
+gimp_data_add_tag (GimpTagged *tagged,
+ GimpTag *tag)
+{
+ GimpDataPrivate *private = GIMP_DATA_GET_PRIVATE (tagged);
+ GList *list;
+
+ for (list = private->tags; list; list = g_list_next (list))
+ {
+ GimpTag *this = GIMP_TAG (list->data);
+
+ if (gimp_tag_equals (tag, this))
+ return FALSE;
+ }
+
+ private->tags = g_list_prepend (private->tags, g_object_ref (tag));
+
+ return TRUE;
+}
+
+static gboolean
+gimp_data_remove_tag (GimpTagged *tagged,
+ GimpTag *tag)
+{
+ GimpDataPrivate *private = GIMP_DATA_GET_PRIVATE (tagged);
+ GList *list;
+
+ for (list = private->tags; list; list = g_list_next (list))
+ {
+ GimpTag *this = GIMP_TAG (list->data);
+
+ if (gimp_tag_equals (tag, this))
+ {
+ private->tags = g_list_delete_link (private->tags, list);
+ g_object_unref (this);
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+static GList *
+gimp_data_get_tags (GimpTagged *tagged)
+{
+ GimpDataPrivate *private = GIMP_DATA_GET_PRIVATE (tagged);
+
+ return private->tags;
+}
+
+static gchar *
+gimp_data_get_identifier (GimpTagged *tagged)
+{
+ GimpDataPrivate *private = GIMP_DATA_GET_PRIVATE (tagged);
+ gchar *identifier = NULL;
+
+ if (private->file)
+ {
+ const gchar *data_dir = gimp_data_directory ();
+ const gchar *gimp_dir = gimp_directory ();
+ gchar *path = g_file_get_path (private->file);
+ gchar *tmp;
+
+ if (g_str_has_prefix (path, data_dir))
+ {
+ tmp = g_strconcat ("${gimp_data_dir}",
+ path + strlen (data_dir),
+ NULL);
+ identifier = g_filename_to_utf8 (tmp, -1, NULL, NULL, NULL);
+ g_free (tmp);
+ }
+ else if (g_str_has_prefix (path, gimp_dir))
+ {
+ tmp = g_strconcat ("${gimp_dir}",
+ path + strlen (gimp_dir),
+ NULL);
+ identifier = g_filename_to_utf8 (tmp, -1, NULL, NULL, NULL);
+ g_free (tmp);
+ }
+ else if (g_str_has_prefix (path, MYPAINT_BRUSHES_DIR))
+ {
+ tmp = g_strconcat ("${mypaint_brushes_dir}",
+ path + strlen (MYPAINT_BRUSHES_DIR),
+ NULL);
+ identifier = g_filename_to_utf8 (tmp, -1, NULL, NULL, NULL);
+ g_free (tmp);
+ }
+ else
+ {
+ identifier = g_filename_to_utf8 (path, -1,
+ NULL, NULL, NULL);
+ }
+
+ if (! identifier)
+ {
+ g_printerr ("%s: failed to convert '%s' to utf8.\n",
+ G_STRFUNC, path);
+ identifier = g_strdup (path);
+ }
+
+ g_free (path);
+ }
+ else if (private->internal)
+ {
+ identifier = g_strdup (private->identifier);
+ }
+
+ return identifier;
+}
+
+static gchar *
+gimp_data_get_checksum (GimpTagged *tagged)
+{
+ return NULL;
+}
+
+/**
+ * gimp_data_save:
+ * @data: object whose contents are to be saved.
+ * @error: return location for errors or %NULL
+ *
+ * Save the object. If the object is marked as "internal", nothing
+ * happens. Otherwise, it is saved to disk, using the file name set
+ * by gimp_data_set_file(). If the save is successful, the object is
+ * marked as not dirty. If not, an error message is returned using
+ * the @error argument.
+ *
+ * Returns: %TRUE if the object is internal or the save is successful.
+ **/
+gboolean
+gimp_data_save (GimpData *data,
+ GError **error)
+{
+ GimpDataPrivate *private;
+ gboolean success = FALSE;
+
+ g_return_val_if_fail (GIMP_IS_DATA (data), FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ private = GIMP_DATA_GET_PRIVATE (data);
+
+ g_return_val_if_fail (private->writable == TRUE, FALSE);
+
+ if (private->internal)
+ {
+ private->dirty = FALSE;
+ return TRUE;
+ }
+
+ g_return_val_if_fail (G_IS_FILE (private->file), FALSE);
+
+ if (GIMP_DATA_GET_CLASS (data)->save)
+ {
+ GOutputStream *output;
+
+ output = G_OUTPUT_STREAM (g_file_replace (private->file,
+ NULL, FALSE, G_FILE_CREATE_NONE,
+ NULL, error));
+
+ if (output)
+ {
+ success = GIMP_DATA_GET_CLASS (data)->save (data, output, error);
+
+ if (success)
+ {
+ if (! g_output_stream_close (output, NULL, error))
+ {
+ g_prefix_error (error,
+ _("Error saving '%s': "),
+ gimp_file_get_utf8_name (private->file));
+ success = FALSE;
+ }
+ }
+ else
+ {
+ GCancellable *cancellable = g_cancellable_new ();
+
+ g_cancellable_cancel (cancellable);
+ if (error && *error)
+ {
+ g_prefix_error (error,
+ _("Error saving '%s': "),
+ gimp_file_get_utf8_name (private->file));
+ }
+ else
+ {
+ g_set_error (error, GIMP_DATA_ERROR, GIMP_DATA_ERROR_WRITE,
+ _("Error saving '%s'"),
+ gimp_file_get_utf8_name (private->file));
+ }
+ g_output_stream_close (output, cancellable, NULL);
+ g_object_unref (cancellable);
+ }
+
+ g_object_unref (output);
+ }
+ }
+
+ if (success)
+ {
+ GFileInfo *info = g_file_query_info (private->file,
+ G_FILE_ATTRIBUTE_TIME_MODIFIED,
+ G_FILE_QUERY_INFO_NONE,
+ NULL, NULL);
+ if (info)
+ {
+ private->mtime =
+ g_file_info_get_attribute_uint64 (info,
+ G_FILE_ATTRIBUTE_TIME_MODIFIED);
+ g_object_unref (info);
+ }
+
+ private->dirty = FALSE;
+ }
+
+ return success;
+}
+
+/**
+ * gimp_data_dirty:
+ * @data: a #GimpData object.
+ *
+ * Marks @data as dirty. Unless the object is frozen, this causes
+ * its preview to be invalidated, and emits a "dirty" signal. If the
+ * object is frozen, the function has no effect.
+ **/
+void
+gimp_data_dirty (GimpData *data)
+{
+ GimpDataPrivate *private;
+
+ g_return_if_fail (GIMP_IS_DATA (data));
+
+ private = GIMP_DATA_GET_PRIVATE (data);
+
+ if (private->freeze_count == 0)
+ g_signal_emit (data, data_signals[DIRTY], 0);
+}
+
+void
+gimp_data_clean (GimpData *data)
+{
+ GimpDataPrivate *private;
+
+ g_return_if_fail (GIMP_IS_DATA (data));
+
+ private = GIMP_DATA_GET_PRIVATE (data);
+
+ private->dirty = FALSE;
+}
+
+gboolean
+gimp_data_is_dirty (GimpData *data)
+{
+ GimpDataPrivate *private;
+
+ g_return_val_if_fail (GIMP_IS_DATA (data), FALSE);
+
+ private = GIMP_DATA_GET_PRIVATE (data);
+
+ return private->dirty;
+}
+
+/**
+ * gimp_data_freeze:
+ * @data: a #GimpData object.
+ *
+ * Increments the freeze count for the object. A positive freeze count
+ * prevents the object from being treated as dirty. Any call to this
+ * function must be followed eventually by a call to gimp_data_thaw().
+ **/
+void
+gimp_data_freeze (GimpData *data)
+{
+ GimpDataPrivate *private;
+
+ g_return_if_fail (GIMP_IS_DATA (data));
+
+ private = GIMP_DATA_GET_PRIVATE (data);
+
+ private->freeze_count++;
+}
+
+/**
+ * gimp_data_thaw:
+ * @data: a #GimpData object.
+ *
+ * Decrements the freeze count for the object. If the freeze count
+ * drops to zero, the object is marked as dirty, and the "dirty"
+ * signal is emitted. It is an error to call this function without
+ * having previously called gimp_data_freeze().
+ **/
+void
+gimp_data_thaw (GimpData *data)
+{
+ GimpDataPrivate *private;
+
+ g_return_if_fail (GIMP_IS_DATA (data));
+
+ private = GIMP_DATA_GET_PRIVATE (data);
+
+ g_return_if_fail (private->freeze_count > 0);
+
+ private->freeze_count--;
+
+ if (private->freeze_count == 0)
+ gimp_data_dirty (data);
+}
+
+gboolean
+gimp_data_is_frozen (GimpData *data)
+{
+ GimpDataPrivate *private;
+
+ g_return_val_if_fail (GIMP_IS_DATA (data), FALSE);
+
+ private = GIMP_DATA_GET_PRIVATE (data);
+
+ return private->freeze_count > 0;
+}
+
+/**
+ * gimp_data_delete_from_disk:
+ * @data: a #GimpData object.
+ * @error: return location for errors or %NULL
+ *
+ * Deletes the object from disk. If the object is marked as "internal",
+ * nothing happens. Otherwise, if the file exists whose name has been
+ * set by gimp_data_set_file(), it is deleted. Obviously this is
+ * a potentially dangerous function, which should be used with care.
+ *
+ * Returns: %TRUE if the object is internal to Gimp, or the deletion is
+ * successful.
+ **/
+gboolean
+gimp_data_delete_from_disk (GimpData *data,
+ GError **error)
+{
+ GimpDataPrivate *private;
+
+ g_return_val_if_fail (GIMP_IS_DATA (data), FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ private = GIMP_DATA_GET_PRIVATE (data);
+
+ g_return_val_if_fail (private->file != NULL, FALSE);
+ g_return_val_if_fail (private->deletable == TRUE, FALSE);
+
+ if (private->internal)
+ return TRUE;
+
+ return g_file_delete (private->file, NULL, error);
+}
+
+const gchar *
+gimp_data_get_extension (GimpData *data)
+{
+ g_return_val_if_fail (GIMP_IS_DATA (data), NULL);
+
+ if (GIMP_DATA_GET_CLASS (data)->get_extension)
+ return GIMP_DATA_GET_CLASS (data)->get_extension (data);
+
+ return NULL;
+}
+
+/**
+ * gimp_data_set_file:
+ * @data: A #GimpData object
+ * @file: File to assign to @data.
+ * @writable: %TRUE if we want to be able to write to this file.
+ * @deletable: %TRUE if we want to be able to delete this file.
+ *
+ * This function assigns a file to @data, and sets some flags
+ * according to the properties of the file. If @writable is %TRUE,
+ * and the user has permission to write or overwrite the requested
+ * file name, and a "save" method exists for @data's object type, then
+ * @data is marked as writable.
+ **/
+void
+gimp_data_set_file (GimpData *data,
+ GFile *file,
+ gboolean writable,
+ gboolean deletable)
+{
+ GimpDataPrivate *private;
+ gchar *path;
+
+ g_return_if_fail (GIMP_IS_DATA (data));
+ g_return_if_fail (G_IS_FILE (file));
+
+ path = g_file_get_path (file);
+
+ g_return_if_fail (path != NULL);
+ g_return_if_fail (g_path_is_absolute (path));
+
+ g_free (path);
+
+ private = GIMP_DATA_GET_PRIVATE (data);
+
+ if (private->internal)
+ return;
+
+ g_set_object (&private->file, file);
+
+ private->writable = FALSE;
+ private->deletable = FALSE;
+
+ /* if the data is supposed to be writable or deletable,
+ * still check if it really is
+ */
+ if (writable || deletable)
+ {
+ GFileInfo *info;
+
+ if (g_file_query_exists (private->file, NULL)) /* check if it exists */
+ {
+ info = g_file_query_info (private->file,
+ G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE,
+ G_FILE_QUERY_INFO_NONE,
+ NULL, NULL);
+
+ /* and we can write it */
+ if (info)
+ {
+ if (g_file_info_get_attribute_boolean (info,
+ G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE))
+ {
+ private->writable = writable ? TRUE : FALSE;
+ private->deletable = deletable ? TRUE : FALSE;
+ }
+
+ g_object_unref (info);
+ }
+ }
+ else /* OR it doesn't exist */
+ {
+ GFile *parent = g_file_get_parent (private->file);
+
+ info = g_file_query_info (parent,
+ G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE,
+ G_FILE_QUERY_INFO_NONE,
+ NULL, NULL);
+
+ /* and we can write to its parent directory */
+ if (info)
+ {
+ if (g_file_info_get_attribute_boolean (info,
+ G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE))
+ {
+ private->writable = writable ? TRUE : FALSE;
+ private->deletable = deletable ? TRUE : FALSE;
+ }
+
+ g_object_unref (info);
+ }
+
+ g_object_unref (parent);
+ }
+
+ /* if we can't save, we are not writable */
+ if (! GIMP_DATA_GET_CLASS (data)->save)
+ private->writable = FALSE;
+ }
+}
+
+GFile *
+gimp_data_get_file (GimpData *data)
+{
+ GimpDataPrivate *private;
+
+ g_return_val_if_fail (GIMP_IS_DATA (data), NULL);
+
+ private = GIMP_DATA_GET_PRIVATE (data);
+
+ return private->file;
+}
+
+/**
+ * gimp_data_create_filename:
+ * @data: a #Gimpdata object.
+ * @dest_dir: directory in which to create a file name.
+ *
+ * This function creates a unique file name to be used for saving
+ * a representation of @data in the directory @dest_dir. If the
+ * user does not have write permission in @dest_dir, then @data
+ * is marked as "not writable", so you should check on this before
+ * assuming that @data can be saved.
+ **/
+void
+gimp_data_create_filename (GimpData *data,
+ GFile *dest_dir)
+{
+ GimpDataPrivate *private;
+ gchar *safename;
+ gchar *basename;
+ GFile *file;
+ gint i;
+ gint unum = 1;
+ GError *error = NULL;
+
+ g_return_if_fail (GIMP_IS_DATA (data));
+ g_return_if_fail (G_IS_FILE (dest_dir));
+
+ private = GIMP_DATA_GET_PRIVATE (data);
+
+ if (private->internal)
+ return;
+
+ safename = g_strstrip (g_strdup (gimp_object_get_name (data)));
+
+ if (safename[0] == '.')
+ safename[0] = '-';
+
+ for (i = 0; safename[i]; i++)
+ if (strchr ("\\/*?\"`'<>{}|\n\t ;:$^&", safename[i]))
+ safename[i] = '-';
+
+ basename = g_strconcat (safename, gimp_data_get_extension (data), NULL);
+
+ file = g_file_get_child_for_display_name (dest_dir, basename, &error);
+ g_free (basename);
+
+ if (! file)
+ {
+ g_warning ("gimp_data_create_filename:\n"
+ "g_file_get_child_for_display_name() failed for '%s': %s",
+ gimp_object_get_name (data), error->message);
+ g_clear_error (&error);
+ g_free (safename);
+ return;
+ }
+
+ while (g_file_query_exists (file, NULL))
+ {
+ g_object_unref (file);
+
+ basename = g_strdup_printf ("%s-%d%s",
+ safename,
+ unum++,
+ gimp_data_get_extension (data));
+
+ file = g_file_get_child_for_display_name (dest_dir, basename, NULL);
+ g_free (basename);
+ }
+
+ g_free (safename);
+
+ gimp_data_set_file (data, file, TRUE, TRUE);
+
+ g_object_unref (file);
+}
+
+static const gchar *tag_blacklist[] = { "brushes",
+ "dynamics",
+ "patterns",
+ "palettes",
+ "gradients",
+ "tool-presets" };
+
+/**
+ * gimp_data_set_folder_tags:
+ * @data: a #Gimpdata object.
+ * @top_directory: the top directory of the currently processed data
+ * hierarchy.
+ *
+ * Sets tags based on all folder names below top_directory. So if the
+ * data's filename is e.g.
+ * /home/foo/.config/GIMP/X.Y/brushes/Flowers/Roses/rose.gbr, it will
+ * add "Flowers" and "Roses" tags.
+ *
+ * if the top directory (as passed, or as derived from the data's
+ * filename) does not end with one of the default data directory names
+ * (brushes, patterns etc), its name will be added as tag too.
+ **/
+void
+gimp_data_set_folder_tags (GimpData *data,
+ GFile *top_directory)
+{
+ GimpDataPrivate *private;
+ gchar *tmp;
+ gchar *dirname;
+ gchar *top_path;
+
+ g_return_if_fail (GIMP_IS_DATA (data));
+ g_return_if_fail (G_IS_FILE (top_directory));
+
+ private = GIMP_DATA_GET_PRIVATE (data);
+
+ if (private->internal)
+ return;
+
+ g_return_if_fail (private->file != NULL);
+
+ tmp = g_file_get_path (private->file);
+ dirname = g_path_get_dirname (tmp);
+ g_free (tmp);
+
+ top_path = g_file_get_path (top_directory);
+
+ g_return_if_fail (g_str_has_prefix (dirname, top_path));
+
+ /* walk up the hierarchy and set each folder on the way as tag,
+ * except the top_directory
+ */
+ while (strcmp (dirname, top_path))
+ {
+ gchar *basename = g_path_get_basename (dirname);
+ GimpTag *tag = gimp_tag_new (basename);
+
+ gimp_tag_set_internal (tag, TRUE);
+ gimp_tagged_add_tag (GIMP_TAGGED (data), tag);
+ g_object_unref (tag);
+ g_free (basename);
+
+ tmp = g_path_get_dirname (dirname);
+ g_free (dirname);
+ dirname = tmp;
+ }
+
+ g_free (top_path);
+
+ if (dirname)
+ {
+ gchar *basename = g_path_get_basename (dirname);
+ gint i;
+
+ for (i = 0; i < G_N_ELEMENTS (tag_blacklist); i++)
+ {
+ if (! strcmp (basename, tag_blacklist[i]))
+ break;
+ }
+
+ if (i == G_N_ELEMENTS (tag_blacklist))
+ {
+ GimpTag *tag = gimp_tag_new (basename);
+
+ gimp_tag_set_internal (tag, TRUE);
+ gimp_tagged_add_tag (GIMP_TAGGED (data), tag);
+ g_object_unref (tag);
+ }
+
+ g_free (basename);
+ g_free (dirname);
+ }
+}
+
+const gchar *
+gimp_data_get_mime_type (GimpData *data)
+{
+ GimpDataPrivate *private;
+
+ g_return_val_if_fail (GIMP_IS_DATA (data), NULL);
+
+ private = GIMP_DATA_GET_PRIVATE (data);
+
+ return g_quark_to_string (private->mime_type);
+}
+
+gboolean
+gimp_data_is_writable (GimpData *data)
+{
+ GimpDataPrivate *private;
+
+ g_return_val_if_fail (GIMP_IS_DATA (data), FALSE);
+
+ private = GIMP_DATA_GET_PRIVATE (data);
+
+ return private->writable;
+}
+
+gboolean
+gimp_data_is_deletable (GimpData *data)
+{
+ GimpDataPrivate *private;
+
+ g_return_val_if_fail (GIMP_IS_DATA (data), FALSE);
+
+ private = GIMP_DATA_GET_PRIVATE (data);
+
+ return private->deletable;
+}
+
+void
+gimp_data_set_mtime (GimpData *data,
+ gint64 mtime)
+{
+ GimpDataPrivate *private;
+
+ g_return_if_fail (GIMP_IS_DATA (data));
+
+ private = GIMP_DATA_GET_PRIVATE (data);
+
+ private->mtime = mtime;
+}
+
+gint64
+gimp_data_get_mtime (GimpData *data)
+{
+ GimpDataPrivate *private;
+
+ g_return_val_if_fail (GIMP_IS_DATA (data), 0);
+
+ private = GIMP_DATA_GET_PRIVATE (data);
+
+ return private->mtime;
+}
+
+gboolean
+gimp_data_is_copyable (GimpData *data)
+{
+ g_return_val_if_fail (GIMP_IS_DATA (data), FALSE);
+
+ return GIMP_DATA_GET_CLASS (data)->copy != NULL;
+}
+
+/**
+ * gimp_data_copy:
+ * @data: a #GimpData object
+ * @src_data: the #GimpData object to copy from
+ *
+ * Copies @src_data to @data. Only the object data is copied: the
+ * name, file name, preview, etc. are not copied.
+ **/
+void
+gimp_data_copy (GimpData *data,
+ GimpData *src_data)
+{
+ g_return_if_fail (GIMP_IS_DATA (data));
+ g_return_if_fail (GIMP_IS_DATA (src_data));
+ g_return_if_fail (GIMP_DATA_GET_CLASS (data)->copy != NULL);
+ g_return_if_fail (GIMP_DATA_GET_CLASS (data)->copy ==
+ GIMP_DATA_GET_CLASS (src_data)->copy);
+
+ if (data != src_data)
+ GIMP_DATA_GET_CLASS (data)->copy (data, src_data);
+}
+
+gboolean
+gimp_data_is_duplicatable (GimpData *data)
+{
+ g_return_val_if_fail (GIMP_IS_DATA (data), FALSE);
+
+ if (GIMP_DATA_GET_CLASS (data)->duplicate == gimp_data_real_duplicate)
+ return gimp_data_is_copyable (data);
+ else
+ return GIMP_DATA_GET_CLASS (data)->duplicate != NULL;
+}
+
+/**
+ * gimp_data_duplicate:
+ * @data: a #GimpData object
+ *
+ * Creates a copy of @data, if possible. Only the object data is
+ * copied: the newly created object is not automatically given an
+ * object name, file name, preview, etc.
+ *
+ * Returns: the newly created copy, or %NULL if @data cannot be copied.
+ **/
+GimpData *
+gimp_data_duplicate (GimpData *data)
+{
+ g_return_val_if_fail (GIMP_IS_DATA (data), NULL);
+
+ if (gimp_data_is_duplicatable (data))
+ {
+ GimpData *new = GIMP_DATA_GET_CLASS (data)->duplicate (data);
+ GimpDataPrivate *private = GIMP_DATA_GET_PRIVATE (new);
+
+ g_object_set (new,
+ "name", NULL,
+ "writable", GIMP_DATA_GET_CLASS (new)->save != NULL,
+ "deletable", TRUE,
+ NULL);
+
+ g_clear_object (&private->file);
+
+ return new;
+ }
+
+ return NULL;
+}
+
+/**
+ * gimp_data_make_internal:
+ * @data: a #GimpData object.
+ *
+ * Mark @data as "internal" to Gimp, which means that it will not be
+ * saved to disk. Note that if you do this, later calls to
+ * gimp_data_save() and gimp_data_delete_from_disk() will
+ * automatically return successfully without giving any warning.
+ *
+ * The identifier name shall be an untranslated globally unique string
+ * that identifies the internal object across sessions.
+ **/
+void
+gimp_data_make_internal (GimpData *data,
+ const gchar *identifier)
+{
+ GimpDataPrivate *private;
+
+ g_return_if_fail (GIMP_IS_DATA (data));
+
+ private = GIMP_DATA_GET_PRIVATE (data);
+
+ g_clear_object (&private->file);
+
+ g_free (private->identifier);
+ private->identifier = g_strdup (identifier);
+
+ private->writable = FALSE;
+ private->deletable = FALSE;
+ private->internal = TRUE;
+}
+
+gboolean
+gimp_data_is_internal (GimpData *data)
+{
+ GimpDataPrivate *private;
+
+ g_return_val_if_fail (GIMP_IS_DATA (data), FALSE);
+
+ private = GIMP_DATA_GET_PRIVATE (data);
+
+ return private->internal;
+}
+
+/**
+ * gimp_data_compare:
+ * @data1: a #GimpData object.
+ * @data2: another #GimpData object.
+ *
+ * Compares two data objects for use in sorting. Objects marked as
+ * "internal" come first, then user-writable objects, then system data
+ * files. In these three groups, the objects are sorted alphabetically
+ * by name, using gimp_object_name_collate().
+ *
+ * Return value: -1 if @data1 compares before @data2,
+ * 0 if they compare equal,
+ * 1 if @data1 compares after @data2.
+ **/
+gint
+gimp_data_compare (GimpData *data1,
+ GimpData *data2)
+{
+ g_return_val_if_fail (GIMP_IS_DATA (data1), 0);
+ g_return_val_if_fail (GIMP_IS_DATA (data2), 0);
+ g_return_val_if_fail (GIMP_DATA_GET_CLASS (data1)->compare ==
+ GIMP_DATA_GET_CLASS (data2)->compare, 0);
+
+ return GIMP_DATA_GET_CLASS (data1)->compare (data1, data2);
+}
+
+/**
+ * gimp_data_error_quark:
+ *
+ * This function is used to implement the GIMP_DATA_ERROR macro. It
+ * shouldn't be called directly.
+ *
+ * Return value: the #GQuark to identify error in the GimpData error domain.
+ **/
+GQuark
+gimp_data_error_quark (void)
+{
+ return g_quark_from_static_string ("gimp-data-error-quark");
+}
diff --git a/app/core/gimpdata.h b/app/core/gimpdata.h
new file mode 100644
index 0000000..44cd26c
--- /dev/null
+++ b/app/core/gimpdata.h
@@ -0,0 +1,133 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpdata.h
+ * Copyright (C) 2001 Michael Natterer <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_DATA_H__
+#define __GIMP_DATA_H__
+
+
+#include "gimpviewable.h"
+
+
+typedef enum
+{
+ GIMP_DATA_ERROR_OPEN, /* opening data file failed */
+ GIMP_DATA_ERROR_READ, /* reading data file failed */
+ GIMP_DATA_ERROR_WRITE, /* writing data file failed */
+ GIMP_DATA_ERROR_DELETE /* deleting data file failed */
+} GimpDataError;
+
+
+#define GIMP_TYPE_DATA (gimp_data_get_type ())
+#define GIMP_DATA(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_DATA, GimpData))
+#define GIMP_DATA_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_DATA, GimpDataClass))
+#define GIMP_IS_DATA(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_DATA))
+#define GIMP_IS_DATA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_DATA))
+#define GIMP_DATA_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_DATA, GimpDataClass))
+
+
+typedef struct _GimpDataPrivate GimpDataPrivate;
+typedef struct _GimpDataClass GimpDataClass;
+
+struct _GimpData
+{
+ GimpViewable parent_instance;
+
+ GimpDataPrivate *priv;
+};
+
+struct _GimpDataClass
+{
+ GimpViewableClass parent_class;
+
+ /* signals */
+ void (* dirty) (GimpData *data);
+
+ /* virtual functions */
+ gboolean (* save) (GimpData *data,
+ GOutputStream *output,
+ GError **error);
+ const gchar * (* get_extension) (GimpData *data);
+ void (* copy) (GimpData *data,
+ GimpData *src_data);
+ GimpData * (* duplicate) (GimpData *data);
+ gint (* compare) (GimpData *data1,
+ GimpData *data2);
+};
+
+
+GType gimp_data_get_type (void) G_GNUC_CONST;
+
+gboolean gimp_data_save (GimpData *data,
+ GError **error);
+
+void gimp_data_dirty (GimpData *data);
+void gimp_data_clean (GimpData *data);
+gboolean gimp_data_is_dirty (GimpData *data);
+
+void gimp_data_freeze (GimpData *data);
+void gimp_data_thaw (GimpData *data);
+gboolean gimp_data_is_frozen (GimpData *data);
+
+gboolean gimp_data_delete_from_disk (GimpData *data,
+ GError **error);
+
+const gchar * gimp_data_get_extension (GimpData *data);
+
+void gimp_data_set_file (GimpData *data,
+ GFile *file,
+ gboolean writable,
+ gboolean deletable);
+GFile * gimp_data_get_file (GimpData *data);
+
+void gimp_data_create_filename (GimpData *data,
+ GFile *dest_dir);
+
+void gimp_data_set_folder_tags (GimpData *data,
+ GFile *top_directory);
+
+const gchar * gimp_data_get_mime_type (GimpData *data);
+
+gboolean gimp_data_is_writable (GimpData *data);
+gboolean gimp_data_is_deletable (GimpData *data);
+
+void gimp_data_set_mtime (GimpData *data,
+ gint64 mtime);
+gint64 gimp_data_get_mtime (GimpData *data);
+
+gboolean gimp_data_is_copyable (GimpData *data);
+void gimp_data_copy (GimpData *data,
+ GimpData *src_data);
+
+gboolean gimp_data_is_duplicatable (GimpData *data);
+GimpData * gimp_data_duplicate (GimpData *data);
+
+void gimp_data_make_internal (GimpData *data,
+ const gchar *identifier);
+gboolean gimp_data_is_internal (GimpData *data);
+
+gint gimp_data_compare (GimpData *data1,
+ GimpData *data2);
+
+#define GIMP_DATA_ERROR (gimp_data_error_quark ())
+
+GQuark gimp_data_error_quark (void) G_GNUC_CONST;
+
+
+#endif /* __GIMP_DATA_H__ */
diff --git a/app/core/gimpdatafactory.c b/app/core/gimpdatafactory.c
new file mode 100644
index 0000000..5ee7114
--- /dev/null
+++ b/app/core/gimpdatafactory.c
@@ -0,0 +1,945 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpdatafactory.c
+ * Copyright (C) 2001-2018 Michael Natterer <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+#include <stdlib.h>
+
+#include "libgimpbase/gimpbase.h"
+#include "libgimpmath/gimpmath.h"
+#include "libgimpconfig/gimpconfig.h"
+
+#include "core-types.h"
+
+#include "gimp.h"
+#include "gimp-utils.h"
+#include "gimpasyncset.h"
+#include "gimpcancelable.h"
+#include "gimpcontext.h"
+#include "gimpdata.h"
+#include "gimpdatafactory.h"
+#include "gimplist.h"
+#include "gimpuncancelablewaitable.h"
+#include "gimpwaitable.h"
+
+#include "gimp-intl.h"
+
+
+enum
+{
+ PROP_0,
+ PROP_GIMP,
+ PROP_DATA_TYPE,
+ PROP_PATH_PROPERTY_NAME,
+ PROP_WRITABLE_PROPERTY_NAME,
+ PROP_NEW_FUNC,
+ PROP_GET_STANDARD_FUNC
+};
+
+
+struct _GimpDataFactoryPrivate
+{
+ Gimp *gimp;
+
+ GType data_type;
+ GimpContainer *container;
+ GimpContainer *container_obsolete;
+
+ gchar *path_property_name;
+ gchar *writable_property_name;
+
+ GimpDataNewFunc data_new_func;
+ GimpDataGetStandardFunc data_get_standard_func;
+
+ GimpAsyncSet *async_set;
+};
+
+#define GET_PRIVATE(obj) (((GimpDataFactory *) (obj))->priv)
+
+
+static void gimp_data_factory_constructed (GObject *object);
+static void gimp_data_factory_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_data_factory_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+static void gimp_data_factory_finalize (GObject *object);
+
+static gint64 gimp_data_factory_get_memsize (GimpObject *object,
+ gint64 *gui_size);
+
+static void gimp_data_factory_real_data_save (GimpDataFactory *factory);
+static void gimp_data_factory_real_data_cancel (GimpDataFactory *factory);
+static GimpData * gimp_data_factory_real_data_duplicate (GimpDataFactory *factory,
+ GimpData *data);
+static gboolean gimp_data_factory_real_data_delete (GimpDataFactory *factory,
+ GimpData *data,
+ gboolean delete_from_disk,
+ GError **error);
+
+static void gimp_data_factory_path_notify (GObject *object,
+ const GParamSpec *pspec,
+ GimpDataFactory *factory);
+static GFile * gimp_data_factory_get_save_dir (GimpDataFactory *factory,
+ GError **error);
+
+
+G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (GimpDataFactory, gimp_data_factory,
+ GIMP_TYPE_OBJECT)
+
+#define parent_class gimp_data_factory_parent_class
+
+
+static void
+gimp_data_factory_class_init (GimpDataFactoryClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpObjectClass *gimp_object_class = GIMP_OBJECT_CLASS (klass);
+
+ object_class->constructed = gimp_data_factory_constructed;
+ object_class->set_property = gimp_data_factory_set_property;
+ object_class->get_property = gimp_data_factory_get_property;
+ object_class->finalize = gimp_data_factory_finalize;
+
+ gimp_object_class->get_memsize = gimp_data_factory_get_memsize;
+
+ klass->data_init = NULL;
+ klass->data_refresh = NULL;
+ klass->data_save = gimp_data_factory_real_data_save;
+ klass->data_cancel = gimp_data_factory_real_data_cancel;
+ klass->data_duplicate = gimp_data_factory_real_data_duplicate;
+ klass->data_delete = gimp_data_factory_real_data_delete;
+
+ g_object_class_install_property (object_class, PROP_GIMP,
+ g_param_spec_object ("gimp", NULL, NULL,
+ GIMP_TYPE_GIMP,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+
+ g_object_class_install_property (object_class, PROP_DATA_TYPE,
+ g_param_spec_gtype ("data-type", NULL, NULL,
+ GIMP_TYPE_DATA,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+
+ g_object_class_install_property (object_class, PROP_PATH_PROPERTY_NAME,
+ g_param_spec_string ("path-property-name",
+ NULL, NULL,
+ NULL,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+
+ g_object_class_install_property (object_class, PROP_WRITABLE_PROPERTY_NAME,
+ g_param_spec_string ("writable-property-name",
+ NULL, NULL,
+ NULL,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+
+ g_object_class_install_property (object_class, PROP_NEW_FUNC,
+ g_param_spec_pointer ("new-func",
+ NULL, NULL,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+
+ g_object_class_install_property (object_class, PROP_GET_STANDARD_FUNC,
+ g_param_spec_pointer ("get-standard-func",
+ NULL, NULL,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+}
+
+static void
+gimp_data_factory_init (GimpDataFactory *factory)
+{
+ factory->priv = gimp_data_factory_get_instance_private (factory);
+
+ factory->priv->async_set = gimp_async_set_new ();
+}
+
+static void
+gimp_data_factory_constructed (GObject *object)
+{
+ GimpDataFactoryPrivate *priv = GET_PRIVATE (object);
+
+ G_OBJECT_CLASS (parent_class)->constructed (object);
+
+ gimp_assert (GIMP_IS_GIMP (priv->gimp));
+ gimp_assert (g_type_is_a (priv->data_type, GIMP_TYPE_DATA));
+ gimp_assert (GIMP_DATA_FACTORY_GET_CLASS (object)->data_init != NULL);
+ gimp_assert (GIMP_DATA_FACTORY_GET_CLASS (object)->data_refresh != NULL);
+
+ priv->container = gimp_list_new (priv->data_type, TRUE);
+ gimp_list_set_sort_func (GIMP_LIST (priv->container),
+ (GCompareFunc) gimp_data_compare);
+
+ priv->container_obsolete = gimp_list_new (priv->data_type, TRUE);
+ gimp_list_set_sort_func (GIMP_LIST (priv->container_obsolete),
+ (GCompareFunc) gimp_data_compare);
+}
+
+static void
+gimp_data_factory_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpDataFactoryPrivate *priv = GET_PRIVATE (object);
+
+ switch (property_id)
+ {
+ case PROP_GIMP:
+ priv->gimp = g_value_get_object (value); /* don't ref */
+ break;
+
+ case PROP_DATA_TYPE:
+ priv->data_type = g_value_get_gtype (value);
+ break;
+
+ case PROP_PATH_PROPERTY_NAME:
+ priv->path_property_name = g_value_dup_string (value);
+ break;
+
+ case PROP_WRITABLE_PROPERTY_NAME:
+ priv->writable_property_name = g_value_dup_string (value);
+ break;
+
+ case PROP_NEW_FUNC:
+ priv->data_new_func = g_value_get_pointer (value);
+ break;
+
+ case PROP_GET_STANDARD_FUNC:
+ priv->data_get_standard_func = g_value_get_pointer (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_data_factory_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpDataFactoryPrivate *priv = GET_PRIVATE (object);
+
+ switch (property_id)
+ {
+ case PROP_GIMP:
+ g_value_set_object (value, priv->gimp);
+ break;
+
+ case PROP_DATA_TYPE:
+ g_value_set_gtype (value, priv->data_type);
+ break;
+
+ case PROP_PATH_PROPERTY_NAME:
+ g_value_set_string (value, priv->path_property_name);
+ break;
+
+ case PROP_WRITABLE_PROPERTY_NAME:
+ g_value_set_string (value, priv->writable_property_name);
+ break;
+
+ case PROP_NEW_FUNC:
+ g_value_set_pointer (value, priv->data_new_func);
+ break;
+
+ case PROP_GET_STANDARD_FUNC:
+ g_value_set_pointer (value, priv->data_get_standard_func);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_data_factory_finalize (GObject *object)
+{
+ GimpDataFactory *factory = GIMP_DATA_FACTORY (object);
+ GimpDataFactoryPrivate *priv = GET_PRIVATE (object);
+
+ if (priv->async_set)
+ {
+ gimp_data_factory_data_cancel (factory);
+
+ g_clear_object (&priv->async_set);
+ }
+
+ g_clear_object (&priv->container);
+ g_clear_object (&priv->container_obsolete);
+
+ g_clear_pointer (&priv->path_property_name, g_free);
+ g_clear_pointer (&priv->writable_property_name, g_free);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static gint64
+gimp_data_factory_get_memsize (GimpObject *object,
+ gint64 *gui_size)
+{
+ GimpDataFactoryPrivate *priv = GET_PRIVATE (object);
+ gint64 memsize = 0;
+
+ memsize += gimp_object_get_memsize (GIMP_OBJECT (priv->container),
+ gui_size);
+ memsize += gimp_object_get_memsize (GIMP_OBJECT (priv->container_obsolete),
+ gui_size);
+
+ return memsize + GIMP_OBJECT_CLASS (parent_class)->get_memsize (object,
+ gui_size);
+}
+
+static void
+gimp_data_factory_real_data_save (GimpDataFactory *factory)
+{
+ GimpDataFactoryPrivate *priv = GET_PRIVATE (factory);
+ GList *dirty = NULL;
+ GList *list;
+ GFile *writable_dir;
+ GError *error = NULL;
+
+ for (list = GIMP_LIST (priv->container)->queue->head;
+ list;
+ list = g_list_next (list))
+ {
+ GimpData *data = list->data;
+
+ if (gimp_data_is_dirty (data) &&
+ gimp_data_is_writable (data))
+ {
+ dirty = g_list_prepend (dirty, data);
+ }
+ }
+
+ if (! dirty)
+ return;
+
+ writable_dir = gimp_data_factory_get_save_dir (factory, &error);
+
+ if (! writable_dir)
+ {
+ gimp_message (priv->gimp, NULL, GIMP_MESSAGE_ERROR,
+ _("Failed to save data:\n\n%s"),
+ error->message);
+ g_clear_error (&error);
+
+ g_list_free (dirty);
+
+ return;
+ }
+
+ for (list = dirty; list; list = g_list_next (list))
+ {
+ GimpData *data = list->data;
+ GError *error = NULL;
+
+ if (! gimp_data_get_file (data))
+ gimp_data_create_filename (data, writable_dir);
+
+ if (factory->priv->gimp->be_verbose)
+ {
+ GFile *file = gimp_data_get_file (data);
+
+ if (file)
+ g_print ("Writing dirty data '%s'\n",
+ gimp_file_get_utf8_name (file));
+ }
+
+ if (! gimp_data_save (data, &error))
+ {
+ /* check if there actually was an error (no error
+ * means the data class does not implement save)
+ */
+ if (error)
+ {
+ gimp_message (priv->gimp, NULL, GIMP_MESSAGE_ERROR,
+ _("Failed to save data:\n\n%s"),
+ error->message);
+ g_clear_error (&error);
+ }
+ }
+ }
+
+ g_object_unref (writable_dir);
+
+ g_list_free (dirty);
+}
+
+static void
+gimp_data_factory_real_data_cancel (GimpDataFactory *factory)
+{
+ GimpDataFactoryPrivate *priv = GET_PRIVATE (factory);
+
+ gimp_cancelable_cancel (GIMP_CANCELABLE (priv->async_set));
+ gimp_waitable_wait (GIMP_WAITABLE (priv->async_set));
+}
+
+static GimpData *
+gimp_data_factory_real_data_duplicate (GimpDataFactory *factory,
+ GimpData *data)
+{
+ GimpDataFactoryPrivate *priv = GET_PRIVATE (factory);
+ GimpData *new_data;
+
+ new_data = gimp_data_duplicate (data);
+
+ if (new_data)
+ {
+ const gchar *name = gimp_object_get_name (data);
+ gchar *ext;
+ gint copy_len;
+ gint number;
+ gchar *new_name;
+
+ ext = strrchr (name, '#');
+ copy_len = strlen (_("copy"));
+
+ if ((strlen (name) >= copy_len &&
+ strcmp (&name[strlen (name) - copy_len], _("copy")) == 0) ||
+ (ext && (number = atoi (ext + 1)) > 0 &&
+ ((gint) (log10 (number) + 1)) == strlen (ext + 1)))
+ {
+ /* don't have redundant "copy"s */
+ new_name = g_strdup (name);
+ }
+ else
+ {
+ new_name = g_strdup_printf (_("%s copy"), name);
+ }
+
+ gimp_object_take_name (GIMP_OBJECT (new_data), new_name);
+
+ gimp_container_add (priv->container, GIMP_OBJECT (new_data));
+ g_object_unref (new_data);
+ }
+
+ return new_data;
+}
+
+static gboolean
+gimp_data_factory_real_data_delete (GimpDataFactory *factory,
+ GimpData *data,
+ gboolean delete_from_disk,
+ GError **error)
+{
+ if (delete_from_disk && gimp_data_get_file (data))
+ return gimp_data_delete_from_disk (data, error);
+
+ return TRUE;
+}
+
+
+/* public functions */
+
+void
+gimp_data_factory_data_init (GimpDataFactory *factory,
+ GimpContext *context,
+ gboolean no_data)
+{
+ GimpDataFactoryPrivate *priv = GET_PRIVATE (factory);
+ gchar *signal_name;
+
+ g_return_if_fail (GIMP_IS_DATA_FACTORY (factory));
+ g_return_if_fail (GIMP_IS_CONTEXT (context));
+
+ /* Always freeze() and thaw() the container around initialization,
+ * even if no_data, the thaw() will implicitly make GimpContext
+ * create the standard data that serves as fallback.
+ */
+ gimp_container_freeze (priv->container);
+
+ if (! no_data)
+ {
+ if (priv->gimp->be_verbose)
+ {
+ const gchar *name = gimp_object_get_name (factory);
+
+ g_print ("Loading '%s' data\n", name ? name : "???");
+ }
+
+ GIMP_DATA_FACTORY_GET_CLASS (factory)->data_init (factory, context);
+ }
+
+ gimp_container_thaw (priv->container);
+
+ signal_name = g_strdup_printf ("notify::%s", priv->path_property_name);
+ g_signal_connect_object (priv->gimp->config, signal_name,
+ G_CALLBACK (gimp_data_factory_path_notify),
+ factory, 0);
+ g_free (signal_name);
+}
+
+static void
+gimp_data_factory_clean_cb (GimpDataFactory *factory,
+ GimpData *data,
+ gpointer user_data)
+{
+ if (gimp_data_is_dirty (data))
+ gimp_data_clean (data);
+}
+
+void
+gimp_data_factory_data_clean (GimpDataFactory *factory)
+{
+ g_return_if_fail (GIMP_IS_DATA_FACTORY (factory));
+
+ gimp_data_factory_data_foreach (factory, TRUE,
+ gimp_data_factory_clean_cb, NULL);
+}
+
+void
+gimp_data_factory_data_refresh (GimpDataFactory *factory,
+ GimpContext *context)
+{
+ g_return_if_fail (GIMP_IS_DATA_FACTORY (factory));
+ g_return_if_fail (GIMP_IS_CONTEXT (context));
+
+ GIMP_DATA_FACTORY_GET_CLASS (factory)->data_refresh (factory, context);
+}
+
+void
+gimp_data_factory_data_save (GimpDataFactory *factory)
+{
+ g_return_if_fail (GIMP_IS_DATA_FACTORY (factory));
+
+ if (! gimp_container_is_empty (factory->priv->container))
+ GIMP_DATA_FACTORY_GET_CLASS (factory)->data_save (factory);
+}
+
+static void
+gimp_data_factory_data_free_foreach (GimpDataFactory *factory,
+ GimpData *data,
+ gpointer user_data)
+{
+ gimp_container_remove (factory->priv->container, GIMP_OBJECT (data));
+}
+
+void
+gimp_data_factory_data_free (GimpDataFactory *factory)
+{
+ g_return_if_fail (GIMP_IS_DATA_FACTORY (factory));
+
+ gimp_data_factory_data_cancel (factory);
+
+ if (! gimp_container_is_empty (factory->priv->container))
+ {
+ gimp_container_freeze (factory->priv->container);
+
+ gimp_data_factory_data_foreach (factory, TRUE,
+ gimp_data_factory_data_free_foreach,
+ NULL);
+
+ gimp_container_thaw (factory->priv->container);
+ }
+}
+
+GimpAsyncSet *
+gimp_data_factory_get_async_set (GimpDataFactory *factory)
+{
+ g_return_val_if_fail (GIMP_IS_DATA_FACTORY (factory), NULL);
+
+ return factory->priv->async_set;
+}
+
+gboolean
+gimp_data_factory_data_wait (GimpDataFactory *factory)
+{
+ GimpDataFactoryPrivate *priv;
+ GimpWaitable *waitable;
+
+ g_return_val_if_fail (GIMP_IS_DATA_FACTORY (factory), FALSE);
+
+ priv = GET_PRIVATE (factory);
+
+ /* don't allow cancellation for now */
+ waitable = gimp_uncancelable_waitable_new (GIMP_WAITABLE (priv->async_set));
+
+ gimp_wait (priv->gimp, waitable,
+ _("Loading fonts (this may take a while...)"));
+
+ g_object_unref (waitable);
+
+ return TRUE;
+}
+
+void
+gimp_data_factory_data_cancel (GimpDataFactory *factory)
+{
+ g_return_if_fail (GIMP_IS_DATA_FACTORY (factory));
+
+ GIMP_DATA_FACTORY_GET_CLASS (factory)->data_cancel (factory);
+}
+
+gboolean
+gimp_data_factory_has_data_new_func (GimpDataFactory *factory)
+{
+ g_return_val_if_fail (GIMP_IS_DATA_FACTORY (factory), FALSE);
+
+ return factory->priv->data_new_func != NULL;
+}
+
+GimpData *
+gimp_data_factory_data_new (GimpDataFactory *factory,
+ GimpContext *context,
+ const gchar *name)
+{
+ GimpDataFactoryPrivate *priv;
+
+ g_return_val_if_fail (GIMP_IS_DATA_FACTORY (factory), NULL);
+ g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL);
+ g_return_val_if_fail (name != NULL, NULL);
+ g_return_val_if_fail (*name != '\0', NULL);
+
+ priv = GET_PRIVATE (factory);
+
+ if (priv->data_new_func)
+ {
+ GimpData *data = priv->data_new_func (context, name);
+
+ if (data)
+ {
+ gimp_container_add (priv->container, GIMP_OBJECT (data));
+ g_object_unref (data);
+
+ return data;
+ }
+
+ g_warning ("%s: GimpDataFactory::data_new_func() returned NULL",
+ G_STRFUNC);
+ }
+
+ return NULL;
+}
+
+GimpData *
+gimp_data_factory_data_get_standard (GimpDataFactory *factory,
+ GimpContext *context)
+{
+ g_return_val_if_fail (GIMP_IS_DATA_FACTORY (factory), NULL);
+ g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL);
+
+ if (factory->priv->data_get_standard_func)
+ return factory->priv->data_get_standard_func (context);
+
+ return NULL;
+}
+
+GimpData *
+gimp_data_factory_data_duplicate (GimpDataFactory *factory,
+ GimpData *data)
+{
+ g_return_val_if_fail (GIMP_IS_DATA_FACTORY (factory), NULL);
+ g_return_val_if_fail (GIMP_IS_DATA (data), NULL);
+
+ return GIMP_DATA_FACTORY_GET_CLASS (factory)->data_duplicate (factory, data);
+}
+
+gboolean
+gimp_data_factory_data_delete (GimpDataFactory *factory,
+ GimpData *data,
+ gboolean delete_from_disk,
+ GError **error)
+{
+ GimpDataFactoryPrivate *priv;
+ gboolean retval = TRUE;
+
+ g_return_val_if_fail (GIMP_IS_DATA_FACTORY (factory), FALSE);
+ g_return_val_if_fail (GIMP_IS_DATA (data), FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ priv = GET_PRIVATE (factory);
+
+ if (gimp_container_have (priv->container, GIMP_OBJECT (data)))
+ {
+ g_object_ref (data);
+
+ gimp_container_remove (priv->container, GIMP_OBJECT (data));
+
+ retval = GIMP_DATA_FACTORY_GET_CLASS (factory)->data_delete (factory, data,
+ delete_from_disk,
+ error);
+
+ g_object_unref (data);
+ }
+
+ return retval;
+}
+
+gboolean
+gimp_data_factory_data_save_single (GimpDataFactory *factory,
+ GimpData *data,
+ GError **error)
+{
+ g_return_val_if_fail (GIMP_IS_DATA_FACTORY (factory), FALSE);
+ g_return_val_if_fail (GIMP_IS_DATA (data), FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ if (! gimp_data_is_dirty (data))
+ return TRUE;
+
+ if (! gimp_data_get_file (data))
+ {
+ GFile *writable_dir;
+ GError *my_error = NULL;
+
+ writable_dir = gimp_data_factory_get_save_dir (factory, &my_error);
+
+ if (! writable_dir)
+ {
+ g_set_error (error, GIMP_DATA_ERROR, 0,
+ _("Failed to save data:\n\n%s"),
+ my_error->message);
+ g_clear_error (&my_error);
+
+ return FALSE;
+ }
+
+ gimp_data_create_filename (data, writable_dir);
+
+ g_object_unref (writable_dir);
+ }
+
+ if (! gimp_data_is_writable (data))
+ return FALSE;
+
+ if (factory->priv->gimp->be_verbose)
+ {
+ GFile *file = gimp_data_get_file (data);
+
+ if (file)
+ g_print ("Writing dirty data '%s'\n",
+ gimp_file_get_utf8_name (file));
+ }
+
+ if (! gimp_data_save (data, error))
+ {
+ /* check if there actually was an error (no error
+ * means the data class does not implement save)
+ */
+ if (! error)
+ g_set_error (error, GIMP_DATA_ERROR, 0,
+ _("Failed to save data:\n\n%s"),
+ "Data class does not implement saving");
+
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+void
+gimp_data_factory_data_foreach (GimpDataFactory *factory,
+ gboolean skip_internal,
+ GimpDataForeachFunc callback,
+ gpointer user_data)
+{
+ GList *list;
+
+ g_return_if_fail (GIMP_IS_DATA_FACTORY (factory));
+ g_return_if_fail (callback != NULL);
+
+ list = GIMP_LIST (factory->priv->container)->queue->head;
+
+ while (list)
+ {
+ GList *next = g_list_next (list);
+
+ if (! (skip_internal && gimp_data_is_internal (list->data)))
+ callback (factory, list->data, user_data);
+
+ list = next;
+ }
+}
+
+Gimp *
+gimp_data_factory_get_gimp (GimpDataFactory *factory)
+{
+ g_return_val_if_fail (GIMP_IS_DATA_FACTORY (factory), NULL);
+
+ return factory->priv->gimp;
+}
+
+GType
+gimp_data_factory_get_data_type (GimpDataFactory *factory)
+{
+ g_return_val_if_fail (GIMP_IS_DATA_FACTORY (factory), G_TYPE_NONE);
+
+ return gimp_container_get_children_type (factory->priv->container);
+}
+
+GimpContainer *
+gimp_data_factory_get_container (GimpDataFactory *factory)
+{
+ g_return_val_if_fail (GIMP_IS_DATA_FACTORY (factory), NULL);
+
+ return factory->priv->container;
+}
+
+GimpContainer *
+gimp_data_factory_get_container_obsolete (GimpDataFactory *factory)
+{
+ g_return_val_if_fail (GIMP_IS_DATA_FACTORY (factory), NULL);
+
+ return factory->priv->container_obsolete;
+}
+
+GList *
+gimp_data_factory_get_data_path (GimpDataFactory *factory)
+{
+ GimpDataFactoryPrivate *priv = GET_PRIVATE (factory);
+ gchar *path = NULL;
+ GList *list = NULL;
+
+ g_return_val_if_fail (GIMP_IS_DATA_FACTORY (factory), NULL);
+
+ g_object_get (priv->gimp->config,
+ priv->path_property_name, &path,
+ NULL);
+
+ if (path)
+ {
+ list = gimp_config_path_expand_to_files (path, NULL);
+ g_free (path);
+ }
+
+ return list;
+}
+
+GList *
+gimp_data_factory_get_data_path_writable (GimpDataFactory *factory)
+{
+ GimpDataFactoryPrivate *priv = GET_PRIVATE (factory);
+ gchar *path = NULL;
+ GList *list = NULL;
+
+ g_return_val_if_fail (GIMP_IS_DATA_FACTORY (factory), NULL);
+
+ g_object_get (priv->gimp->config,
+ priv->writable_property_name, &path,
+ NULL);
+
+ if (path)
+ {
+ list = gimp_config_path_expand_to_files (path, NULL);
+ g_free (path);
+ }
+
+ return list;
+}
+
+
+/* private functions */
+
+static void
+gimp_data_factory_path_notify (GObject *object,
+ const GParamSpec *pspec,
+ GimpDataFactory *factory)
+{
+ GimpDataFactoryPrivate *priv = GET_PRIVATE (factory);
+
+ gimp_set_busy (priv->gimp);
+
+ gimp_data_factory_data_refresh (factory, gimp_get_user_context (priv->gimp));
+
+ gimp_unset_busy (priv->gimp);
+}
+
+static GFile *
+gimp_data_factory_get_save_dir (GimpDataFactory *factory,
+ GError **error)
+{
+ GList *path;
+ GList *writable_path;
+ GFile *writable_dir = NULL;
+
+ path = gimp_data_factory_get_data_path (factory);
+ writable_path = gimp_data_factory_get_data_path_writable (factory);
+
+ if (writable_path)
+ {
+ GList *list;
+ gboolean found_any = FALSE;
+
+ for (list = writable_path; list; list = g_list_next (list))
+ {
+ GList *found = g_list_find_custom (path, list->data,
+ (GCompareFunc) gimp_file_compare);
+ if (found)
+ {
+ GFile *dir = found->data;
+
+ found_any = TRUE;
+
+ if (g_file_query_file_type (dir, G_FILE_QUERY_INFO_NONE,
+ NULL) != G_FILE_TYPE_DIRECTORY)
+ {
+ /* error out only if this is the last chance */
+ if (! list->next)
+ {
+ g_set_error (error, GIMP_DATA_ERROR, 0,
+ _("You have a writable data folder "
+ "configured (%s), but this folder does "
+ "not exist. Please create the folder or "
+ "fix your configuration in the "
+ "Preferences dialog's 'Folders' section."),
+ gimp_file_get_utf8_name (dir));
+ }
+ }
+ else
+ {
+ writable_dir = g_object_ref (dir);
+ break;
+ }
+ }
+ }
+
+ if (! writable_dir && ! found_any)
+ {
+ g_set_error (error, GIMP_DATA_ERROR, 0,
+ _("You have a writable data folder configured, but this "
+ "folder is not part of your data search path. You "
+ "probably edited the gimprc file manually, "
+ "please fix it in the Preferences dialog's 'Folders' "
+ "section."));
+ }
+ }
+ else
+ {
+ g_set_error (error, GIMP_DATA_ERROR, 0,
+ _("You don't have any writable data folder configured."));
+ }
+
+ g_list_free_full (path, (GDestroyNotify) g_object_unref);
+ g_list_free_full (writable_path, (GDestroyNotify) g_object_unref);
+
+ return writable_dir;
+}
diff --git a/app/core/gimpdatafactory.h b/app/core/gimpdatafactory.h
new file mode 100644
index 0000000..47c57ae
--- /dev/null
+++ b/app/core/gimpdatafactory.h
@@ -0,0 +1,125 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpdatafactory.h
+ * Copyright (C) 2001-2018 Michael Natterer <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_DATA_FACTORY_H__
+#define __GIMP_DATA_FACTORY_H__
+
+
+#include "gimpobject.h"
+
+
+typedef GimpData * (* GimpDataNewFunc) (GimpContext *context,
+ const gchar *name);
+typedef GimpData * (* GimpDataGetStandardFunc) (GimpContext *context);
+
+typedef void (* GimpDataForeachFunc) (GimpDataFactory *factory,
+ GimpData *data,
+ gpointer user_data);
+
+
+#define GIMP_TYPE_DATA_FACTORY (gimp_data_factory_get_type ())
+#define GIMP_DATA_FACTORY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_DATA_FACTORY, GimpDataFactory))
+#define GIMP_DATA_FACTORY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_DATA_FACTORY, GimpDataFactoryClass))
+#define GIMP_IS_DATA_FACTORY(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_DATA_FACTORY))
+#define GIMP_IS_DATA_FACTORY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_DATA_FACTORY))
+#define GIMP_DATA_FACTORY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_DATA_FACTORY, GimpDataFactoryClass))
+
+
+typedef struct _GimpDataFactoryPrivate GimpDataFactoryPrivate;
+typedef struct _GimpDataFactoryClass GimpDataFactoryClass;
+
+struct _GimpDataFactory
+{
+ GimpObject parent_instance;
+
+ GimpDataFactoryPrivate *priv;
+};
+
+struct _GimpDataFactoryClass
+{
+ GimpObjectClass parent_class;
+
+ void (* data_init) (GimpDataFactory *factory,
+ GimpContext *context);
+ void (* data_refresh) (GimpDataFactory *factory,
+ GimpContext *context);
+ void (* data_save) (GimpDataFactory *factory);
+
+ void (* data_cancel) (GimpDataFactory *factory);
+
+ GimpData * (* data_duplicate) (GimpDataFactory *factory,
+ GimpData *data);
+ gboolean (* data_delete) (GimpDataFactory *factory,
+ GimpData *data,
+ gboolean delete_from_disk,
+ GError **error);
+};
+
+
+GType gimp_data_factory_get_type (void) G_GNUC_CONST;
+
+void gimp_data_factory_data_init (GimpDataFactory *factory,
+ GimpContext *context,
+ gboolean no_data);
+void gimp_data_factory_data_clean (GimpDataFactory *factory);
+void gimp_data_factory_data_refresh (GimpDataFactory *factory,
+ GimpContext *context);
+void gimp_data_factory_data_save (GimpDataFactory *factory);
+void gimp_data_factory_data_free (GimpDataFactory *factory);
+
+GimpAsyncSet * gimp_data_factory_get_async_set (GimpDataFactory *factory);
+gboolean gimp_data_factory_data_wait (GimpDataFactory *factory);
+void gimp_data_factory_data_cancel (GimpDataFactory *factory);
+
+gboolean gimp_data_factory_has_data_new_func (GimpDataFactory *factory);
+GimpData * gimp_data_factory_data_new (GimpDataFactory *factory,
+ GimpContext *context,
+ const gchar *name);
+GimpData * gimp_data_factory_data_get_standard (GimpDataFactory *factory,
+ GimpContext *context);
+
+GimpData * gimp_data_factory_data_duplicate (GimpDataFactory *factory,
+ GimpData *data);
+gboolean gimp_data_factory_data_delete (GimpDataFactory *factory,
+ GimpData *data,
+ gboolean delete_from_disk,
+ GError **error);
+
+gboolean gimp_data_factory_data_save_single (GimpDataFactory *factory,
+ GimpData *data,
+ GError **error);
+
+void gimp_data_factory_data_foreach (GimpDataFactory *factory,
+ gboolean skip_internal,
+ GimpDataForeachFunc callback,
+ gpointer user_data);
+
+Gimp * gimp_data_factory_get_gimp (GimpDataFactory *factory);
+GType gimp_data_factory_get_data_type (GimpDataFactory *factory);
+GimpContainer * gimp_data_factory_get_container (GimpDataFactory *factory);
+GimpContainer * gimp_data_factory_get_container_obsolete
+ (GimpDataFactory *factory);
+GList * gimp_data_factory_get_data_path (GimpDataFactory *factory);
+GList * gimp_data_factory_get_data_path_writable
+ (GimpDataFactory *factory);
+
+
+
+#endif /* __GIMP_DATA_FACTORY_H__ */
diff --git a/app/core/gimpdataloaderfactory.c b/app/core/gimpdataloaderfactory.c
new file mode 100644
index 0000000..01c4496
--- /dev/null
+++ b/app/core/gimpdataloaderfactory.c
@@ -0,0 +1,562 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpdataloaderfactory.c
+ * Copyright (C) 2001-2018 Michael Natterer <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpbase/gimpbase.h"
+
+#include "core-types.h"
+
+#include "gimp.h"
+#include "gimp-utils.h"
+#include "gimpcontainer.h"
+#include "gimpdata.h"
+#include "gimpdataloaderfactory.h"
+
+#include "gimp-intl.h"
+
+
+/* Data files that have this string in their path are considered
+ * obsolete and are only kept around for backwards compatibility
+ */
+#define GIMP_OBSOLETE_DATA_DIR_NAME "gimp-obsolete-files"
+
+
+typedef struct _GimpDataLoader GimpDataLoader;
+
+struct _GimpDataLoader
+{
+ gchar *name;
+ GimpDataLoadFunc load_func;
+ gchar *extension;
+ gboolean writable;
+};
+
+
+struct _GimpDataLoaderFactoryPrivate
+{
+ GList *loaders;
+ GimpDataLoader *fallback;
+};
+
+#define GET_PRIVATE(obj) (((GimpDataLoaderFactory *) (obj))->priv)
+
+
+static void gimp_data_loader_factory_finalize (GObject *object);
+
+static void gimp_data_loader_factory_data_init (GimpDataFactory *factory,
+ GimpContext *context);
+static void gimp_data_loader_factory_data_refresh (GimpDataFactory *factory,
+ GimpContext *context);
+
+static GimpDataLoader *
+ gimp_data_loader_factory_get_loader (GimpDataFactory *factory,
+ GFile *file);
+
+static void gimp_data_loader_factory_load (GimpDataFactory *factory,
+ GimpContext *context,
+ GHashTable *cache);
+static void gimp_data_loader_factory_load_directory (GimpDataFactory *factory,
+ GimpContext *context,
+ GHashTable *cache,
+ gboolean dir_writable,
+ GFile *directory,
+ GFile *top_directory);
+static void gimp_data_loader_factory_load_data (GimpDataFactory *factory,
+ GimpContext *context,
+ GHashTable *cache,
+ gboolean dir_writable,
+ GFile *file,
+ GFileInfo *info,
+ GFile *top_directory);
+
+static GimpDataLoader * gimp_data_loader_new (const gchar *name,
+ GimpDataLoadFunc load_func,
+ const gchar *extension,
+ gboolean writable);
+static void gimp_data_loader_free (GimpDataLoader *loader);
+
+
+G_DEFINE_TYPE_WITH_PRIVATE (GimpDataLoaderFactory, gimp_data_loader_factory,
+ GIMP_TYPE_DATA_FACTORY)
+
+#define parent_class gimp_data_loader_factory_parent_class
+
+
+static void
+gimp_data_loader_factory_class_init (GimpDataLoaderFactoryClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpDataFactoryClass *factory_class = GIMP_DATA_FACTORY_CLASS (klass);
+
+ object_class->finalize = gimp_data_loader_factory_finalize;
+
+ factory_class->data_init = gimp_data_loader_factory_data_init;
+ factory_class->data_refresh = gimp_data_loader_factory_data_refresh;
+}
+
+static void
+gimp_data_loader_factory_init (GimpDataLoaderFactory *factory)
+{
+ factory->priv = gimp_data_loader_factory_get_instance_private (factory);
+}
+
+static void
+gimp_data_loader_factory_finalize (GObject *object)
+{
+ GimpDataLoaderFactoryPrivate *priv = GET_PRIVATE (object);
+
+ g_list_free_full (priv->loaders, (GDestroyNotify) gimp_data_loader_free);
+ priv->loaders = NULL;
+
+ g_clear_pointer (&priv->fallback, gimp_data_loader_free);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gimp_data_loader_factory_data_init (GimpDataFactory *factory,
+ GimpContext *context)
+{
+ gimp_data_loader_factory_load (factory, context, NULL);
+}
+
+static void
+gimp_data_loader_factory_refresh_cache_add (GimpDataFactory *factory,
+ GimpData *data,
+ gpointer user_data)
+{
+ GFile *file = gimp_data_get_file (data);
+
+ if (file)
+ {
+ GimpContainer *container = gimp_data_factory_get_container (factory);
+ GHashTable *cache = user_data;
+ GList *list;
+
+ g_object_ref (data);
+
+ gimp_container_remove (container, GIMP_OBJECT (data));
+
+ list = g_hash_table_lookup (cache, file);
+ list = g_list_prepend (list, data);
+
+ g_hash_table_insert (cache, file, list);
+ }
+}
+
+static gboolean
+gimp_data_loader_factory_refresh_cache_remove (gpointer key,
+ gpointer value,
+ gpointer user_data)
+{
+ GList *list;
+
+ for (list = value; list; list = list->next)
+ g_object_unref (list->data);
+
+ g_list_free (value);
+
+ return TRUE;
+}
+
+static void
+gimp_data_loader_factory_data_refresh (GimpDataFactory *factory,
+ GimpContext *context)
+{
+ GimpContainer *container = gimp_data_factory_get_container (factory);
+ GHashTable *cache;
+
+ gimp_container_freeze (container);
+
+ /* First, save all dirty data objects */
+ gimp_data_factory_data_save (factory);
+
+ cache = g_hash_table_new (g_file_hash, (GEqualFunc) g_file_equal);
+
+ gimp_data_factory_data_foreach (factory, TRUE,
+ gimp_data_loader_factory_refresh_cache_add,
+ cache);
+
+ /* Now the cache contains a GFile => list-of-objects mapping of
+ * the old objects. So we should now traverse the directory and for
+ * each file load it only if its mtime is newer.
+ *
+ * Once a file was added, it is removed from the cache, so the only
+ * objects remaining there will be those that are not present on
+ * the disk (that have to be destroyed)
+ */
+ gimp_data_loader_factory_load (factory, context, cache);
+
+ /* Now all the data is loaded. Free what remains in the cache */
+ g_hash_table_foreach_remove (cache,
+ gimp_data_loader_factory_refresh_cache_remove,
+ NULL);
+
+ g_hash_table_destroy (cache);
+
+ gimp_container_thaw (container);
+}
+
+
+/* public functions */
+
+GimpDataFactory *
+gimp_data_loader_factory_new (Gimp *gimp,
+ GType data_type,
+ const gchar *path_property_name,
+ const gchar *writable_property_name,
+ GimpDataNewFunc new_func,
+ GimpDataGetStandardFunc get_standard_func)
+{
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
+ g_return_val_if_fail (g_type_is_a (data_type, GIMP_TYPE_DATA), NULL);
+ g_return_val_if_fail (path_property_name != NULL, NULL);
+ g_return_val_if_fail (writable_property_name != NULL, NULL);
+
+ return g_object_new (GIMP_TYPE_DATA_LOADER_FACTORY,
+ "gimp", gimp,
+ "data-type", data_type,
+ "path-property-name", path_property_name,
+ "writable-property-name", writable_property_name,
+ "new-func", new_func,
+ "get-standard-func", get_standard_func,
+ NULL);
+}
+
+void
+gimp_data_loader_factory_add_loader (GimpDataFactory *factory,
+ const gchar *name,
+ GimpDataLoadFunc load_func,
+ const gchar *extension,
+ gboolean writable)
+{
+ GimpDataLoaderFactoryPrivate *priv;
+ GimpDataLoader *loader;
+
+ g_return_if_fail (GIMP_IS_DATA_LOADER_FACTORY (factory));
+ g_return_if_fail (name != NULL);
+ g_return_if_fail (load_func != NULL);
+ g_return_if_fail (extension != NULL);
+
+ priv = GET_PRIVATE (factory);
+
+ loader = gimp_data_loader_new (name, load_func, extension, writable);
+
+ priv->loaders = g_list_append (priv->loaders, loader);
+}
+
+void
+gimp_data_loader_factory_add_fallback (GimpDataFactory *factory,
+ const gchar *name,
+ GimpDataLoadFunc load_func)
+{
+ GimpDataLoaderFactoryPrivate *priv;
+
+ g_return_if_fail (GIMP_IS_DATA_LOADER_FACTORY (factory));
+ g_return_if_fail (name != NULL);
+ g_return_if_fail (load_func != NULL);
+
+ priv = GET_PRIVATE (factory);
+
+ g_clear_pointer (&priv->fallback, gimp_data_loader_free);
+
+ priv->fallback = gimp_data_loader_new (name, load_func, NULL, FALSE);
+}
+
+
+/* private functions */
+
+static GimpDataLoader *
+gimp_data_loader_factory_get_loader (GimpDataFactory *factory,
+ GFile *file)
+{
+ GimpDataLoaderFactoryPrivate *priv = GET_PRIVATE (factory);
+ GList *list;
+
+ for (list = priv->loaders; list; list = g_list_next (list))
+ {
+ GimpDataLoader *loader = list->data;
+
+ if (gimp_file_has_extension (file, loader->extension))
+ return loader;
+ }
+
+ return priv->fallback;
+}
+
+static void
+gimp_data_loader_factory_load (GimpDataFactory *factory,
+ GimpContext *context,
+ GHashTable *cache)
+{
+ GList *path;
+ GList *writable_path;
+ GList *list;
+
+ path = gimp_data_factory_get_data_path (factory);
+ writable_path = gimp_data_factory_get_data_path_writable (factory);
+
+ for (list = path; list; list = g_list_next (list))
+ {
+ gboolean dir_writable = FALSE;
+
+ if (g_list_find_custom (writable_path, list->data,
+ (GCompareFunc) gimp_file_compare))
+ dir_writable = TRUE;
+
+ gimp_data_loader_factory_load_directory (factory, context, cache,
+ dir_writable,
+ list->data,
+ list->data);
+ }
+
+ g_list_free_full (path, (GDestroyNotify) g_object_unref);
+ g_list_free_full (writable_path, (GDestroyNotify) g_object_unref);
+}
+
+static void
+gimp_data_loader_factory_load_directory (GimpDataFactory *factory,
+ GimpContext *context,
+ GHashTable *cache,
+ gboolean dir_writable,
+ GFile *directory,
+ GFile *top_directory)
+{
+ GFileEnumerator *enumerator;
+
+ enumerator = g_file_enumerate_children (directory,
+ G_FILE_ATTRIBUTE_STANDARD_NAME ","
+ G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN ","
+ G_FILE_ATTRIBUTE_STANDARD_TYPE ","
+ G_FILE_ATTRIBUTE_TIME_MODIFIED,
+ G_FILE_QUERY_INFO_NONE,
+ NULL, NULL);
+
+ if (enumerator)
+ {
+ GFileInfo *info;
+
+ while ((info = g_file_enumerator_next_file (enumerator, NULL, NULL)))
+ {
+ GFileType file_type;
+ GFile *child;
+
+ if (g_file_info_get_is_hidden (info))
+ {
+ g_object_unref (info);
+ continue;
+ }
+
+ file_type = g_file_info_get_file_type (info);
+ child = g_file_enumerator_get_child (enumerator, info);
+
+ if (file_type == G_FILE_TYPE_DIRECTORY)
+ {
+ gimp_data_loader_factory_load_directory (factory, context, cache,
+ dir_writable,
+ child,
+ top_directory);
+ }
+ else if (file_type == G_FILE_TYPE_REGULAR)
+ {
+ gimp_data_loader_factory_load_data (factory, context, cache,
+ dir_writable,
+ child, info,
+ top_directory);
+ }
+
+ g_object_unref (child);
+ g_object_unref (info);
+ }
+
+ g_object_unref (enumerator);
+ }
+}
+
+static void
+gimp_data_loader_factory_load_data (GimpDataFactory *factory,
+ GimpContext *context,
+ GHashTable *cache,
+ gboolean dir_writable,
+ GFile *file,
+ GFileInfo *info,
+ GFile *top_directory)
+{
+ GimpDataLoader *loader;
+ GimpContainer *container;
+ GimpContainer *container_obsolete;
+ GList *data_list = NULL;
+ GInputStream *input;
+ guint64 mtime;
+ GError *error = NULL;
+
+ loader = gimp_data_loader_factory_get_loader (factory, file);
+
+ if (! loader)
+ return;
+
+ container = gimp_data_factory_get_container (factory);
+ container_obsolete = gimp_data_factory_get_container_obsolete (factory);
+
+ if (gimp_data_factory_get_gimp (factory)->be_verbose)
+ g_print (" Loading %s\n", gimp_file_get_utf8_name (file));
+
+ mtime = g_file_info_get_attribute_uint64 (info,
+ G_FILE_ATTRIBUTE_TIME_MODIFIED);
+
+ if (cache)
+ {
+ GList *cached_data = g_hash_table_lookup (cache, file);
+
+ if (cached_data &&
+ gimp_data_get_mtime (cached_data->data) != 0 &&
+ gimp_data_get_mtime (cached_data->data) == mtime)
+ {
+ GList *list;
+
+ for (list = cached_data; list; list = g_list_next (list))
+ gimp_container_add (container, list->data);
+
+ return;
+ }
+ }
+
+ input = G_INPUT_STREAM (g_file_read (file, NULL, &error));
+
+ if (input)
+ {
+ GInputStream *buffered = g_buffered_input_stream_new (input);
+
+ data_list = loader->load_func (context, file, buffered, &error);
+
+ if (error)
+ {
+ g_prefix_error (&error,
+ _("Error loading '%s': "),
+ gimp_file_get_utf8_name (file));
+ }
+ else if (! data_list)
+ {
+ g_set_error (&error, GIMP_DATA_ERROR, GIMP_DATA_ERROR_READ,
+ _("Error loading '%s'"),
+ gimp_file_get_utf8_name (file));
+ }
+
+ g_object_unref (buffered);
+ g_object_unref (input);
+ }
+ else
+ {
+ g_prefix_error (&error,
+ _("Could not open '%s' for reading: "),
+ gimp_file_get_utf8_name (file));
+ }
+
+ if (G_LIKELY (data_list))
+ {
+ GList *list;
+ gchar *uri;
+ gboolean obsolete;
+ gboolean writable = FALSE;
+ gboolean deletable = FALSE;
+
+ uri = g_file_get_uri (file);
+
+ obsolete = (strstr (uri, GIMP_OBSOLETE_DATA_DIR_NAME) != 0);
+
+ g_free (uri);
+
+ /* obsolete files are immutable, don't check their writability */
+ if (! obsolete)
+ {
+ deletable = (g_list_length (data_list) == 1 && dir_writable);
+ writable = (deletable && loader->writable);
+ }
+
+ for (list = data_list; list; list = g_list_next (list))
+ {
+ GimpData *data = list->data;
+
+ gimp_data_set_file (data, file, writable, deletable);
+ gimp_data_set_mtime (data, mtime);
+ gimp_data_clean (data);
+
+ if (obsolete)
+ {
+ gimp_container_add (container_obsolete,
+ GIMP_OBJECT (data));
+ }
+ else
+ {
+ gimp_data_set_folder_tags (data, top_directory);
+
+ gimp_container_add (container,
+ GIMP_OBJECT (data));
+ }
+
+ g_object_unref (data);
+ }
+
+ g_list_free (data_list);
+ }
+
+ /* not else { ... } because loader->load_func() can return a list
+ * of data objects *and* an error message if loading failed after
+ * something was already loaded
+ */
+ if (G_UNLIKELY (error))
+ {
+ gimp_message (gimp_data_factory_get_gimp (factory), NULL,
+ GIMP_MESSAGE_ERROR,
+ _("Failed to load data:\n\n%s"), error->message);
+ g_clear_error (&error);
+ }
+}
+
+static GimpDataLoader *
+gimp_data_loader_new (const gchar *name,
+ GimpDataLoadFunc load_func,
+ const gchar *extension,
+ gboolean writable)
+{
+ GimpDataLoader *loader = g_slice_new (GimpDataLoader);
+
+ loader->name = g_strdup (name);
+ loader->load_func = load_func;
+ loader->extension = g_strdup (extension);
+ loader->writable = writable ? TRUE : FALSE;
+
+ return loader;
+}
+
+static void
+gimp_data_loader_free (GimpDataLoader *loader)
+{
+ g_free (loader->name);
+ g_free (loader->extension);
+
+ g_slice_free (GimpDataLoader, loader);
+}
diff --git a/app/core/gimpdataloaderfactory.h b/app/core/gimpdataloaderfactory.h
new file mode 100644
index 0000000..cf0d742
--- /dev/null
+++ b/app/core/gimpdataloaderfactory.h
@@ -0,0 +1,77 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpdataloaderfactory.h
+ * Copyright (C) 2001-2018 Michael Natterer <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_DATA_LOADER_FACTORY_H__
+#define __GIMP_DATA_LOADER_FACTORY_H__
+
+
+#include "gimpdatafactory.h"
+
+
+typedef GList * (* GimpDataLoadFunc) (GimpContext *context,
+ GFile *file,
+ GInputStream *input,
+ GError **error);
+
+
+#define GIMP_TYPE_DATA_LOADER_FACTORY (gimp_data_loader_factory_get_type ())
+#define GIMP_DATA_LOADER_FACTORY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_DATA_LOADER_FACTORY, GimpDataLoaderFactory))
+#define GIMP_DATA_LOADER_FACTORY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_DATA_LOADER_FACTORY, GimpDataLoaderFactoryClass))
+#define GIMP_IS_DATA_LOADER_FACTORY(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_DATA_LOADER_FACTORY))
+#define GIMP_IS_DATA_LOADER_FACTORY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_DATA_LOADER_FACTORY))
+#define GIMP_DATA_LOADER_FACTORY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_DATA_LOADER_FACTORY, GimpDataLoaderFactoryClass))
+
+
+typedef struct _GimpDataLoaderFactoryPrivate GimpDataLoaderFactoryPrivate;
+typedef struct _GimpDataLoaderFactoryClass GimpDataLoaderFactoryClass;
+
+struct _GimpDataLoaderFactory
+{
+ GimpDataFactory parent_instance;
+
+ GimpDataLoaderFactoryPrivate *priv;
+};
+
+struct _GimpDataLoaderFactoryClass
+{
+ GimpDataFactoryClass parent_class;
+};
+
+
+GType gimp_data_loader_factory_get_type (void) G_GNUC_CONST;
+
+GimpDataFactory * gimp_data_loader_factory_new (Gimp *gimp,
+ GType data_type,
+ const gchar *path_property_name,
+ const gchar *writable_property_name,
+ GimpDataNewFunc new_func,
+ GimpDataGetStandardFunc get_standard_func);
+
+void gimp_data_loader_factory_add_loader (GimpDataFactory *factory,
+ const gchar *name,
+ GimpDataLoadFunc load_func,
+ const gchar *extension,
+ gboolean writable);
+void gimp_data_loader_factory_add_fallback (GimpDataFactory *factory,
+ const gchar *name,
+ GimpDataLoadFunc load_func);
+
+
+#endif /* __GIMP_DATA_LOADER_FACTORY_H__ */
diff --git a/app/core/gimpdocumentlist.c b/app/core/gimpdocumentlist.c
new file mode 100644
index 0000000..fe10f6f
--- /dev/null
+++ b/app/core/gimpdocumentlist.c
@@ -0,0 +1,106 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-1997 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpconfig/gimpconfig.h"
+
+#include "core-types.h"
+
+#include "config/gimpcoreconfig.h"
+
+#include "gimp.h"
+#include "gimpdocumentlist.h"
+#include "gimpimagefile.h"
+
+
+G_DEFINE_TYPE (GimpDocumentList, gimp_document_list, GIMP_TYPE_LIST)
+
+
+static void
+gimp_document_list_class_init (GimpDocumentListClass *klass)
+{
+}
+
+static void
+gimp_document_list_init (GimpDocumentList *list)
+{
+}
+
+GimpContainer *
+gimp_document_list_new (Gimp *gimp)
+{
+ GimpDocumentList *document_list;
+
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
+
+ document_list = g_object_new (GIMP_TYPE_DOCUMENT_LIST,
+ "name", "document-list",
+ "children-type", GIMP_TYPE_IMAGEFILE,
+ "policy", GIMP_CONTAINER_POLICY_STRONG,
+ NULL);
+
+ document_list->gimp = gimp;
+
+ return GIMP_CONTAINER (document_list);
+}
+
+GimpImagefile *
+gimp_document_list_add_file (GimpDocumentList *document_list,
+ GFile *file,
+ const gchar *mime_type)
+{
+ Gimp *gimp;
+ GimpImagefile *imagefile;
+ GimpContainer *container;
+ gchar *uri;
+
+ g_return_val_if_fail (GIMP_IS_DOCUMENT_LIST (document_list), NULL);
+ g_return_val_if_fail (G_IS_FILE (file), NULL);
+
+ container = GIMP_CONTAINER (document_list);
+
+ gimp = document_list->gimp;
+
+ uri = g_file_get_uri (file);
+
+ imagefile = (GimpImagefile *) gimp_container_get_child_by_name (container,
+ uri);
+
+ g_free (uri);
+
+ if (imagefile)
+ {
+ gimp_container_reorder (container, GIMP_OBJECT (imagefile), 0);
+ }
+ else
+ {
+ imagefile = gimp_imagefile_new (gimp, file);
+ gimp_container_add (container, GIMP_OBJECT (imagefile));
+ g_object_unref (imagefile);
+ }
+
+ gimp_imagefile_set_mime_type (imagefile, mime_type);
+
+ if (gimp->config->save_document_history)
+ gimp_recent_list_add_file (gimp, file, mime_type);
+
+ return imagefile;
+}
diff --git a/app/core/gimpdocumentlist.h b/app/core/gimpdocumentlist.h
new file mode 100644
index 0000000..68bca80
--- /dev/null
+++ b/app/core/gimpdocumentlist.h
@@ -0,0 +1,54 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-1997 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_DOCUMENT_LIST_H__
+#define __GIMP_DOCUMENT_LIST_H__
+
+#include "core/gimplist.h"
+
+
+#define GIMP_TYPE_DOCUMENT_LIST (gimp_document_list_get_type ())
+#define GIMP_DOCUMENT_LIST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_DOCUMENT_LIST, GimpDocumentList))
+#define GIMP_DOCUMENT_LIST_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_DOCUMENT_LIST, GimpDocumentListClass))
+#define GIMP_IS_DOCUMENT_LIST(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_DOCUMENT_LIST))
+#define GIMP_IS_DOCUMENT_LIST_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_DOCUMENT_LIST))
+
+
+typedef struct _GimpDocumentListClass GimpDocumentListClass;
+
+struct _GimpDocumentList
+{
+ GimpList parent_instance;
+
+ Gimp *gimp;
+};
+
+struct _GimpDocumentListClass
+{
+ GimpListClass parent_class;
+};
+
+
+GType gimp_document_list_get_type (void) G_GNUC_CONST;
+GimpContainer * gimp_document_list_new (Gimp *gimp);
+
+GimpImagefile * gimp_document_list_add_file (GimpDocumentList *document_list,
+ GFile *file,
+ const gchar *mime_type);
+
+
+#endif /* __GIMP_DOCUMENT_LIST_H__ */
diff --git a/app/core/gimpdrawable-bucket-fill.c b/app/core/gimpdrawable-bucket-fill.c
new file mode 100644
index 0000000..1cef758
--- /dev/null
+++ b/app/core/gimpdrawable-bucket-fill.c
@@ -0,0 +1,499 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <cairo.h>
+#define GEGL_ITERATOR2_API
+#include <gegl.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+#include "libgimpbase/gimpbase.h"
+#include "libgimpcolor/gimpcolor.h"
+
+#include "core-types.h"
+
+#include "gegl/gimp-gegl-apply-operation.h"
+#include "gegl/gimp-gegl-mask.h"
+#include "gegl/gimp-gegl-mask-combine.h"
+#include "gegl/gimp-gegl-utils.h"
+
+#include "operations/layer-modes/gimp-layer-modes.h"
+
+#include "gimp.h"
+#include "gimpchannel.h"
+#include "gimpdrawable.h"
+#include "gimpdrawable-bucket-fill.h"
+#include "gimpfilloptions.h"
+#include "gimpimage.h"
+#include "gimppickable.h"
+#include "gimppickable-contiguous-region.h"
+
+#include "gimp-intl.h"
+
+
+/* public functions */
+
+void
+gimp_drawable_bucket_fill (GimpDrawable *drawable,
+ GimpFillOptions *options,
+ gboolean fill_transparent,
+ GimpSelectCriterion fill_criterion,
+ gdouble threshold,
+ gboolean sample_merged,
+ gboolean diagonal_neighbors,
+ gdouble seed_x,
+ gdouble seed_y)
+{
+ GimpImage *image;
+ GeglBuffer *buffer;
+ gdouble mask_x;
+ gdouble mask_y;
+ gint width, height;
+
+ g_return_if_fail (GIMP_IS_DRAWABLE (drawable));
+ g_return_if_fail (gimp_item_is_attached (GIMP_ITEM (drawable)));
+ g_return_if_fail (GIMP_IS_FILL_OPTIONS (options));
+
+ image = gimp_item_get_image (GIMP_ITEM (drawable));
+
+ gimp_set_busy (image->gimp);
+
+ buffer = gimp_drawable_get_bucket_fill_buffer (drawable, options,
+ fill_transparent, fill_criterion,
+ threshold, FALSE, sample_merged,
+ diagonal_neighbors,
+ seed_x, seed_y, NULL,
+ &mask_x, &mask_y, &width, &height);
+
+ if (buffer)
+ {
+ /* Apply it to the image */
+ gimp_drawable_apply_buffer (drawable, buffer,
+ GEGL_RECTANGLE (0, 0, width, height),
+ TRUE, C_("undo-type", "Bucket Fill"),
+ gimp_context_get_opacity (GIMP_CONTEXT (options)),
+ gimp_context_get_paint_mode (GIMP_CONTEXT (options)),
+ GIMP_LAYER_COLOR_SPACE_AUTO,
+ GIMP_LAYER_COLOR_SPACE_AUTO,
+ gimp_layer_mode_get_paint_composite_mode
+ (gimp_context_get_paint_mode (GIMP_CONTEXT (options))),
+ NULL, (gint) mask_x, mask_y);
+ g_object_unref (buffer);
+
+ gimp_drawable_update (drawable, mask_x, mask_y, width, height);
+ }
+
+ gimp_unset_busy (image->gimp);
+}
+
+/**
+ * gimp_drawable_get_bucket_fill_buffer:
+ * @drawable: the #GimpDrawable to edit.
+ * @options:
+ * @fill_transparent:
+ * @fill_criterion:
+ * @threshold:
+ * @show_all:
+ * @sample_merged:
+ * @diagonal_neighbors:
+ * @seed_x: X coordinate to start the fill.
+ * @seed_y: Y coordinate to start the fill.
+ * @mask_buffer: mask of the fill in-progress when in an interactive
+ * filling process. Set to NULL if you need a one-time
+ * fill.
+ * @mask_x: returned x bound of @mask_buffer.
+ * @mask_y: returned x bound of @mask_buffer.
+ * @mask_width: returned width bound of @mask_buffer.
+ * @mask_height: returned height bound of @mask_buffer.
+ *
+ * Creates the fill buffer for a bucket fill operation on @drawable,
+ * without actually applying it (if you want to apply it directly as a
+ * one-time operation, use gimp_drawable_bucket_fill() instead). If
+ * @mask_buffer is not NULL, the intermediate fill mask will also be
+ * returned. This fill mask can later be reused in successive calls to
+ * gimp_drawable_get_bucket_fill_buffer() for interactive filling.
+ *
+ * Returns: a fill buffer which can be directly applied to @drawable, or
+ * used in a drawable filter as preview.
+ */
+GeglBuffer *
+gimp_drawable_get_bucket_fill_buffer (GimpDrawable *drawable,
+ GimpFillOptions *options,
+ gboolean fill_transparent,
+ GimpSelectCriterion fill_criterion,
+ gdouble threshold,
+ gboolean show_all,
+ gboolean sample_merged,
+ gboolean diagonal_neighbors,
+ gdouble seed_x,
+ gdouble seed_y,
+ GeglBuffer **mask_buffer,
+ gdouble *mask_x,
+ gdouble *mask_y,
+ gint *mask_width,
+ gint *mask_height)
+{
+ GimpImage *image;
+ GimpPickable *pickable;
+ GeglBuffer *buffer;
+ GeglBuffer *new_mask;
+ gboolean antialias;
+ gint x, y, width, height;
+ gint mask_offset_x = 0;
+ gint mask_offset_y = 0;
+ gint sel_x, sel_y, sel_width, sel_height;
+
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), NULL);
+ g_return_val_if_fail (gimp_item_is_attached (GIMP_ITEM (drawable)), NULL);
+ g_return_val_if_fail (GIMP_IS_FILL_OPTIONS (options), NULL);
+
+ image = gimp_item_get_image (GIMP_ITEM (drawable));
+
+ if (! gimp_item_mask_intersect (GIMP_ITEM (drawable),
+ &sel_x, &sel_y, &sel_width, &sel_height))
+ return NULL;
+
+ if (mask_buffer && *mask_buffer && threshold == 0.0)
+ {
+ gfloat pixel;
+
+ gegl_buffer_sample (*mask_buffer, seed_x, seed_y, NULL, &pixel,
+ babl_format ("Y float"),
+ GEGL_SAMPLER_NEAREST, GEGL_ABYSS_NONE);
+
+ if (pixel != 0.0)
+ /* Already selected. This seed won't change the selection. */
+ return NULL;
+ }
+
+ gimp_set_busy (image->gimp);
+
+ if (sample_merged)
+ {
+ if (! show_all)
+ pickable = GIMP_PICKABLE (image);
+ else
+ pickable = GIMP_PICKABLE (gimp_image_get_projection (image));
+ }
+ else
+ {
+ pickable = GIMP_PICKABLE (drawable);
+ }
+
+ antialias = gimp_fill_options_get_antialias (options);
+
+ /* Do a seed bucket fill...To do this, calculate a new
+ * contiguous region.
+ */
+ new_mask = gimp_pickable_contiguous_region_by_seed (pickable,
+ antialias,
+ threshold,
+ fill_transparent,
+ fill_criterion,
+ diagonal_neighbors,
+ (gint) seed_x,
+ (gint) seed_y);
+ if (mask_buffer && *mask_buffer)
+ {
+ gimp_gegl_mask_combine_buffer (new_mask, *mask_buffer,
+ GIMP_CHANNEL_OP_ADD, 0, 0);
+ g_object_unref (*mask_buffer);
+ }
+
+ if (mask_buffer)
+ *mask_buffer = new_mask;
+
+ gimp_gegl_mask_bounds (new_mask, &x, &y, &width, &height);
+ width -= x;
+ height -= y;
+
+ /* If there is a selection, intersect the region bounds
+ * with the selection bounds, to avoid processing areas
+ * that are going to be masked out anyway. The actual
+ * intersection of the fill region with the mask data
+ * happens when combining the fill buffer, in
+ * gimp_drawable_apply_buffer().
+ */
+ if (! gimp_channel_is_empty (gimp_image_get_mask (image)))
+ {
+ gint off_x = 0;
+ gint off_y = 0;
+
+ if (sample_merged)
+ gimp_item_get_offset (GIMP_ITEM (drawable), &off_x, &off_y);
+
+ if (! gimp_rectangle_intersect (x, y, width, height,
+
+ sel_x + off_x, sel_y + off_y,
+ sel_width, sel_height,
+
+ &x, &y, &width, &height))
+ {
+ /* The fill region and the selection are disjoint; bail. */
+
+ if (! mask_buffer)
+ g_object_unref (new_mask);
+
+ gimp_unset_busy (image->gimp);
+
+ return NULL;
+ }
+ }
+
+ /* make sure we handle the mask correctly if it was sample-merged */
+ if (sample_merged)
+ {
+ GimpItem *item = GIMP_ITEM (drawable);
+ gint off_x, off_y;
+
+ /* Limit the channel bounds to the drawable's extents */
+ gimp_item_get_offset (item, &off_x, &off_y);
+
+ gimp_rectangle_intersect (x, y, width, height,
+
+ off_x, off_y,
+ gimp_item_get_width (item),
+ gimp_item_get_height (item),
+
+ &x, &y, &width, &height);
+
+ mask_offset_x = x;
+ mask_offset_y = y;
+
+ /* translate mask bounds to drawable coords */
+ x -= off_x;
+ y -= off_y;
+ }
+ else
+ {
+ mask_offset_x = x;
+ mask_offset_y = y;
+ }
+
+ buffer = gimp_fill_options_create_buffer (options, drawable,
+ GEGL_RECTANGLE (0, 0,
+ width, height),
+ -x, -y);
+
+ gimp_gegl_apply_opacity (buffer, NULL, NULL, buffer, new_mask,
+ -mask_offset_x, -mask_offset_y, 1.0);
+
+ if (mask_x)
+ *mask_x = x;
+ if (mask_y)
+ *mask_y = y;
+ if (mask_width)
+ *mask_width = width;
+ if (mask_height)
+ *mask_height = height;
+
+ if (! mask_buffer)
+ g_object_unref (new_mask);
+
+ gimp_unset_busy (image->gimp);
+
+ return buffer;
+}
+
+/**
+ * gimp_drawable_get_line_art_fill_buffer:
+ * @drawable: the #GimpDrawable to edit.
+ * @line_art: the #GimpLineArt computed as fill source.
+ * @options: the #GimpFillOptions.
+ * @sample_merged:
+ * @seed_x: X coordinate to start the fill.
+ * @seed_y: Y coordinate to start the fill.
+ * @mask_buffer: mask of the fill in-progress when in an interactive
+ * filling process. Set to NULL if you need a one-time
+ * fill.
+ * @mask_x: returned x bound of @mask_buffer.
+ * @mask_y: returned x bound of @mask_buffer.
+ * @mask_width: returned width bound of @mask_buffer.
+ * @mask_height: returned height bound of @mask_buffer.
+ *
+ * Creates the fill buffer for a bucket fill operation on @drawable
+ * based on @line_art and @options, without actually applying it.
+ * If @mask_buffer is not NULL, the intermediate fill mask will also be
+ * returned. This fill mask can later be reused in successive calls to
+ * gimp_drawable_get_bucket_fill_buffer() for interactive filling.
+ *
+ * Returns: a fill buffer which can be directly applied to @drawable, or
+ * used in a drawable filter as preview.
+ */
+GeglBuffer *
+gimp_drawable_get_line_art_fill_buffer (GimpDrawable *drawable,
+ GimpLineArt *line_art,
+ GimpFillOptions *options,
+ gboolean sample_merged,
+ gdouble seed_x,
+ gdouble seed_y,
+ GeglBuffer **mask_buffer,
+ gdouble *mask_x,
+ gdouble *mask_y,
+ gint *mask_width,
+ gint *mask_height)
+{
+ GimpImage *image;
+ GeglBuffer *buffer;
+ GeglBuffer *new_mask;
+ gint x, y, width, height;
+ gint mask_offset_x = 0;
+ gint mask_offset_y = 0;
+ gint sel_x, sel_y, sel_width, sel_height;
+ gdouble feather_radius;
+
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), NULL);
+ g_return_val_if_fail (gimp_item_is_attached (GIMP_ITEM (drawable)), NULL);
+ g_return_val_if_fail (GIMP_IS_FILL_OPTIONS (options), NULL);
+
+ image = gimp_item_get_image (GIMP_ITEM (drawable));
+
+ if (! gimp_item_mask_intersect (GIMP_ITEM (drawable),
+ &sel_x, &sel_y, &sel_width, &sel_height))
+ return NULL;
+
+ if (mask_buffer && *mask_buffer)
+ {
+ gfloat pixel;
+
+ gegl_buffer_sample (*mask_buffer, seed_x, seed_y, NULL, &pixel,
+ babl_format ("Y float"),
+ GEGL_SAMPLER_NEAREST, GEGL_ABYSS_NONE);
+
+ if (pixel != 0.0)
+ /* Already selected. This seed won't change the selection. */
+ return NULL;
+ }
+
+ gimp_set_busy (image->gimp);
+
+ /* Do a seed bucket fill...To do this, calculate a new
+ * contiguous region.
+ */
+ new_mask = gimp_pickable_contiguous_region_by_line_art (NULL, line_art,
+ (gint) seed_x,
+ (gint) seed_y);
+ if (mask_buffer && *mask_buffer)
+ {
+ gimp_gegl_mask_combine_buffer (new_mask, *mask_buffer,
+ GIMP_CHANNEL_OP_ADD, 0, 0);
+ g_object_unref (*mask_buffer);
+ }
+ if (mask_buffer)
+ *mask_buffer = new_mask;
+
+ gimp_gegl_mask_bounds (new_mask, &x, &y, &width, &height);
+ width -= x;
+ height -= y;
+
+ /* If there is a selection, intersect the region bounds
+ * with the selection bounds, to avoid processing areas
+ * that are going to be masked out anyway. The actual
+ * intersection of the fill region with the mask data
+ * happens when combining the fill buffer, in
+ * gimp_drawable_apply_buffer().
+ */
+ if (! gimp_channel_is_empty (gimp_image_get_mask (image)))
+ {
+ gint off_x = 0;
+ gint off_y = 0;
+
+ if (sample_merged)
+ gimp_item_get_offset (GIMP_ITEM (drawable), &off_x, &off_y);
+
+ if (! gimp_rectangle_intersect (x, y, width, height,
+
+ sel_x + off_x, sel_y + off_y,
+ sel_width, sel_height,
+
+ &x, &y, &width, &height))
+ {
+ if (! mask_buffer)
+ g_object_unref (new_mask);
+ /* The fill region and the selection are disjoint; bail. */
+ gimp_unset_busy (image->gimp);
+
+ return NULL;
+ }
+ }
+
+ /* make sure we handle the mask correctly if it was sample-merged */
+ if (sample_merged)
+ {
+ GimpItem *item = GIMP_ITEM (drawable);
+ gint off_x, off_y;
+
+ /* Limit the channel bounds to the drawable's extents */
+ gimp_item_get_offset (item, &off_x, &off_y);
+
+ gimp_rectangle_intersect (x, y, width, height,
+
+ off_x, off_y,
+ gimp_item_get_width (item),
+ gimp_item_get_height (item),
+
+ &x, &y, &width, &height);
+
+ mask_offset_x = x;
+ mask_offset_y = y;
+
+ /* translate mask bounds to drawable coords */
+ x -= off_x;
+ y -= off_y;
+ }
+ else
+ {
+ mask_offset_x = x;
+ mask_offset_y = y;
+ }
+
+ buffer = gimp_fill_options_create_buffer (options, drawable,
+ GEGL_RECTANGLE (0, 0,
+ width, height),
+ -x, -y);
+
+ gimp_gegl_apply_opacity (buffer, NULL, NULL, buffer, new_mask,
+ -mask_offset_x, -mask_offset_y, 1.0);
+
+ if (gimp_fill_options_get_feather (options, &feather_radius))
+ {
+ /* Feathering for the line art algorithm is not applied during
+ * mask creation because we just want to apply it on the borders
+ * of the mask at the end (since the mask can evolve, we don't
+ * want to actually touch it, but only the intermediate results).
+ */
+ gimp_gegl_apply_feather (buffer, NULL, NULL, buffer, NULL,
+ feather_radius, feather_radius, TRUE);
+ }
+
+ if (mask_x)
+ *mask_x = x;
+ if (mask_y)
+ *mask_y = y;
+ if (mask_width)
+ *mask_width = width;
+ if (mask_height)
+ *mask_height = height;
+
+ if (! mask_buffer)
+ g_object_unref (new_mask);
+
+ gimp_unset_busy (image->gimp);
+
+ return buffer;
+}
diff --git a/app/core/gimpdrawable-bucket-fill.h b/app/core/gimpdrawable-bucket-fill.h
new file mode 100644
index 0000000..148de97
--- /dev/null
+++ b/app/core/gimpdrawable-bucket-fill.h
@@ -0,0 +1,59 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_DRAWABLE_BUCKET_FILL_H__
+#define __GIMP_DRAWABLE_BUCKET_FILL_H__
+
+
+void gimp_drawable_bucket_fill (GimpDrawable *drawable,
+ GimpFillOptions *options,
+ gboolean fill_transparent,
+ GimpSelectCriterion fill_criterion,
+ gdouble threshold,
+ gboolean sample_merged,
+ gboolean diagonal_neighbors,
+ gdouble x,
+ gdouble y);
+GeglBuffer * gimp_drawable_get_bucket_fill_buffer (GimpDrawable *drawable,
+ GimpFillOptions *options,
+ gboolean fill_transparent,
+ GimpSelectCriterion fill_criterion,
+ gdouble threshold,
+ gboolean show_all,
+ gboolean sample_merged,
+ gboolean diagonal_neighbors,
+ gdouble seed_x,
+ gdouble seed_y,
+ GeglBuffer **mask_buffer,
+ gdouble *mask_x,
+ gdouble *mask_y,
+ gint *mask_width,
+ gint *mask_height);
+
+GeglBuffer * gimp_drawable_get_line_art_fill_buffer (GimpDrawable *drawable,
+ GimpLineArt *line_art,
+ GimpFillOptions *options,
+ gboolean sample_merged,
+ gdouble seed_x,
+ gdouble seed_y,
+ GeglBuffer **mask_buffer,
+ gdouble *mask_x,
+ gdouble *mask_y,
+ gint *mask_width,
+ gint *mask_height);
+
+#endif /* __GIMP_DRAWABLE_BUCKET_FILL_H__ */
diff --git a/app/core/gimpdrawable-combine.c b/app/core/gimpdrawable-combine.c
new file mode 100644
index 0000000..e48d9dd
--- /dev/null
+++ b/app/core/gimpdrawable-combine.c
@@ -0,0 +1,144 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <cairo.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpbase/gimpbase.h"
+
+#include "core-types.h"
+
+#include "gegl/gimpapplicator.h"
+
+#include "gimp.h"
+#include "gimpchannel.h"
+#include "gimpchunkiterator.h"
+#include "gimpdrawable-combine.h"
+#include "gimpimage.h"
+
+
+void
+gimp_drawable_real_apply_buffer (GimpDrawable *drawable,
+ GeglBuffer *buffer,
+ const GeglRectangle *buffer_region,
+ gboolean push_undo,
+ const gchar *undo_desc,
+ gdouble opacity,
+ GimpLayerMode mode,
+ GimpLayerColorSpace blend_space,
+ GimpLayerColorSpace composite_space,
+ GimpLayerCompositeMode composite_mode,
+ GeglBuffer *base_buffer,
+ gint base_x,
+ gint base_y)
+{
+ GimpItem *item = GIMP_ITEM (drawable);
+ GimpImage *image = gimp_item_get_image (item);
+ GimpChannel *mask = gimp_image_get_mask (image);
+ GimpApplicator *applicator;
+ GimpChunkIterator *iter;
+ gint x, y, width, height;
+ gint offset_x, offset_y;
+
+ /* don't apply the mask to itself and don't apply an empty mask */
+ if (GIMP_DRAWABLE (mask) == drawable || gimp_channel_is_empty (mask))
+ mask = NULL;
+
+ if (! base_buffer)
+ base_buffer = gimp_drawable_get_buffer (drawable);
+
+ /* get the layer offsets */
+ gimp_item_get_offset (item, &offset_x, &offset_y);
+
+ /* make sure the image application coordinates are within drawable bounds */
+ if (! gimp_rectangle_intersect (base_x, base_y,
+ buffer_region->width, buffer_region->height,
+ 0, 0,
+ gimp_item_get_width (item),
+ gimp_item_get_height (item),
+ &x, &y, &width, &height))
+ {
+ return;
+ }
+
+ if (mask)
+ {
+ GimpItem *mask_item = GIMP_ITEM (mask);
+
+ /* make sure coordinates are in mask bounds ...
+ * we need to add the layer offset to transform coords
+ * into the mask coordinate system
+ */
+ if (! gimp_rectangle_intersect (x, y, width, height,
+ -offset_x, -offset_y,
+ gimp_item_get_width (mask_item),
+ gimp_item_get_height (mask_item),
+ &x, &y, &width, &height))
+ {
+ return;
+ }
+ }
+
+ if (push_undo)
+ {
+ gimp_drawable_push_undo (drawable, undo_desc,
+ NULL, x, y, width, height);
+ }
+
+ applicator = gimp_applicator_new (NULL);
+
+ if (mask)
+ {
+ GeglBuffer *mask_buffer;
+
+ mask_buffer = gimp_drawable_get_buffer (GIMP_DRAWABLE (mask));
+
+ gimp_applicator_set_mask_buffer (applicator, mask_buffer);
+ gimp_applicator_set_mask_offset (applicator, -offset_x, -offset_y);
+ }
+
+ gimp_applicator_set_src_buffer (applicator, base_buffer);
+ gimp_applicator_set_dest_buffer (applicator,
+ gimp_drawable_get_buffer (drawable));
+
+ gimp_applicator_set_apply_buffer (applicator, buffer);
+ gimp_applicator_set_apply_offset (applicator,
+ base_x - buffer_region->x,
+ base_y - buffer_region->y);
+
+ gimp_applicator_set_opacity (applicator, opacity);
+ gimp_applicator_set_mode (applicator, mode,
+ blend_space, composite_space, composite_mode);
+ gimp_applicator_set_affect (applicator,
+ gimp_drawable_get_active_mask (drawable));
+
+ iter = gimp_chunk_iterator_new (cairo_region_create_rectangle (
+ &(cairo_rectangle_int_t) {x, y, width, height}));
+
+ while (gimp_chunk_iterator_next (iter))
+ {
+ GeglRectangle rect;
+
+ while (gimp_chunk_iterator_get_rect (iter, &rect))
+ gimp_applicator_blit (applicator, &rect);
+ }
+
+ g_object_unref (applicator);
+}
diff --git a/app/core/gimpdrawable-combine.h b/app/core/gimpdrawable-combine.h
new file mode 100644
index 0000000..d21d558
--- /dev/null
+++ b/app/core/gimpdrawable-combine.h
@@ -0,0 +1,39 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_DRAWABLE_COMBINE_H__
+#define __GIMP_DRAWABLE_COMBINE_H__
+
+
+/* virtual functions of GimpDrawable, don't call directly */
+
+void gimp_drawable_real_apply_buffer (GimpDrawable *drawable,
+ GeglBuffer *buffer,
+ const GeglRectangle *buffer_region,
+ gboolean push_undo,
+ const gchar *undo_desc,
+ gdouble opacity,
+ GimpLayerMode mode,
+ GimpLayerColorSpace blend_space,
+ GimpLayerColorSpace composite_space,
+ GimpLayerCompositeMode composite_mode,
+ GeglBuffer *base_buffer,
+ gint base_x,
+ gint base_y);
+
+
+#endif /* __GIMP_DRAWABLE_COMBINE_H__ */
diff --git a/app/core/gimpdrawable-edit.c b/app/core/gimpdrawable-edit.c
new file mode 100644
index 0000000..214b8fb
--- /dev/null
+++ b/app/core/gimpdrawable-edit.c
@@ -0,0 +1,231 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+#include "core-types.h"
+
+#include "operations/layer-modes/gimp-layer-modes.h"
+
+#include "gegl/gimp-gegl-loops.h"
+
+#include "gimpchannel.h"
+#include "gimpdrawable.h"
+#include "gimpdrawable-edit.h"
+#include "gimpdrawablefilter.h"
+#include "gimpcontext.h"
+#include "gimpfilloptions.h"
+#include "gimpimage.h"
+#include "gimppattern.h"
+#include "gimptempbuf.h"
+
+#include "gimp-intl.h"
+
+
+/* local function prototypes */
+
+static gboolean gimp_drawable_edit_can_fill_direct (GimpDrawable *drawable,
+ GimpFillOptions *options);
+static void gimp_drawable_edit_fill_direct (GimpDrawable *drawable,
+ GimpFillOptions *options,
+ const gchar *undo_desc);
+
+
+/* private functions */
+
+static gboolean
+gimp_drawable_edit_can_fill_direct (GimpDrawable *drawable,
+ GimpFillOptions *options)
+{
+ GimpImage *image;
+ GimpContext *context;
+ gdouble opacity;
+ GimpComponentMask affect;
+ GimpLayerMode mode;
+ GimpLayerCompositeMode composite_mode;
+ GimpLayerCompositeRegion composite_region;
+
+ image = gimp_item_get_image (GIMP_ITEM (drawable));
+ context = GIMP_CONTEXT (options);
+ opacity = gimp_context_get_opacity (context);
+ affect = gimp_drawable_get_active_mask (drawable);
+ mode = gimp_context_get_paint_mode (context);
+ composite_mode = gimp_layer_mode_get_paint_composite_mode (mode);
+ composite_region = gimp_layer_mode_get_included_region (mode, composite_mode);
+
+ if (gimp_channel_is_empty (gimp_image_get_mask (image)) &&
+ opacity == GIMP_OPACITY_OPAQUE &&
+ affect == GIMP_COMPONENT_MASK_ALL &&
+ gimp_layer_mode_is_trivial (mode) &&
+ (! gimp_layer_mode_is_subtractive (mode) ^
+ ! (composite_region & GIMP_LAYER_COMPOSITE_REGION_SOURCE)))
+ {
+ switch (gimp_fill_options_get_style (options))
+ {
+ case GIMP_FILL_STYLE_SOLID:
+ return TRUE;
+
+ case GIMP_FILL_STYLE_PATTERN:
+ {
+ GimpPattern *pattern;
+ GimpTempBuf *mask;
+ const Babl *format;
+
+ pattern = gimp_context_get_pattern (context);
+ mask = gimp_pattern_get_mask (pattern);
+ format = gimp_temp_buf_get_format (mask);
+
+ return ! babl_format_has_alpha (format);
+ }
+ }
+ }
+
+ return FALSE;
+}
+
+static void
+gimp_drawable_edit_fill_direct (GimpDrawable *drawable,
+ GimpFillOptions *options,
+ const gchar *undo_desc)
+{
+ GeglBuffer *buffer;
+ GimpContext *context;
+ GimpLayerMode mode;
+ gint width;
+ gint height;
+
+ buffer = gimp_drawable_get_buffer (drawable);
+ context = GIMP_CONTEXT (options);
+ mode = gimp_context_get_paint_mode (context);
+ width = gimp_item_get_width (GIMP_ITEM (drawable));
+ height = gimp_item_get_height (GIMP_ITEM (drawable));
+
+ gimp_drawable_push_undo (drawable, undo_desc,
+ NULL, 0, 0, width, height);
+
+ if (! gimp_layer_mode_is_subtractive (mode))
+ gimp_fill_options_fill_buffer (options, drawable, buffer, 0, 0);
+ else
+ gimp_gegl_clear (buffer, NULL);
+}
+
+
+/* public functions */
+
+void
+gimp_drawable_edit_clear (GimpDrawable *drawable,
+ GimpContext *context)
+{
+ GimpFillOptions *options;
+
+ g_return_if_fail (GIMP_IS_DRAWABLE (drawable));
+ g_return_if_fail (gimp_item_is_attached (GIMP_ITEM (drawable)));
+ g_return_if_fail (GIMP_IS_CONTEXT (context));
+
+ options = gimp_fill_options_new (context->gimp, NULL, FALSE);
+
+ if (gimp_drawable_has_alpha (drawable))
+ gimp_fill_options_set_by_fill_type (options, context,
+ GIMP_FILL_TRANSPARENT, NULL);
+ else
+ gimp_fill_options_set_by_fill_type (options, context,
+ GIMP_FILL_BACKGROUND, NULL);
+
+ gimp_drawable_edit_fill (drawable, options, C_("undo-type", "Clear"));
+
+ g_object_unref (options);
+}
+
+void
+gimp_drawable_edit_fill (GimpDrawable *drawable,
+ GimpFillOptions *options,
+ const gchar *undo_desc)
+{
+ GimpContext *context;
+ gint x, y, width, height;
+
+ g_return_if_fail (GIMP_IS_DRAWABLE (drawable));
+ g_return_if_fail (gimp_item_is_attached (GIMP_ITEM (drawable)));
+ g_return_if_fail (GIMP_IS_FILL_OPTIONS (options));
+
+ if (! gimp_item_mask_intersect (GIMP_ITEM (drawable),
+ &x, &y, &width, &height))
+ {
+ return; /* nothing to do, but the fill succeeded */
+ }
+
+ context = GIMP_CONTEXT (options);
+
+ if (gimp_layer_mode_is_alpha_only (gimp_context_get_paint_mode (context)))
+ {
+ if (! gimp_drawable_has_alpha (drawable) ||
+ ! (gimp_drawable_get_active_mask (drawable) &
+ GIMP_COMPONENT_MASK_ALPHA))
+ {
+ return; /* nothing to do, but the fill succeeded */
+ }
+ }
+
+ if (! undo_desc)
+ undo_desc = gimp_fill_options_get_undo_desc (options);
+
+ /* check if we can fill the drawable's buffer directly */
+ if (gimp_drawable_edit_can_fill_direct (drawable, options))
+ {
+ gimp_drawable_edit_fill_direct (drawable, options, undo_desc);
+
+ gimp_drawable_update (drawable, x, y, width, height);
+ }
+ else
+ {
+ GeglNode *operation;
+ GimpDrawableFilter *filter;
+ gdouble opacity;
+ GimpLayerMode mode;
+ GimpLayerMode composite_mode;
+
+ opacity = gimp_context_get_opacity (context);
+ mode = gimp_context_get_paint_mode (context);
+ composite_mode = gimp_layer_mode_get_paint_composite_mode (mode);
+
+ operation = gegl_node_new_child (NULL,
+ "operation", "gimp:fill-source",
+ "options", options,
+ "drawable", drawable,
+ "pattern-offset-x", -x,
+ "pattern-offset-y", -y,
+ NULL);
+
+ filter = gimp_drawable_filter_new (drawable, undo_desc, operation, NULL);
+
+ gimp_drawable_filter_set_opacity (filter, opacity);
+ gimp_drawable_filter_set_mode (filter,
+ mode,
+ GIMP_LAYER_COLOR_SPACE_AUTO,
+ GIMP_LAYER_COLOR_SPACE_AUTO,
+ composite_mode);
+
+ gimp_drawable_filter_apply (filter, NULL);
+ gimp_drawable_filter_commit (filter, NULL, FALSE);
+
+ g_object_unref (filter);
+ g_object_unref (operation);
+ }
+}
diff --git a/app/core/gimpdrawable-edit.h b/app/core/gimpdrawable-edit.h
new file mode 100644
index 0000000..a4ecb1c
--- /dev/null
+++ b/app/core/gimpdrawable-edit.h
@@ -0,0 +1,29 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_DRAWABLE_EDIT_H__
+#define __GIMP_DRAWABLE_EDIT_H__
+
+
+void gimp_drawable_edit_clear (GimpDrawable *drawable,
+ GimpContext *context);
+void gimp_drawable_edit_fill (GimpDrawable *drawable,
+ GimpFillOptions *options,
+ const gchar *undo_desc);
+
+
+#endif /* __GIMP_DRAWABLE_EDIT_H__ */
diff --git a/app/core/gimpdrawable-equalize.c b/app/core/gimpdrawable-equalize.c
new file mode 100644
index 0000000..202dc7f
--- /dev/null
+++ b/app/core/gimpdrawable-equalize.c
@@ -0,0 +1,71 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "core-types.h"
+
+#include "gimpdrawable.h"
+#include "gimpdrawable-equalize.h"
+#include "gimpdrawable-histogram.h"
+#include "gimpdrawable-operation.h"
+#include "gimphistogram.h"
+#include "gimpimage.h"
+#include "gimpselection.h"
+
+#include "gimp-intl.h"
+
+
+void
+gimp_drawable_equalize (GimpDrawable *drawable,
+ gboolean mask_only)
+{
+ GimpImage *image;
+ GimpChannel *selection;
+ GimpHistogram *histogram;
+ GeglNode *equalize;
+
+ g_return_if_fail (GIMP_IS_DRAWABLE (drawable));
+ g_return_if_fail (gimp_item_is_attached (GIMP_ITEM (drawable)));
+
+ image = gimp_item_get_image (GIMP_ITEM (drawable));
+ selection = gimp_image_get_mask (image);
+
+ histogram = gimp_histogram_new (FALSE);
+ gimp_drawable_calculate_histogram (drawable, histogram, FALSE);
+
+ equalize = gegl_node_new_child (NULL,
+ "operation", "gimp:equalize",
+ "histogram", histogram,
+ NULL);
+
+ if (! mask_only)
+ gimp_selection_suspend (GIMP_SELECTION (selection));
+
+ gimp_drawable_apply_operation (drawable, NULL,
+ C_("undo-type", "Equalize"),
+ equalize);
+
+ if (! mask_only)
+ gimp_selection_resume (GIMP_SELECTION (selection));
+
+ g_object_unref (equalize);
+ g_object_unref (histogram);
+}
diff --git a/app/core/gimpdrawable-equalize.h b/app/core/gimpdrawable-equalize.h
new file mode 100644
index 0000000..065b83f
--- /dev/null
+++ b/app/core/gimpdrawable-equalize.h
@@ -0,0 +1,26 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_DRAWABLE_EQUALIZE_H__
+#define __GIMP_DRAWABLE_EQUALIZE_H__
+
+
+void gimp_drawable_equalize (GimpDrawable *drawable,
+ gboolean mask_only);
+
+
+#endif /* __GIMP_DRAWABLE_EQUALIZE_H__ */
diff --git a/app/core/gimpdrawable-fill.c b/app/core/gimpdrawable-fill.c
new file mode 100644
index 0000000..a596d47
--- /dev/null
+++ b/app/core/gimpdrawable-fill.c
@@ -0,0 +1,279 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <cairo.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpcolor/gimpcolor.h"
+
+#include "core-types.h"
+
+#include "gegl/gimp-babl.h"
+#include "gegl/gimp-gegl-apply-operation.h"
+#include "gegl/gimp-gegl-loops.h"
+#include "gegl/gimp-gegl-utils.h"
+
+#include "operations/layer-modes/gimp-layer-modes.h"
+
+#include "gimp-utils.h"
+#include "gimpbezierdesc.h"
+#include "gimpchannel.h"
+#include "gimpdrawable-fill.h"
+#include "gimperror.h"
+#include "gimpfilloptions.h"
+#include "gimpimage.h"
+#include "gimppattern.h"
+#include "gimppickable.h"
+#include "gimpscanconvert.h"
+
+#include "vectors/gimpvectors.h"
+
+#include "gimp-intl.h"
+
+
+/* public functions */
+
+void
+gimp_drawable_fill (GimpDrawable *drawable,
+ GimpContext *context,
+ GimpFillType fill_type)
+{
+ GimpRGB color;
+ GimpPattern *pattern;
+
+ g_return_if_fail (GIMP_IS_DRAWABLE (drawable));
+ g_return_if_fail (GIMP_IS_CONTEXT (context));
+
+ if (fill_type == GIMP_FILL_TRANSPARENT &&
+ ! gimp_drawable_has_alpha (drawable))
+ {
+ fill_type = GIMP_FILL_BACKGROUND;
+ }
+
+ if (! gimp_get_fill_params (context, fill_type, &color, &pattern, NULL))
+ return;
+
+ gimp_drawable_fill_buffer (drawable,
+ gimp_drawable_get_buffer (drawable),
+ &color, pattern, 0, 0);
+
+ gimp_drawable_update (drawable, 0, 0, -1, -1);
+}
+
+void
+gimp_drawable_fill_buffer (GimpDrawable *drawable,
+ GeglBuffer *buffer,
+ const GimpRGB *color,
+ GimpPattern *pattern,
+ gint pattern_offset_x,
+ gint pattern_offset_y)
+{
+ g_return_if_fail (GIMP_IS_DRAWABLE (drawable));
+ g_return_if_fail (GEGL_IS_BUFFER (buffer));
+ g_return_if_fail (color != NULL || pattern != NULL);
+ g_return_if_fail (pattern == NULL || GIMP_IS_PATTERN (pattern));
+
+ if (pattern)
+ {
+ GeglBuffer *src_buffer;
+ GeglBuffer *dest_buffer;
+ GimpColorProfile *src_profile;
+ GimpColorProfile *dest_profile;
+
+ src_buffer = gimp_pattern_create_buffer (pattern);
+
+ src_profile = gimp_babl_format_get_color_profile (
+ gegl_buffer_get_format (src_buffer));
+ dest_profile = gimp_color_managed_get_color_profile (
+ GIMP_COLOR_MANAGED (drawable));
+
+ if (gimp_color_transform_can_gegl_copy (src_profile, dest_profile))
+ {
+ dest_buffer = g_object_ref (src_buffer);
+ }
+ else
+ {
+ dest_buffer = gegl_buffer_new (gegl_buffer_get_extent (src_buffer),
+ gegl_buffer_get_format (buffer));
+
+ gimp_gegl_convert_color_profile (
+ src_buffer, NULL, src_profile,
+ dest_buffer, NULL, dest_profile,
+ GIMP_COLOR_RENDERING_INTENT_PERCEPTUAL,
+ TRUE,
+ NULL);
+ }
+
+ gegl_buffer_set_pattern (buffer, NULL, dest_buffer,
+ pattern_offset_x, pattern_offset_y);
+
+ g_object_unref (src_buffer);
+ g_object_unref (dest_buffer);
+ }
+ else
+ {
+ GimpRGB image_color;
+ GeglColor *gegl_color;
+
+ gimp_pickable_srgb_to_image_color (GIMP_PICKABLE (drawable),
+ color, &image_color);
+
+ if (! gimp_drawable_has_alpha (drawable))
+ gimp_rgb_set_alpha (&image_color, 1.0);
+
+ gegl_color = gimp_gegl_color_new (&image_color);
+ gegl_buffer_set_color (buffer, NULL, gegl_color);
+ g_object_unref (gegl_color);
+ }
+}
+
+void
+gimp_drawable_fill_boundary (GimpDrawable *drawable,
+ GimpFillOptions *options,
+ const GimpBoundSeg *bound_segs,
+ gint n_bound_segs,
+ gint offset_x,
+ gint offset_y,
+ gboolean push_undo)
+{
+ GimpScanConvert *scan_convert;
+
+ g_return_if_fail (GIMP_IS_DRAWABLE (drawable));
+ g_return_if_fail (gimp_item_is_attached (GIMP_ITEM (drawable)));
+ g_return_if_fail (GIMP_IS_FILL_OPTIONS (options));
+ g_return_if_fail (bound_segs == NULL || n_bound_segs != 0);
+ g_return_if_fail (gimp_fill_options_get_style (options) !=
+ GIMP_FILL_STYLE_PATTERN ||
+ gimp_context_get_pattern (GIMP_CONTEXT (options)) != NULL);
+
+ scan_convert = gimp_scan_convert_new_from_boundary (bound_segs, n_bound_segs,
+ offset_x, offset_y);
+
+ if (scan_convert)
+ {
+ gimp_drawable_fill_scan_convert (drawable, options,
+ scan_convert, push_undo);
+ gimp_scan_convert_free (scan_convert);
+ }
+}
+
+gboolean
+gimp_drawable_fill_vectors (GimpDrawable *drawable,
+ GimpFillOptions *options,
+ GimpVectors *vectors,
+ gboolean push_undo,
+ GError **error)
+{
+ const GimpBezierDesc *bezier;
+
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), FALSE);
+ g_return_val_if_fail (gimp_item_is_attached (GIMP_ITEM (drawable)), FALSE);
+ g_return_val_if_fail (GIMP_IS_FILL_OPTIONS (options), FALSE);
+ g_return_val_if_fail (GIMP_IS_VECTORS (vectors), FALSE);
+ g_return_val_if_fail (gimp_fill_options_get_style (options) !=
+ GIMP_FILL_STYLE_PATTERN ||
+ gimp_context_get_pattern (GIMP_CONTEXT (options)) != NULL,
+ FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ bezier = gimp_vectors_get_bezier (vectors);
+
+ if (bezier && bezier->num_data > 4)
+ {
+ GimpScanConvert *scan_convert = gimp_scan_convert_new ();
+
+ gimp_scan_convert_add_bezier (scan_convert, bezier);
+ gimp_drawable_fill_scan_convert (drawable, options,
+ scan_convert, push_undo);
+
+ gimp_scan_convert_free (scan_convert);
+
+ return TRUE;
+ }
+
+ g_set_error_literal (error, GIMP_ERROR, GIMP_FAILED,
+ _("Not enough points to fill"));
+
+ return FALSE;
+}
+
+void
+gimp_drawable_fill_scan_convert (GimpDrawable *drawable,
+ GimpFillOptions *options,
+ GimpScanConvert *scan_convert,
+ gboolean push_undo)
+{
+ GimpContext *context;
+ GeglBuffer *buffer;
+ GeglBuffer *mask_buffer;
+ gint x, y, w, h;
+ gint off_x;
+ gint off_y;
+
+ g_return_if_fail (GIMP_IS_DRAWABLE (drawable));
+ g_return_if_fail (gimp_item_is_attached (GIMP_ITEM (drawable)));
+ g_return_if_fail (GIMP_IS_FILL_OPTIONS (options));
+ g_return_if_fail (scan_convert != NULL);
+ g_return_if_fail (gimp_fill_options_get_style (options) !=
+ GIMP_FILL_STYLE_PATTERN ||
+ gimp_context_get_pattern (GIMP_CONTEXT (options)) != NULL);
+
+ context = GIMP_CONTEXT (options);
+
+ if (! gimp_item_mask_intersect (GIMP_ITEM (drawable), &x, &y, &w, &h))
+ return;
+
+ /* fill a 1-bpp GeglBuffer with black, this will describe the shape
+ * of the stroke.
+ */
+ mask_buffer = gegl_buffer_new (GEGL_RECTANGLE (0, 0, w, h),
+ babl_format ("Y u8"));
+
+ /* render the stroke into it */
+ gimp_item_get_offset (GIMP_ITEM (drawable), &off_x, &off_y);
+
+ gimp_scan_convert_render (scan_convert, mask_buffer,
+ x + off_x, y + off_y,
+ gimp_fill_options_get_antialias (options));
+
+ buffer = gimp_fill_options_create_buffer (options, drawable,
+ GEGL_RECTANGLE (0, 0, w, h),
+ -x, -y);
+
+ gimp_gegl_apply_opacity (buffer, NULL, NULL, buffer,
+ mask_buffer, 0, 0, 1.0);
+ g_object_unref (mask_buffer);
+
+ /* Apply to drawable */
+ gimp_drawable_apply_buffer (drawable, buffer,
+ GEGL_RECTANGLE (0, 0, w, h),
+ push_undo, C_("undo-type", "Render Stroke"),
+ gimp_context_get_opacity (context),
+ gimp_context_get_paint_mode (context),
+ GIMP_LAYER_COLOR_SPACE_AUTO,
+ GIMP_LAYER_COLOR_SPACE_AUTO,
+ gimp_layer_mode_get_paint_composite_mode (
+ gimp_context_get_paint_mode (context)),
+ NULL, x, y);
+
+ g_object_unref (buffer);
+
+ gimp_drawable_update (drawable, x, y, w, h);
+}
diff --git a/app/core/gimpdrawable-fill.h b/app/core/gimpdrawable-fill.h
new file mode 100644
index 0000000..d85d5bb
--- /dev/null
+++ b/app/core/gimpdrawable-fill.h
@@ -0,0 +1,60 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_DRAWABLE_FILL_H__
+#define __GIMP_DRAWABLE_FILL_H__
+
+
+/* Lowlevel API that is used for initializing entire drawables and
+ * buffers before they are used in images, they don't push an undo.
+ */
+
+void gimp_drawable_fill (GimpDrawable *drawable,
+ GimpContext *context,
+ GimpFillType fill_type);
+void gimp_drawable_fill_buffer (GimpDrawable *drawable,
+ GeglBuffer *buffer,
+ const GimpRGB *color,
+ GimpPattern *pattern,
+ gint pattern_offset_x,
+ gint pattern_offset_y);
+
+
+/* Proper API that is used for actual editing (not just initializing)
+ */
+
+void gimp_drawable_fill_boundary (GimpDrawable *drawable,
+ GimpFillOptions *options,
+ const GimpBoundSeg *bound_segs,
+ gint n_bound_segs,
+ gint offset_x,
+ gint offset_y,
+ gboolean push_undo);
+
+gboolean gimp_drawable_fill_vectors (GimpDrawable *drawable,
+ GimpFillOptions *options,
+ GimpVectors *vectors,
+ gboolean push_undo,
+ GError **error);
+
+void gimp_drawable_fill_scan_convert (GimpDrawable *drawable,
+ GimpFillOptions *options,
+ GimpScanConvert *scan_convert,
+ gboolean push_undo);
+
+
+#endif /* __GIMP_DRAWABLE_FILL_H__ */
diff --git a/app/core/gimpdrawable-filters.c b/app/core/gimpdrawable-filters.c
new file mode 100644
index 0000000..762a870
--- /dev/null
+++ b/app/core/gimpdrawable-filters.c
@@ -0,0 +1,343 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpdrawable-filters.c
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+#include <cairo.h>
+
+#include "core-types.h"
+
+#include "gegl/gimpapplicator.h"
+#include "gegl/gimp-gegl-apply-operation.h"
+#include "gegl/gimp-gegl-loops.h"
+
+#include "gimp.h"
+#include "gimp-utils.h"
+#include "gimpdrawable.h"
+#include "gimpdrawable-filters.h"
+#include "gimpdrawable-private.h"
+#include "gimpfilter.h"
+#include "gimpfilterstack.h"
+#include "gimpimage.h"
+#include "gimpimage-undo.h"
+#include "gimplayer.h"
+#include "gimpprogress.h"
+#include "gimpprojection.h"
+
+
+GimpContainer *
+gimp_drawable_get_filters (GimpDrawable *drawable)
+{
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), NULL);
+
+ return drawable->private->filter_stack;
+}
+
+gboolean
+gimp_drawable_has_filters (GimpDrawable *drawable)
+{
+ GList *list;
+
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), FALSE);
+
+ for (list = GIMP_LIST (drawable->private->filter_stack)->queue->head;
+ list;
+ list = g_list_next (list))
+ {
+ GimpFilter *filter = list->data;
+
+ if (gimp_filter_get_active (filter))
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+void
+gimp_drawable_add_filter (GimpDrawable *drawable,
+ GimpFilter *filter)
+{
+ g_return_if_fail (GIMP_IS_DRAWABLE (drawable));
+ g_return_if_fail (GIMP_IS_FILTER (filter));
+ g_return_if_fail (gimp_drawable_has_filter (drawable, filter) == FALSE);
+
+ gimp_container_add (drawable->private->filter_stack,
+ GIMP_OBJECT (filter));
+}
+
+void
+gimp_drawable_remove_filter (GimpDrawable *drawable,
+ GimpFilter *filter)
+{
+ g_return_if_fail (GIMP_IS_DRAWABLE (drawable));
+ g_return_if_fail (GIMP_IS_FILTER (filter));
+ g_return_if_fail (gimp_drawable_has_filter (drawable, filter) == TRUE);
+
+ gimp_container_remove (drawable->private->filter_stack,
+ GIMP_OBJECT (filter));
+}
+
+gboolean
+gimp_drawable_has_filter (GimpDrawable *drawable,
+ GimpFilter *filter)
+{
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), FALSE);
+ g_return_val_if_fail (GIMP_IS_FILTER (filter), FALSE);
+
+ return gimp_container_have (drawable->private->filter_stack,
+ GIMP_OBJECT (filter));
+}
+
+gboolean
+gimp_drawable_merge_filter (GimpDrawable *drawable,
+ GimpFilter *filter,
+ GimpProgress *progress,
+ const gchar *undo_desc,
+ const Babl *format,
+ gboolean clip,
+ gboolean cancellable,
+ gboolean update)
+{
+ GimpImage *image;
+ GimpApplicator *applicator;
+ gboolean applicator_cache = FALSE;
+ const Babl *applicator_output_format = NULL;
+ GeglBuffer *buffer = NULL;
+ GeglBuffer *dest_buffer;
+ GeglBuffer *undo_buffer = NULL;
+ GeglRectangle undo_rect;
+ GeglBuffer *cache = NULL;
+ GeglRectangle *rects = NULL;
+ gint n_rects = 0;
+ GeglRectangle rect;
+ gboolean success = TRUE;
+
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), FALSE);
+ g_return_val_if_fail (GIMP_IS_FILTER (filter), FALSE);
+ g_return_val_if_fail (progress == NULL || GIMP_IS_PROGRESS (progress), FALSE);
+
+ image = gimp_item_get_image (GIMP_ITEM (drawable));
+ applicator = gimp_filter_get_applicator (filter);
+ dest_buffer = gimp_drawable_get_buffer (drawable);
+
+ if (! format)
+ format = gimp_drawable_get_format (drawable);
+
+ rect = gegl_node_get_bounding_box (gimp_filter_get_node (filter));
+
+ if (! clip && gegl_rectangle_equal (&rect,
+ gegl_buffer_get_extent (dest_buffer)))
+ {
+ clip = TRUE;
+ }
+
+ if (clip)
+ {
+ if (! gimp_item_mask_intersect (GIMP_ITEM (drawable),
+ &rect.x, &rect.y,
+ &rect.width, &rect.height))
+ {
+ return TRUE;
+ }
+
+ if (format != gimp_drawable_get_format (drawable))
+ {
+ buffer = gegl_buffer_new (gegl_buffer_get_extent (dest_buffer),
+ format);
+
+ dest_buffer = buffer;
+ }
+ }
+ else
+ {
+ buffer = gegl_buffer_new (GEGL_RECTANGLE (0, 0, rect.width, rect.height),
+ format);
+
+ dest_buffer = g_object_new (GEGL_TYPE_BUFFER,
+ "source", buffer,
+ "shift-x", -rect.x,
+ "shift-y", -rect.y,
+ NULL);
+ }
+
+ if (applicator)
+ {
+ const GeglRectangle *crop_rect;
+
+ crop_rect = gimp_applicator_get_crop (applicator);
+
+ if (crop_rect && ! gegl_rectangle_intersect (&rect, &rect, crop_rect))
+ return TRUE;
+
+ /* the cache and its valid rectangles are the region that
+ * has already been processed by this applicator.
+ */
+ cache = gimp_applicator_get_cache_buffer (applicator,
+ &rects, &n_rects);
+
+ /* skip the cache and output-format conversion while processing
+ * the remaining area, so that the result is written directly to
+ * the drawable's buffer.
+ */
+ applicator_cache = gimp_applicator_get_cache (applicator);
+ applicator_output_format = gimp_applicator_get_output_format (applicator);
+
+ gimp_applicator_set_cache (applicator, FALSE);
+ if (applicator_output_format == format)
+ gimp_applicator_set_output_format (applicator, NULL);
+ }
+
+ if (! buffer)
+ {
+ gegl_rectangle_align_to_buffer (
+ &undo_rect,
+ &rect,
+ gimp_drawable_get_buffer (drawable),
+ GEGL_RECTANGLE_ALIGNMENT_SUPERSET);
+
+ undo_buffer = gegl_buffer_new (GEGL_RECTANGLE (0, 0,
+ undo_rect.width,
+ undo_rect.height),
+ gimp_drawable_get_format (drawable));
+
+ gimp_gegl_buffer_copy (gimp_drawable_get_buffer (drawable),
+ &undo_rect,
+ GEGL_ABYSS_NONE,
+ undo_buffer,
+ GEGL_RECTANGLE (0, 0, 0, 0));
+ }
+
+ gimp_projection_stop_rendering (gimp_image_get_projection (image));
+
+ /* make sure we have a source node - this connects the filter stack to the
+ * underlying source node
+ */
+ (void) gimp_drawable_get_source_node (drawable);
+
+ if (gimp_gegl_apply_cached_operation (gimp_drawable_get_buffer (drawable),
+ progress, undo_desc,
+ gimp_filter_get_node (filter), FALSE,
+ dest_buffer, &rect, FALSE,
+ cache, rects, n_rects,
+ cancellable))
+ {
+ /* finished successfully */
+
+ if (clip)
+ {
+ if (buffer)
+ {
+ gimp_drawable_set_buffer_full (drawable,
+ TRUE, undo_desc,
+ buffer, NULL,
+ FALSE);
+ }
+ else
+ {
+ gimp_drawable_push_undo (drawable, undo_desc, undo_buffer,
+ undo_rect.x, undo_rect.y,
+ undo_rect.width, undo_rect.height);
+ }
+ }
+ else
+ {
+ GimpLayerMask *mask = NULL;
+ gint offset_x;
+ gint offset_y;
+
+ gimp_item_get_offset (GIMP_ITEM (drawable), &offset_x, &offset_y);
+
+ if (GIMP_IS_LAYER (drawable))
+ mask = gimp_layer_get_mask (GIMP_LAYER (drawable));
+
+ if (mask)
+ {
+ gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_DRAWABLE_MOD,
+ undo_desc);
+ }
+
+ gimp_drawable_set_buffer_full (
+ drawable, TRUE, undo_desc, buffer,
+ GEGL_RECTANGLE (offset_x + rect.x, offset_y + rect.y, 0, 0),
+ FALSE);
+
+ if (mask)
+ {
+ gimp_item_resize (GIMP_ITEM (mask),
+ gimp_get_default_context (image->gimp),
+ GIMP_FILL_TRANSPARENT,
+ rect.width, rect.height,
+ -rect.x, -rect.y);
+
+ gimp_image_undo_group_end (image);
+ }
+ }
+ }
+ else
+ {
+ /* canceled by the user */
+
+ if (clip)
+ {
+ gimp_gegl_buffer_copy (undo_buffer,
+ GEGL_RECTANGLE (0, 0,
+ undo_rect.width,
+ undo_rect.height),
+ GEGL_ABYSS_NONE,
+ gimp_drawable_get_buffer (drawable),
+ &undo_rect);
+ }
+
+ success = FALSE;
+ }
+
+ if (clip)
+ {
+ g_clear_object (&undo_buffer);
+ g_clear_object (&buffer);
+ }
+ else
+ {
+ g_object_unref (buffer);
+ g_object_unref (dest_buffer);
+ }
+
+ if (cache)
+ {
+ g_object_unref (cache);
+ g_free (rects);
+ }
+
+ if (applicator)
+ {
+ gimp_applicator_set_cache (applicator, applicator_cache);
+ gimp_applicator_set_output_format (applicator, applicator_output_format);
+ }
+
+ if (update)
+ {
+ gimp_drawable_update (drawable,
+ rect.x, rect.y,
+ rect.width, rect.height);
+ }
+
+ return success;
+}
diff --git a/app/core/gimpdrawable-filters.h b/app/core/gimpdrawable-filters.h
new file mode 100644
index 0000000..504d402
--- /dev/null
+++ b/app/core/gimpdrawable-filters.h
@@ -0,0 +1,46 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpdrawable-filters.h
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_DRAWABLE_FILTERS_H__
+#define __GIMP_DRAWABLE_FILTERS_H__
+
+
+GimpContainer * gimp_drawable_get_filters (GimpDrawable *drawable);
+
+gboolean gimp_drawable_has_filters (GimpDrawable *drawable);
+
+void gimp_drawable_add_filter (GimpDrawable *drawable,
+ GimpFilter *filter);
+void gimp_drawable_remove_filter (GimpDrawable *drawable,
+ GimpFilter *filter);
+
+gboolean gimp_drawable_has_filter (GimpDrawable *drawable,
+ GimpFilter *filter);
+
+gboolean gimp_drawable_merge_filter (GimpDrawable *drawable,
+ GimpFilter *filter,
+ GimpProgress *progress,
+ const gchar *undo_desc,
+ const Babl *format,
+ gboolean clip,
+ gboolean cancellable,
+ gboolean update);
+
+
+#endif /* __GIMP_DRAWABLE_FILTERS_H__ */
diff --git a/app/core/gimpdrawable-floating-selection.c b/app/core/gimpdrawable-floating-selection.c
new file mode 100644
index 0000000..71866b1
--- /dev/null
+++ b/app/core/gimpdrawable-floating-selection.c
@@ -0,0 +1,512 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <cairo.h>
+#include <gegl.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+#include "libgimpbase/gimpbase.h"
+
+#include "core-types.h"
+
+#include "gegl/gimpapplicator.h"
+
+#include "gimpchannel.h"
+#include "gimpdrawable-floating-selection.h"
+#include "gimpdrawable-filters.h"
+#include "gimpdrawable-private.h"
+#include "gimpimage.h"
+#include "gimplayer.h"
+
+#include "gimp-log.h"
+
+#include "gimp-intl.h"
+
+
+/* local function prototypes */
+
+static void gimp_drawable_remove_fs_filter (GimpDrawable *drawable);
+static void gimp_drawable_sync_fs_filter (GimpDrawable *drawable);
+
+static void gimp_drawable_fs_notify (GObject *object,
+ const GParamSpec *pspec,
+ GimpDrawable *drawable);
+static void gimp_drawable_fs_lock_position_changed (GimpDrawable *signal_drawable,
+ GimpDrawable *drawable);
+static void gimp_drawable_fs_format_changed (GimpDrawable *signal_drawable,
+ GimpDrawable *drawable);
+static void gimp_drawable_fs_affect_changed (GimpImage *image,
+ GimpChannelType channel,
+ GimpDrawable *drawable);
+static void gimp_drawable_fs_mask_changed (GimpImage *image,
+ GimpDrawable *drawable);
+static void gimp_drawable_fs_visibility_changed (GimpLayer *fs,
+ GimpDrawable *drawable);
+static void gimp_drawable_fs_excludes_backdrop_changed (GimpLayer *fs,
+ GimpDrawable *drawable);
+static void gimp_drawable_fs_bounding_box_changed (GimpLayer *fs,
+ GimpDrawable *drawable);
+static void gimp_drawable_fs_update (GimpLayer *fs,
+ gint x,
+ gint y,
+ gint width,
+ gint height,
+ GimpDrawable *drawable);
+
+
+/* public functions */
+
+GimpLayer *
+gimp_drawable_get_floating_sel (GimpDrawable *drawable)
+{
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), NULL);
+
+ return drawable->private->floating_selection;
+}
+
+void
+gimp_drawable_attach_floating_sel (GimpDrawable *drawable,
+ GimpLayer *fs)
+{
+ GimpImage *image;
+
+ g_return_if_fail (GIMP_IS_DRAWABLE (drawable));
+ g_return_if_fail (gimp_item_is_attached (GIMP_ITEM (drawable)));
+ g_return_if_fail (gimp_drawable_get_floating_sel (drawable) == NULL);
+ g_return_if_fail (GIMP_IS_LAYER (fs));
+
+ GIMP_LOG (FLOATING_SELECTION, "%s", G_STRFUNC);
+
+ image = gimp_item_get_image (GIMP_ITEM (drawable));
+
+ drawable->private->floating_selection = fs;
+ gimp_image_set_floating_selection (image, fs);
+
+ /* clear the selection */
+ gimp_drawable_invalidate_boundary (GIMP_DRAWABLE (fs));
+
+ gimp_item_bind_visible_to_active (GIMP_ITEM (fs), FALSE);
+ gimp_filter_set_active (GIMP_FILTER (fs), FALSE);
+
+ _gimp_drawable_add_floating_sel_filter (drawable);
+
+ g_signal_connect (fs, "visibility-changed",
+ G_CALLBACK (gimp_drawable_fs_visibility_changed),
+ drawable);
+ g_signal_connect (fs, "excludes-backdrop-changed",
+ G_CALLBACK (gimp_drawable_fs_excludes_backdrop_changed),
+ drawable);
+ g_signal_connect (fs, "bounding-box-changed",
+ G_CALLBACK (gimp_drawable_fs_bounding_box_changed),
+ drawable);
+ g_signal_connect (fs, "update",
+ G_CALLBACK (gimp_drawable_fs_update),
+ drawable);
+
+ gimp_drawable_fs_update (fs,
+ 0, 0,
+ gimp_item_get_width (GIMP_ITEM (fs)),
+ gimp_item_get_height (GIMP_ITEM (fs)),
+ drawable);
+}
+
+void
+gimp_drawable_detach_floating_sel (GimpDrawable *drawable)
+{
+ GimpImage *image;
+ GimpLayer *fs;
+
+ g_return_if_fail (GIMP_IS_DRAWABLE (drawable));
+ g_return_if_fail (gimp_drawable_get_floating_sel (drawable) != NULL);
+
+ GIMP_LOG (FLOATING_SELECTION, "%s", G_STRFUNC);
+
+ image = gimp_item_get_image (GIMP_ITEM (drawable));
+ fs = drawable->private->floating_selection;
+
+ gimp_drawable_remove_fs_filter (drawable);
+
+ g_signal_handlers_disconnect_by_func (fs,
+ gimp_drawable_fs_visibility_changed,
+ drawable);
+ g_signal_handlers_disconnect_by_func (fs,
+ gimp_drawable_fs_excludes_backdrop_changed,
+ drawable);
+ g_signal_handlers_disconnect_by_func (fs,
+ gimp_drawable_fs_bounding_box_changed,
+ drawable);
+ g_signal_handlers_disconnect_by_func (fs,
+ gimp_drawable_fs_update,
+ drawable);
+
+ gimp_drawable_fs_update (fs,
+ 0, 0,
+ gimp_item_get_width (GIMP_ITEM (fs)),
+ gimp_item_get_height (GIMP_ITEM (fs)),
+ drawable);
+
+ gimp_item_bind_visible_to_active (GIMP_ITEM (fs), TRUE);
+
+ /* clear the selection */
+ gimp_drawable_invalidate_boundary (GIMP_DRAWABLE (fs));
+
+ gimp_image_set_floating_selection (image, NULL);
+ drawable->private->floating_selection = NULL;
+}
+
+GimpFilter *
+gimp_drawable_get_floating_sel_filter (GimpDrawable *drawable)
+{
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), NULL);
+ g_return_val_if_fail (gimp_drawable_get_floating_sel (drawable) != NULL, NULL);
+
+ /* Ensure that the graph is construced before the filter is used.
+ * Otherwise, we rely on the projection to cause the graph to be
+ * constructed, which fails for images that aren't displayed.
+ */
+ gimp_filter_get_node (GIMP_FILTER (drawable));
+
+ return drawable->private->fs_filter;
+}
+
+
+/* private functions */
+
+void
+_gimp_drawable_add_floating_sel_filter (GimpDrawable *drawable)
+{
+ GimpDrawablePrivate *private = drawable->private;
+ GimpImage *image = gimp_item_get_image (GIMP_ITEM (drawable));
+ GimpLayer *fs = gimp_drawable_get_floating_sel (drawable);
+ GeglNode *node;
+ GeglNode *fs_source;
+
+ if (! private->source_node)
+ return;
+
+ private->fs_filter = gimp_filter_new (_("Floating Selection"));
+ gimp_viewable_set_icon_name (GIMP_VIEWABLE (private->fs_filter),
+ "gimp-floating-selection");
+
+ node = gimp_filter_get_node (private->fs_filter);
+
+ fs_source = gimp_drawable_get_source_node (GIMP_DRAWABLE (fs));
+
+ /* rip the fs' source node out of its graph */
+ if (fs->layer_offset_node)
+ {
+ gegl_node_disconnect (fs->layer_offset_node, "input");
+ gegl_node_remove_child (gimp_filter_get_node (GIMP_FILTER (fs)),
+ fs_source);
+ }
+
+ gegl_node_add_child (node, fs_source);
+
+ private->fs_applicator = gimp_applicator_new (node);
+
+ gimp_filter_set_applicator (private->fs_filter, private->fs_applicator);
+
+ gimp_applicator_set_cache (private->fs_applicator, TRUE);
+
+ private->fs_crop_node = gegl_node_new_child (node,
+ "operation", "gegl:nop",
+ NULL);
+
+ gegl_node_connect_to (fs_source, "output",
+ private->fs_crop_node, "input");
+ gegl_node_connect_to (private->fs_crop_node, "output",
+ node, "aux");
+
+ gimp_drawable_add_filter (drawable, private->fs_filter);
+
+ g_signal_connect (fs, "notify",
+ G_CALLBACK (gimp_drawable_fs_notify),
+ drawable);
+ g_signal_connect (drawable, "notify::offset-x",
+ G_CALLBACK (gimp_drawable_fs_notify),
+ drawable);
+ g_signal_connect (drawable, "notify::offset-y",
+ G_CALLBACK (gimp_drawable_fs_notify),
+ drawable);
+ g_signal_connect (drawable, "lock-position-changed",
+ G_CALLBACK (gimp_drawable_fs_lock_position_changed),
+ drawable);
+ g_signal_connect (drawable, "format-changed",
+ G_CALLBACK (gimp_drawable_fs_format_changed),
+ drawable);
+ g_signal_connect (image, "component-active-changed",
+ G_CALLBACK (gimp_drawable_fs_affect_changed),
+ drawable);
+ g_signal_connect (image, "mask-changed",
+ G_CALLBACK (gimp_drawable_fs_mask_changed),
+ drawable);
+
+ gimp_drawable_sync_fs_filter (drawable);
+}
+
+
+/* private functions */
+
+static void
+gimp_drawable_remove_fs_filter (GimpDrawable *drawable)
+{
+ GimpDrawablePrivate *private = drawable->private;
+ GimpImage *image = gimp_item_get_image (GIMP_ITEM (drawable));
+ GimpLayer *fs = gimp_drawable_get_floating_sel (drawable);
+
+ if (private->fs_filter)
+ {
+ GeglNode *node;
+ GeglNode *fs_source;
+
+ g_signal_handlers_disconnect_by_func (fs,
+ gimp_drawable_fs_notify,
+ drawable);
+ g_signal_handlers_disconnect_by_func (drawable,
+ gimp_drawable_fs_notify,
+ drawable);
+ g_signal_handlers_disconnect_by_func (drawable,
+ gimp_drawable_fs_lock_position_changed,
+ drawable);
+ g_signal_handlers_disconnect_by_func (drawable,
+ gimp_drawable_fs_format_changed,
+ drawable);
+ g_signal_handlers_disconnect_by_func (image,
+ gimp_drawable_fs_affect_changed,
+ drawable);
+ g_signal_handlers_disconnect_by_func (image,
+ gimp_drawable_fs_mask_changed,
+ drawable);
+
+ gimp_drawable_remove_filter (drawable, private->fs_filter);
+
+ node = gimp_filter_get_node (private->fs_filter);
+
+ fs_source = gimp_drawable_get_source_node (GIMP_DRAWABLE (fs));
+
+ gegl_node_remove_child (node, fs_source);
+
+ /* plug the fs' source node back into its graph */
+ if (fs->layer_offset_node)
+ {
+ gegl_node_add_child (gimp_filter_get_node (GIMP_FILTER (fs)),
+ fs_source);
+ gegl_node_connect_to (fs_source, "output",
+ fs->layer_offset_node, "input");
+ }
+
+ g_clear_object (&private->fs_filter);
+ g_clear_object (&private->fs_applicator);
+
+ private->fs_crop_node = NULL;
+
+ gimp_drawable_update_bounding_box (drawable);
+ }
+}
+
+static void
+gimp_drawable_sync_fs_filter (GimpDrawable *drawable)
+{
+ GimpDrawablePrivate *private = drawable->private;
+ GimpImage *image = gimp_item_get_image (GIMP_ITEM (drawable));
+ GimpChannel *mask = gimp_image_get_mask (image);
+ GimpLayer *fs = gimp_drawable_get_floating_sel (drawable);
+ gint off_x, off_y;
+ gint fs_off_x, fs_off_y;
+
+ gimp_filter_set_active (private->fs_filter,
+ gimp_item_get_visible (GIMP_ITEM (fs)));
+
+ gimp_item_get_offset (GIMP_ITEM (drawable), &off_x, &off_y);
+ gimp_item_get_offset (GIMP_ITEM (fs), &fs_off_x, &fs_off_y);
+
+ if (gimp_item_get_clip (GIMP_ITEM (drawable), GIMP_TRANSFORM_RESIZE_ADJUST) ==
+ GIMP_TRANSFORM_RESIZE_CLIP ||
+ ! gimp_drawable_has_alpha (drawable))
+ {
+ gegl_node_set (
+ private->fs_crop_node,
+ "operation", "gegl:crop",
+ "x", (gdouble) (off_x - fs_off_x),
+ "y", (gdouble) (off_y - fs_off_y),
+ "width", (gdouble) gimp_item_get_width (GIMP_ITEM (drawable)),
+ "height", (gdouble) gimp_item_get_height (GIMP_ITEM (drawable)),
+ NULL);
+ }
+ else
+ {
+ gegl_node_set (
+ private->fs_crop_node,
+ "operation", "gegl:nop",
+ NULL);
+ }
+
+ gimp_applicator_set_apply_offset (private->fs_applicator,
+ fs_off_x - off_x,
+ fs_off_y - off_y);
+
+ if (gimp_channel_is_empty (mask))
+ {
+ gimp_applicator_set_mask_buffer (private->fs_applicator, NULL);
+ }
+ else
+ {
+ GeglBuffer *buffer = gimp_drawable_get_buffer (GIMP_DRAWABLE (mask));
+
+ gimp_applicator_set_mask_buffer (private->fs_applicator, buffer);
+ gimp_applicator_set_mask_offset (private->fs_applicator,
+ -off_x, -off_y);
+ }
+
+ gimp_applicator_set_opacity (private->fs_applicator,
+ gimp_layer_get_opacity (fs));
+ gimp_applicator_set_mode (private->fs_applicator,
+ gimp_layer_get_mode (fs),
+ gimp_layer_get_blend_space (fs),
+ gimp_layer_get_composite_space (fs),
+ gimp_layer_get_composite_mode (fs));
+ gimp_applicator_set_affect (private->fs_applicator,
+ gimp_drawable_get_active_mask (drawable));
+ gimp_applicator_set_output_format (private->fs_applicator,
+ gimp_drawable_get_format (drawable));
+
+ gimp_drawable_update_bounding_box (drawable);
+}
+
+static void
+gimp_drawable_fs_notify (GObject *object,
+ const GParamSpec *pspec,
+ GimpDrawable *drawable)
+{
+ if (! strcmp (pspec->name, "offset-x") ||
+ ! strcmp (pspec->name, "offset-y") ||
+ ! strcmp (pspec->name, "visible") ||
+ ! strcmp (pspec->name, "mode") ||
+ ! strcmp (pspec->name, "blend-space") ||
+ ! strcmp (pspec->name, "composite-space") ||
+ ! strcmp (pspec->name, "composite-mode") ||
+ ! strcmp (pspec->name, "opacity"))
+ {
+ gimp_drawable_sync_fs_filter (drawable);
+ }
+}
+
+static void
+gimp_drawable_fs_lock_position_changed (GimpDrawable *signal_drawable,
+ GimpDrawable *drawable)
+{
+ GimpLayer *fs = gimp_drawable_get_floating_sel (drawable);
+
+ gimp_drawable_sync_fs_filter (drawable);
+
+ gimp_drawable_update (GIMP_DRAWABLE (fs), 0, 0, -1, -1);
+}
+
+static void
+gimp_drawable_fs_format_changed (GimpDrawable *signal_drawable,
+ GimpDrawable *drawable)
+{
+ GimpLayer *fs = gimp_drawable_get_floating_sel (drawable);
+
+ gimp_drawable_sync_fs_filter (drawable);
+
+ gimp_drawable_update (GIMP_DRAWABLE (fs), 0, 0, -1, -1);
+}
+
+static void
+gimp_drawable_fs_affect_changed (GimpImage *image,
+ GimpChannelType channel,
+ GimpDrawable *drawable)
+{
+ GimpLayer *fs = gimp_drawable_get_floating_sel (drawable);
+
+ gimp_drawable_sync_fs_filter (drawable);
+
+ gimp_drawable_update (GIMP_DRAWABLE (fs), 0, 0, -1, -1);
+}
+
+static void
+gimp_drawable_fs_mask_changed (GimpImage *image,
+ GimpDrawable *drawable)
+{
+ GimpLayer *fs = gimp_drawable_get_floating_sel (drawable);
+
+ gimp_drawable_sync_fs_filter (drawable);
+
+ gimp_drawable_update (GIMP_DRAWABLE (fs), 0, 0, -1, -1);
+}
+
+static void
+gimp_drawable_fs_visibility_changed (GimpLayer *fs,
+ GimpDrawable *drawable)
+{
+ if (gimp_layer_get_excludes_backdrop (fs))
+ gimp_drawable_update (drawable, 0, 0, -1, -1);
+ else
+ gimp_drawable_update (GIMP_DRAWABLE (fs), 0, 0, -1, -1);
+}
+
+static void
+gimp_drawable_fs_excludes_backdrop_changed (GimpLayer *fs,
+ GimpDrawable *drawable)
+{
+ if (gimp_item_get_visible (GIMP_ITEM (fs)))
+ gimp_drawable_update (drawable, 0, 0, -1, -1);
+}
+
+static void
+gimp_drawable_fs_bounding_box_changed (GimpLayer *fs,
+ GimpDrawable *drawable)
+{
+ gimp_drawable_update_bounding_box (drawable);
+}
+
+static void
+gimp_drawable_fs_update (GimpLayer *fs,
+ gint x,
+ gint y,
+ gint width,
+ gint height,
+ GimpDrawable *drawable)
+{
+ GeglRectangle bounding_box;
+ GeglRectangle rect;
+ gint fs_off_x, fs_off_y;
+ gint off_x, off_y;
+
+ gimp_item_get_offset (GIMP_ITEM (fs), &fs_off_x, &fs_off_y);
+ gimp_item_get_offset (GIMP_ITEM (drawable), &off_x, &off_y);
+
+ bounding_box = gimp_drawable_get_bounding_box (drawable);
+
+ bounding_box.x += off_x;
+ bounding_box.y += off_y;
+
+ rect.x = x + fs_off_x;
+ rect.y = y + fs_off_y;
+ rect.width = width;
+ rect.height = height;
+
+ if (gegl_rectangle_intersect (&rect, &rect, &bounding_box))
+ {
+ gimp_drawable_update (drawable,
+ rect.x - off_x, rect.y - off_y,
+ rect.width, rect.height);
+ }
+}
diff --git a/app/core/gimpdrawable-floating-selection.h b/app/core/gimpdrawable-floating-selection.h
new file mode 100644
index 0000000..336a0cf
--- /dev/null
+++ b/app/core/gimpdrawable-floating-selection.h
@@ -0,0 +1,31 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_DRAWABLE_FLOATING_SELECTION_H__
+#define __GIMP_DRAWABLE_FLOATING_SELECTION_H__
+
+
+GimpLayer * gimp_drawable_get_floating_sel (GimpDrawable *drawable);
+void gimp_drawable_attach_floating_sel (GimpDrawable *drawable,
+ GimpLayer *floating_sel);
+void gimp_drawable_detach_floating_sel (GimpDrawable *drawable);
+GimpFilter * gimp_drawable_get_floating_sel_filter (GimpDrawable *drawable);
+
+void _gimp_drawable_add_floating_sel_filter (GimpDrawable *drawable);
+
+
+#endif /* __GIMP_DRAWABLE_FLOATING_SELECTION_H__ */
diff --git a/app/core/gimpdrawable-foreground-extract.c b/app/core/gimpdrawable-foreground-extract.c
new file mode 100644
index 0000000..5846363
--- /dev/null
+++ b/app/core/gimpdrawable-foreground-extract.c
@@ -0,0 +1,150 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gio/gio.h>
+#include <gegl.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+#include "libgimpbase/gimpbase.h"
+
+#include "core-types.h"
+
+#include "gegl/gimp-gegl-utils.h"
+
+#include "gimpchannel.h"
+#include "gimpdrawable.h"
+#include "gimpdrawable-foreground-extract.h"
+#include "gimpimage.h"
+#include "gimpprogress.h"
+
+#include "gimp-intl.h"
+
+
+/* public functions */
+
+GeglBuffer *
+gimp_drawable_foreground_extract (GimpDrawable *drawable,
+ GimpMattingEngine engine,
+ gint global_iterations,
+ gint levin_levels,
+ gint levin_active_levels,
+ GeglBuffer *trimap,
+ GimpProgress *progress)
+{
+ GeglBuffer *drawable_buffer;
+ GeglNode *gegl;
+ GeglNode *input_node;
+ GeglNode *trimap_node;
+ GeglNode *matting_node;
+ GeglNode *output_node;
+ GeglBuffer *buffer;
+ GeglProcessor *processor;
+ gdouble value;
+ gint off_x, off_y;
+
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), NULL);
+ g_return_val_if_fail (GEGL_IS_BUFFER (trimap), NULL);
+ g_return_val_if_fail (progress == NULL || GIMP_IS_PROGRESS (progress), NULL);
+
+ progress = gimp_progress_start (progress, FALSE,
+ _("Computing alpha of unknown pixels"));
+
+ drawable_buffer = gimp_drawable_get_buffer (drawable);
+
+ gegl = gegl_node_new ();
+
+ trimap_node = gegl_node_new_child (gegl,
+ "operation", "gegl:buffer-source",
+ "buffer", trimap,
+ NULL);
+ input_node = gegl_node_new_child (gegl,
+ "operation", "gegl:buffer-source",
+ "buffer", drawable_buffer,
+ NULL);
+ output_node = gegl_node_new_child (gegl,
+ "operation", "gegl:buffer-sink",
+ "buffer", &buffer,
+ "format", NULL,
+ NULL);
+
+ if (engine == GIMP_MATTING_ENGINE_GLOBAL)
+ {
+ matting_node = gegl_node_new_child (gegl,
+ "operation", "gegl:matting-global",
+ "iterations", global_iterations,
+ NULL);
+ }
+ else
+ {
+ matting_node = gegl_node_new_child (gegl,
+ "operation", "gegl:matting-levin",
+ "levels", levin_levels,
+ "active_levels", levin_active_levels,
+ NULL);
+ }
+
+ gimp_item_get_offset (GIMP_ITEM (drawable), &off_x, &off_y);
+
+ if (off_x || off_y)
+ {
+ GeglNode *pre;
+ GeglNode *post;
+
+ pre = gegl_node_new_child (gegl,
+ "operation", "gegl:translate",
+ "x", -1.0 * off_x,
+ "y", -1.0 * off_y,
+ NULL);
+ post = gegl_node_new_child (gegl,
+ "operation", "gegl:translate",
+ "x", 1.0 * off_x,
+ "y", 1.0 * off_y,
+ NULL);
+
+ gegl_node_connect_to (trimap_node, "output", pre, "input");
+ gegl_node_connect_to (pre, "output", matting_node, "aux");
+ gegl_node_link_many (input_node, matting_node, post, output_node, NULL);
+ }
+ else
+ {
+ gegl_node_connect_to (input_node, "output",
+ matting_node, "input");
+ gegl_node_connect_to (trimap_node, "output",
+ matting_node, "aux");
+ gegl_node_connect_to (matting_node, "output",
+ output_node, "input");
+ }
+
+ processor = gegl_node_new_processor (output_node, NULL);
+
+ while (gegl_processor_work (processor, &value))
+ {
+ if (progress)
+ gimp_progress_set_value (progress, value);
+ }
+
+ if (progress)
+ gimp_progress_end (progress);
+
+ g_object_unref (processor);
+
+ g_object_unref (gegl);
+
+ return buffer;
+}
diff --git a/app/core/gimpdrawable-foreground-extract.h b/app/core/gimpdrawable-foreground-extract.h
new file mode 100644
index 0000000..19e2356
--- /dev/null
+++ b/app/core/gimpdrawable-foreground-extract.h
@@ -0,0 +1,31 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_DRAWABLE_FOREGROUND_EXTRACT_H__
+#define __GIMP_DRAWABLE_FOREGROUND_EXTRACT_H__
+
+
+GeglBuffer * gimp_drawable_foreground_extract (GimpDrawable *drawable,
+ GimpMattingEngine engine,
+ gint global_iterations,
+ gint levin_levels,
+ gint levin_active_levels,
+ GeglBuffer *trimap,
+ GimpProgress *progress);
+
+
+#endif /* __GIMP_DRAWABLE_FOREGROUND_EXTRACT_H__ */
diff --git a/app/core/gimpdrawable-gradient.c b/app/core/gimpdrawable-gradient.c
new file mode 100644
index 0000000..2d7cbe8
--- /dev/null
+++ b/app/core/gimpdrawable-gradient.c
@@ -0,0 +1,313 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <cairo.h>
+#include <gegl.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+#include "libgimpmath/gimpmath.h"
+
+#include "core-types.h"
+
+#include "gegl/gimp-gegl-apply-operation.h"
+#include "gegl/gimp-gegl-loops.h"
+#include "gegl/gimp-gegl-utils.h"
+
+#include "operations/layer-modes/gimp-layer-modes.h"
+
+#include "gimp.h"
+#include "gimpchannel.h"
+#include "gimpcontext.h"
+#include "gimpdrawable-gradient.h"
+#include "gimpgradient.h"
+#include "gimpimage.h"
+#include "gimpprogress.h"
+
+#include "gimp-intl.h"
+
+
+/* public functions */
+
+void
+gimp_drawable_gradient (GimpDrawable *drawable,
+ GimpContext *context,
+ GimpGradient *gradient,
+ GeglDistanceMetric metric,
+ GimpLayerMode paint_mode,
+ GimpGradientType gradient_type,
+ gdouble opacity,
+ gdouble offset,
+ GimpRepeatMode repeat,
+ gboolean reverse,
+ GimpGradientBlendColorSpace blend_color_space,
+ gboolean supersample,
+ gint max_depth,
+ gdouble threshold,
+ gboolean dither,
+ gdouble startx,
+ gdouble starty,
+ gdouble endx,
+ gdouble endy,
+ GimpProgress *progress)
+{
+ GimpImage *image;
+ GeglBuffer *buffer;
+ GeglBuffer *shapeburst = NULL;
+ GeglNode *render;
+ gint x, y, width, height;
+
+ g_return_if_fail (GIMP_IS_DRAWABLE (drawable));
+ g_return_if_fail (gimp_item_is_attached (GIMP_ITEM (drawable)));
+ g_return_if_fail (GIMP_IS_CONTEXT (context));
+ g_return_if_fail (GIMP_IS_GRADIENT (gradient));
+ g_return_if_fail (progress == NULL || GIMP_IS_PROGRESS (progress));
+
+ image = gimp_item_get_image (GIMP_ITEM (drawable));
+
+ if (! gimp_item_mask_intersect (GIMP_ITEM (drawable), &x, &y, &width, &height))
+ return;
+
+ gimp_set_busy (image->gimp);
+
+ /* Always create an alpha temp buf (for generality) */
+ buffer = gegl_buffer_new (GEGL_RECTANGLE (x, y, width, height),
+ gimp_drawable_get_format_with_alpha (drawable));
+
+ if (gradient_type >= GIMP_GRADIENT_SHAPEBURST_ANGULAR &&
+ gradient_type <= GIMP_GRADIENT_SHAPEBURST_DIMPLED)
+ {
+ shapeburst =
+ gimp_drawable_gradient_shapeburst_distmap (drawable, metric,
+ GEGL_RECTANGLE (x, y, width, height),
+ progress);
+ }
+
+ gimp_drawable_gradient_adjust_coords (drawable,
+ gradient_type,
+ GEGL_RECTANGLE (x, y, width, height),
+ &startx, &starty, &endx, &endy);
+
+ render = gegl_node_new_child (NULL,
+ "operation", "gimp:gradient",
+ "context", context,
+ "gradient", gradient,
+ "start-x", startx,
+ "start-y", starty,
+ "end-x", endx,
+ "end-y", endy,
+ "gradient-type", gradient_type,
+ "gradient-repeat", repeat,
+ "offset", offset,
+ "gradient-reverse", reverse,
+ "gradient-blend-color-space", blend_color_space,
+ "supersample", supersample,
+ "supersample-depth", max_depth,
+ "supersample-threshold", threshold,
+ "dither", dither,
+ NULL);
+
+ gimp_gegl_apply_operation (shapeburst, progress, C_("undo-type", "Gradient"),
+ render,
+ buffer, GEGL_RECTANGLE (x, y, width, height),
+ FALSE);
+
+ g_object_unref (render);
+
+ if (shapeburst)
+ g_object_unref (shapeburst);
+
+ gimp_drawable_apply_buffer (drawable, buffer,
+ GEGL_RECTANGLE (x, y, width, height),
+ TRUE, C_("undo-type", "Gradient"),
+ opacity, paint_mode,
+ GIMP_LAYER_COLOR_SPACE_AUTO,
+ GIMP_LAYER_COLOR_SPACE_AUTO,
+ gimp_layer_mode_get_paint_composite_mode (paint_mode),
+ NULL, x, y);
+
+ gimp_drawable_update (drawable, x, y, width, height);
+
+ g_object_unref (buffer);
+
+ gimp_unset_busy (image->gimp);
+}
+
+GeglBuffer *
+gimp_drawable_gradient_shapeburst_distmap (GimpDrawable *drawable,
+ GeglDistanceMetric metric,
+ const GeglRectangle *region,
+ GimpProgress *progress)
+{
+ GimpChannel *mask;
+ GimpImage *image;
+ GeglBuffer *dist_buffer;
+ GeglBuffer *temp_buffer;
+ GeglNode *shapeburst;
+
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), NULL);
+ g_return_val_if_fail (gimp_item_is_attached (GIMP_ITEM (drawable)), NULL);
+ g_return_val_if_fail (progress == NULL || GIMP_IS_PROGRESS (progress), NULL);
+
+ image = gimp_item_get_image (GIMP_ITEM (drawable));
+
+ /* allocate the distance map */
+ dist_buffer = gegl_buffer_new (region, babl_format ("Y float"));
+
+ /* allocate the selection mask copy */
+ temp_buffer = gegl_buffer_new (region, babl_format ("Y float"));
+
+ mask = gimp_image_get_mask (image);
+
+ /* If the image mask is not empty, use it as the shape burst source */
+ if (! gimp_channel_is_empty (mask))
+ {
+ gint x, y, width, height;
+ gint off_x, off_y;
+
+ gimp_item_mask_intersect (GIMP_ITEM (drawable), &x, &y, &width, &height);
+ gimp_item_get_offset (GIMP_ITEM (drawable), &off_x, &off_y);
+
+ /* copy the mask to the temp mask */
+ gimp_gegl_buffer_copy (
+ gimp_drawable_get_buffer (GIMP_DRAWABLE (mask)),
+ GEGL_RECTANGLE (x + off_x, y + off_y, width, height),
+ GEGL_ABYSS_NONE, temp_buffer, region);
+ }
+ else
+ {
+ /* If the intended drawable has an alpha channel, use that */
+ if (gimp_drawable_has_alpha (drawable))
+ {
+ const Babl *component_format;
+
+ component_format = babl_format ("A float");
+
+ /* extract the aplha into the temp mask */
+ gegl_buffer_set_format (temp_buffer, component_format);
+ gimp_gegl_buffer_copy (gimp_drawable_get_buffer (drawable), region,
+ GEGL_ABYSS_NONE,
+ temp_buffer, region);
+ gegl_buffer_set_format (temp_buffer, NULL);
+ }
+ else
+ {
+ GeglColor *white = gegl_color_new ("white");
+
+ /* Otherwise, just fill the shapeburst to white */
+ gegl_buffer_set_color (temp_buffer, NULL, white);
+ g_object_unref (white);
+ }
+ }
+
+ shapeburst = gegl_node_new_child (NULL,
+ "operation", "gegl:distance-transform",
+ "normalize", TRUE,
+ "metric", metric,
+ NULL);
+
+ if (progress)
+ gimp_gegl_progress_connect (shapeburst, progress,
+ _("Calculating distance map"));
+
+ gimp_gegl_apply_operation (temp_buffer, NULL, NULL,
+ shapeburst,
+ dist_buffer, region, FALSE);
+
+ g_object_unref (shapeburst);
+
+ g_object_unref (temp_buffer);
+
+ return dist_buffer;
+}
+
+void
+gimp_drawable_gradient_adjust_coords (GimpDrawable *drawable,
+ GimpGradientType gradient_type,
+ const GeglRectangle *region,
+ gdouble *startx,
+ gdouble *starty,
+ gdouble *endx,
+ gdouble *endy)
+{
+ g_return_if_fail (GIMP_IS_DRAWABLE (drawable));
+ g_return_if_fail (region != NULL);
+ g_return_if_fail (startx != NULL);
+ g_return_if_fail (starty != NULL);
+ g_return_if_fail (endx != NULL);
+ g_return_if_fail (endy != NULL);
+
+ /* we potentially adjust the gradient coordinates according to the gradient
+ * type, so that in cases where the gradient span is not related to the
+ * segment length, the gradient cache (in GimpOperationGradient) is big
+ * enough not to produce banding.
+ */
+
+ switch (gradient_type)
+ {
+ /* for conical gradients, use a segment with the original origin and
+ * direction, whose length is the circumference of the largest circle
+ * centered at the origin, passing through one of the regions's vertices.
+ */
+ case GIMP_GRADIENT_CONICAL_SYMMETRIC:
+ case GIMP_GRADIENT_CONICAL_ASYMMETRIC:
+ {
+ gdouble r = 0.0;
+ GimpVector2 v;
+
+ r = MAX (r, hypot (region->x - *startx,
+ region->y - *starty));
+ r = MAX (r, hypot (region->x + region->width - *startx,
+ region->y - *starty));
+ r = MAX (r, hypot (region->x - *startx,
+ region->y + region->height - *starty));
+ r = MAX (r, hypot (region->x + region->width - *startx,
+ region->y + region->height - *starty));
+
+ /* symmetric conical gradients only span half a revolution, and
+ * therefore require only half the cache size.
+ */
+ if (gradient_type == GIMP_GRADIENT_CONICAL_SYMMETRIC)
+ r /= 2.0;
+
+ gimp_vector2_set (&v, *endx - *startx, *endy - *starty);
+ gimp_vector2_normalize (&v);
+ gimp_vector2_mul (&v, 2.0 * G_PI * r);
+
+ *endx = *startx + v.x;
+ *endy = *starty + v.y;
+ }
+ break;
+
+ /* for shaped gradients, only the segment's length matters; use the
+ * regions's diagonal, which is the largest possible distance between two
+ * points in the region.
+ */
+ case GIMP_GRADIENT_SHAPEBURST_ANGULAR:
+ case GIMP_GRADIENT_SHAPEBURST_SPHERICAL:
+ case GIMP_GRADIENT_SHAPEBURST_DIMPLED:
+ *startx = region->x;
+ *starty = region->y;
+ *endx = region->x + region->width;
+ *endy = region->y + region->height;
+ break;
+
+ default:
+ break;
+ }
+}
diff --git a/app/core/gimpdrawable-gradient.h b/app/core/gimpdrawable-gradient.h
new file mode 100644
index 0000000..4f38c8a
--- /dev/null
+++ b/app/core/gimpdrawable-gradient.h
@@ -0,0 +1,57 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_DRAWABLE_GRADIENT_H__
+#define __GIMP_DRAWABLE_GRADIENT_H__
+
+
+void gimp_drawable_gradient (GimpDrawable *drawable,
+ GimpContext *context,
+ GimpGradient *gradient,
+ GeglDistanceMetric metric,
+ GimpLayerMode paint_mode,
+ GimpGradientType gradient_type,
+ gdouble opacity,
+ gdouble offset,
+ GimpRepeatMode repeat,
+ gboolean reverse,
+ GimpGradientBlendColorSpace blend_color_space,
+ gboolean supersample,
+ gint max_depth,
+ gdouble threshold,
+ gboolean dither,
+ gdouble startx,
+ gdouble starty,
+ gdouble endx,
+ gdouble endy,
+ GimpProgress *progress);
+
+GeglBuffer * gimp_drawable_gradient_shapeburst_distmap (GimpDrawable *drawable,
+ GeglDistanceMetric metric,
+ const GeglRectangle *region,
+ GimpProgress *progress);
+
+void gimp_drawable_gradient_adjust_coords (GimpDrawable *drawable,
+ GimpGradientType gradient_type,
+ const GeglRectangle *region,
+ gdouble *startx,
+ gdouble *starty,
+ gdouble *endx,
+ gdouble *endy);
+
+
+#endif /* __GIMP_DRAWABLE_GRADIENT_H__ */
diff --git a/app/core/gimpdrawable-histogram.c b/app/core/gimpdrawable-histogram.c
new file mode 100644
index 0000000..b0be3f5
--- /dev/null
+++ b/app/core/gimpdrawable-histogram.c
@@ -0,0 +1,258 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimphistogram module Copyright (C) 1999 Jay Cox <jaycox@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <cairo.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "core-types.h"
+
+#include "gegl/gimp-gegl-nodes.h"
+#include "gegl/gimptilehandlervalidate.h"
+
+#include "gimpasync.h"
+#include "gimpchannel.h"
+#include "gimpdrawable-filters.h"
+#include "gimpdrawable-histogram.h"
+#include "gimphistogram.h"
+#include "gimpimage.h"
+#include "gimpprojectable.h"
+
+
+/* local function prototypes */
+
+static GimpAsync * gimp_drawable_calculate_histogram_internal (GimpDrawable *drawable,
+ GimpHistogram *histogram,
+ gboolean with_filters,
+ gboolean run_async);
+
+
+/* private functions */
+
+
+static GimpAsync *
+gimp_drawable_calculate_histogram_internal (GimpDrawable *drawable,
+ GimpHistogram *histogram,
+ gboolean with_filters,
+ gboolean run_async)
+{
+ GimpAsync *async = NULL;
+ GimpImage *image;
+ GimpChannel *mask;
+ gint x, y, width, height;
+
+ if (! gimp_item_mask_intersect (GIMP_ITEM (drawable), &x, &y, &width, &height))
+ goto end;
+
+ image = gimp_item_get_image (GIMP_ITEM (drawable));
+ mask = gimp_image_get_mask (image);
+
+ if (FALSE)
+ {
+ GeglNode *node = gegl_node_new ();
+ GeglNode *source;
+ GeglNode *histogram_sink;
+ GeglProcessor *processor;
+
+ if (with_filters)
+ {
+ source = gimp_drawable_get_source_node (drawable);
+ }
+ else
+ {
+ source =
+ gimp_gegl_add_buffer_source (node,
+ gimp_drawable_get_buffer (drawable),
+ 0, 0);
+ }
+
+ histogram_sink =
+ gegl_node_new_child (node,
+ "operation", "gimp:histogram-sink",
+ "histogram", histogram,
+ NULL);
+
+ gegl_node_connect_to (source, "output",
+ histogram_sink, "input");
+
+ if (! gimp_channel_is_empty (mask))
+ {
+ GeglNode *mask_source;
+ gint off_x, off_y;
+
+ g_printerr ("adding mask aux\n");
+
+ gimp_item_get_offset (GIMP_ITEM (drawable), &off_x, &off_y);
+
+ mask_source =
+ gimp_gegl_add_buffer_source (node,
+ gimp_drawable_get_buffer (GIMP_DRAWABLE (mask)),
+ -off_x, -off_y);
+
+ gegl_node_connect_to (mask_source, "output",
+ histogram_sink, "aux");
+ }
+
+ processor = gegl_node_new_processor (histogram_sink,
+ GEGL_RECTANGLE (x, y, width, height));
+
+ while (gegl_processor_work (processor, NULL));
+
+ g_object_unref (processor);
+ g_object_unref (node);
+ }
+ else
+ {
+ GeglBuffer *buffer = gimp_drawable_get_buffer (drawable);
+ GimpProjectable *projectable = NULL;
+
+ if (with_filters && gimp_drawable_has_filters (drawable))
+ {
+ GimpTileHandlerValidate *validate;
+ GeglNode *node;
+
+ node = gimp_drawable_get_source_node (drawable);
+
+ buffer = gegl_buffer_new (gegl_buffer_get_extent (buffer),
+ gegl_buffer_get_format (buffer));
+
+ validate =
+ GIMP_TILE_HANDLER_VALIDATE (gimp_tile_handler_validate_new (node));
+
+ gimp_tile_handler_validate_assign (validate, buffer);
+
+ g_object_unref (validate);
+
+ gimp_tile_handler_validate_invalidate (validate,
+ gegl_buffer_get_extent (buffer));
+
+#if 0
+ /* this would keep the buffer updated across drawable or
+ * filter changes, but the histogram is created in one go
+ * and doesn't need the signal connection
+ */
+ g_signal_connect_object (node, "invalidated",
+ G_CALLBACK (gimp_tile_handler_validate_invalidate),
+ validate, G_CONNECT_SWAPPED);
+#endif
+
+ if (GIMP_IS_PROJECTABLE (drawable))
+ projectable = GIMP_PROJECTABLE (drawable);
+ }
+ else
+ {
+ g_object_ref (buffer);
+ }
+
+ if (projectable)
+ gimp_projectable_begin_render (projectable);
+
+ if (! gimp_channel_is_empty (mask))
+ {
+ gint off_x, off_y;
+
+ gimp_item_get_offset (GIMP_ITEM (drawable), &off_x, &off_y);
+
+ if (run_async)
+ {
+ async = gimp_histogram_calculate_async (
+ histogram, buffer,
+ GEGL_RECTANGLE (x, y, width, height),
+ gimp_drawable_get_buffer (GIMP_DRAWABLE (mask)),
+ GEGL_RECTANGLE (x + off_x, y + off_y,
+ width, height));
+ }
+ else
+ {
+ gimp_histogram_calculate (
+ histogram, buffer,
+ GEGL_RECTANGLE (x, y, width, height),
+ gimp_drawable_get_buffer (GIMP_DRAWABLE (mask)),
+ GEGL_RECTANGLE (x + off_x, y + off_y,
+ width, height));
+ }
+ }
+ else
+ {
+ if (run_async)
+ {
+ async = gimp_histogram_calculate_async (
+ histogram, buffer,
+ GEGL_RECTANGLE (x, y, width, height),
+ NULL, NULL);
+ }
+ else
+ {
+ gimp_histogram_calculate (
+ histogram, buffer,
+ GEGL_RECTANGLE (x, y, width, height),
+ NULL, NULL);
+ }
+ }
+
+ if (projectable)
+ gimp_projectable_end_render (projectable);
+
+ g_object_unref (buffer);
+ }
+
+end:
+ if (run_async && ! async)
+ {
+ async = gimp_async_new ();
+
+ gimp_async_finish (async, NULL);
+ }
+
+ return async;
+}
+
+
+/* public functions */
+
+
+void
+gimp_drawable_calculate_histogram (GimpDrawable *drawable,
+ GimpHistogram *histogram,
+ gboolean with_filters)
+{
+ g_return_if_fail (GIMP_IS_DRAWABLE (drawable));
+ g_return_if_fail (gimp_item_is_attached (GIMP_ITEM (drawable)));
+ g_return_if_fail (histogram != NULL);
+
+ gimp_drawable_calculate_histogram_internal (drawable,
+ histogram, with_filters,
+ FALSE);
+}
+
+GimpAsync *
+gimp_drawable_calculate_histogram_async (GimpDrawable *drawable,
+ GimpHistogram *histogram,
+ gboolean with_filters)
+{
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), NULL);
+ g_return_val_if_fail (gimp_item_is_attached (GIMP_ITEM (drawable)), NULL);
+ g_return_val_if_fail (histogram != NULL, NULL);
+
+ return gimp_drawable_calculate_histogram_internal (drawable,
+ histogram, with_filters,
+ TRUE);
+}
diff --git a/app/core/gimpdrawable-histogram.h b/app/core/gimpdrawable-histogram.h
new file mode 100644
index 0000000..947393a
--- /dev/null
+++ b/app/core/gimpdrawable-histogram.h
@@ -0,0 +1,32 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimphistogram module Copyright (C) 1999 Jay Cox <jaycox@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_DRAWABLE_HISTOGRAM_H__
+#define __GIMP_DRAWABLE_HISTOGRAM_H__
+
+
+void gimp_drawable_calculate_histogram (GimpDrawable *drawable,
+ GimpHistogram *histogram,
+ gboolean with_filters);
+GimpAsync * gimp_drawable_calculate_histogram_async (GimpDrawable *drawable,
+ GimpHistogram *histogram,
+ gboolean with_filters);
+
+
+#endif /* __GIMP_HISTOGRAM_H__ */
diff --git a/app/core/gimpdrawable-levels.c b/app/core/gimpdrawable-levels.c
new file mode 100644
index 0000000..301326b
--- /dev/null
+++ b/app/core/gimpdrawable-levels.c
@@ -0,0 +1,77 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "core-types.h"
+
+#include "operations/gimplevelsconfig.h"
+
+#include "gimpdrawable.h"
+#include "gimpdrawable-histogram.h"
+#include "gimpdrawable-levels.h"
+#include "gimpdrawable-operation.h"
+#include "gimphistogram.h"
+#include "gimpprogress.h"
+
+#include "gimp-intl.h"
+
+
+/* public functions */
+
+void
+gimp_drawable_levels_stretch (GimpDrawable *drawable,
+ GimpProgress *progress)
+{
+ GimpLevelsConfig *config;
+ GimpHistogram *histogram;
+ GeglNode *levels;
+
+ g_return_if_fail (GIMP_IS_DRAWABLE (drawable));
+ g_return_if_fail (gimp_item_is_attached (GIMP_ITEM (drawable)));
+ g_return_if_fail (progress == NULL || GIMP_IS_PROGRESS (progress));
+
+ if (! gimp_item_mask_intersect (GIMP_ITEM (drawable), NULL, NULL, NULL, NULL))
+ return;
+
+ config = g_object_new (GIMP_TYPE_LEVELS_CONFIG, NULL);
+
+ histogram = gimp_histogram_new (FALSE);
+ gimp_drawable_calculate_histogram (drawable, histogram, FALSE);
+
+ gimp_levels_config_stretch (config, histogram,
+ gimp_drawable_is_rgb (drawable));
+
+ g_object_unref (histogram);
+
+ levels = g_object_new (GEGL_TYPE_NODE,
+ "operation", "gimp:levels",
+ NULL);
+
+ gegl_node_set (levels,
+ "config", config,
+ NULL);
+
+ gimp_drawable_apply_operation (drawable, progress, _("Levels"),
+ levels);
+
+ g_object_unref (levels);
+ g_object_unref (config);
+}
diff --git a/app/core/gimpdrawable-levels.h b/app/core/gimpdrawable-levels.h
new file mode 100644
index 0000000..22011c1
--- /dev/null
+++ b/app/core/gimpdrawable-levels.h
@@ -0,0 +1,26 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_DRAWABLE_LEVELS_H__
+#define __GIMP_DRAWABLE_LEVELS_H__
+
+
+void gimp_drawable_levels_stretch (GimpDrawable *drawable,
+ GimpProgress *progress);
+
+
+#endif /* __GIMP_DRAWABLE_LEVELS_H__ */
diff --git a/app/core/gimpdrawable-offset.c b/app/core/gimpdrawable-offset.c
new file mode 100644
index 0000000..b7f5eb2
--- /dev/null
+++ b/app/core/gimpdrawable-offset.c
@@ -0,0 +1,83 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <cairo.h>
+#include <gegl.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+#include "core-types.h"
+
+#include "gimp.h"
+#include "gimpcontext.h"
+#include "gimpdrawable.h"
+#include "gimpdrawable-offset.h"
+#include "gimpdrawable-operation.h"
+
+#include "gimp-intl.h"
+
+
+void
+gimp_drawable_offset (GimpDrawable *drawable,
+ GimpContext *context,
+ gboolean wrap_around,
+ GimpOffsetType fill_type,
+ gint offset_x,
+ gint offset_y)
+{
+ GeglNode *node;
+ gint width;
+ gint height;
+
+ g_return_if_fail (GIMP_IS_DRAWABLE (drawable));
+ g_return_if_fail (GIMP_IS_CONTEXT (context));
+
+ if (! gimp_item_mask_intersect (GIMP_ITEM (drawable),
+ NULL, NULL, &width, &height))
+ {
+ return;
+ }
+
+ if (wrap_around)
+ fill_type = GIMP_OFFSET_WRAP_AROUND;
+
+ if (fill_type == GIMP_OFFSET_WRAP_AROUND)
+ {
+ offset_x %= width;
+ offset_y %= height;
+ }
+
+ if (offset_x == 0 && offset_y == 0)
+ return;
+
+ node = gegl_node_new_child (NULL,
+ "operation", "gimp:offset",
+ "context", context,
+ "type", fill_type,
+ "x", offset_x,
+ "y", offset_y,
+ NULL);
+
+ gimp_drawable_apply_operation (drawable, NULL,
+ C_("undo-type", "Offset Drawable"),
+ node);
+
+ g_object_unref (node);
+}
diff --git a/app/core/gimpdrawable-offset.h b/app/core/gimpdrawable-offset.h
new file mode 100644
index 0000000..6629150
--- /dev/null
+++ b/app/core/gimpdrawable-offset.h
@@ -0,0 +1,30 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_DRAWABLE_OFFSET_H__
+#define __GIMP_DRAWABLE_OFFSET_H__
+
+
+void gimp_drawable_offset (GimpDrawable *drawable,
+ GimpContext *context,
+ gboolean wrap_around,
+ GimpOffsetType fill_type,
+ gint offset_x,
+ gint offset_y);
+
+
+#endif /* __GIMP_DRAWABLE_OFFSET_H__ */
diff --git a/app/core/gimpdrawable-operation.c b/app/core/gimpdrawable-operation.c
new file mode 100644
index 0000000..fdda286
--- /dev/null
+++ b/app/core/gimpdrawable-operation.c
@@ -0,0 +1,128 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpdrawable-operation.c
+ * Copyright (C) 2007 Øyvind Kolås <pippin@gimp.org>
+ * Sven Neumann <sven@gimp.org>
+ * Michael Natterer <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "core-types.h"
+
+#include "gegl/gimp-gegl-utils.h"
+
+#include "operations/gimp-operation-config.h"
+#include "operations/gimpoperationsettings.h"
+
+#include "gimpdrawable.h"
+#include "gimpdrawable-operation.h"
+#include "gimpdrawablefilter.h"
+#include "gimpprogress.h"
+#include "gimpsettings.h"
+
+
+/* public functions */
+
+void
+gimp_drawable_apply_operation (GimpDrawable *drawable,
+ GimpProgress *progress,
+ const gchar *undo_desc,
+ GeglNode *operation)
+{
+ gimp_drawable_apply_operation_with_config (drawable,
+ progress, undo_desc,
+ operation, NULL);
+}
+
+void
+gimp_drawable_apply_operation_with_config (GimpDrawable *drawable,
+ GimpProgress *progress,
+ const gchar *undo_desc,
+ GeglNode *operation,
+ GObject *config)
+{
+ GimpDrawableFilter *filter;
+
+ g_return_if_fail (GIMP_IS_DRAWABLE (drawable));
+ g_return_if_fail (gimp_item_is_attached (GIMP_ITEM (drawable)));
+ g_return_if_fail (progress == NULL || GIMP_IS_PROGRESS (progress));
+ g_return_if_fail (undo_desc != NULL);
+ g_return_if_fail (GEGL_IS_NODE (operation));
+ g_return_if_fail (config == NULL || GIMP_IS_OPERATION_SETTINGS (config));
+
+ if (! gimp_item_mask_intersect (GIMP_ITEM (drawable),
+ NULL, NULL, NULL, NULL))
+ {
+ return;
+ }
+
+ filter = gimp_drawable_filter_new (drawable, undo_desc, operation, NULL);
+
+ gimp_drawable_filter_set_add_alpha (filter,
+ gimp_gegl_node_has_key (operation,
+ "needs-alpha"));
+
+ if (config)
+ {
+ gimp_operation_config_sync_node (config, operation);
+
+ gimp_operation_settings_sync_drawable_filter (
+ GIMP_OPERATION_SETTINGS (config), filter);
+ }
+
+ gimp_drawable_filter_apply (filter, NULL);
+ gimp_drawable_filter_commit (filter, progress, TRUE);
+
+ g_object_unref (filter);
+
+ if (progress)
+ gimp_progress_end (progress);
+}
+
+void
+gimp_drawable_apply_operation_by_name (GimpDrawable *drawable,
+ GimpProgress *progress,
+ const gchar *undo_desc,
+ const gchar *operation_type,
+ GObject *config)
+{
+ GeglNode *node;
+
+ g_return_if_fail (GIMP_IS_DRAWABLE (drawable));
+ g_return_if_fail (gimp_item_is_attached (GIMP_ITEM (drawable)));
+ g_return_if_fail (progress == NULL || GIMP_IS_PROGRESS (progress));
+ g_return_if_fail (undo_desc != NULL);
+ g_return_if_fail (operation_type != NULL);
+ g_return_if_fail (config == NULL || GIMP_IS_SETTINGS (config));
+
+ node = g_object_new (GEGL_TYPE_NODE,
+ "operation", operation_type,
+ NULL);
+
+ if (config)
+ gegl_node_set (node,
+ "config", config,
+ NULL);
+
+ gimp_drawable_apply_operation (drawable, progress, undo_desc, node);
+
+ g_object_unref (node);
+}
diff --git a/app/core/gimpdrawable-operation.h b/app/core/gimpdrawable-operation.h
new file mode 100644
index 0000000..6ca381f
--- /dev/null
+++ b/app/core/gimpdrawable-operation.h
@@ -0,0 +1,43 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpdrawable-operation.h
+ * Copyright (C) 2007 Øyvind Kolås <pippin@gimp.org>
+ * Sven Neumann <sven@gimp.org>
+ * Michael Natterer <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_DRAWABLE_OPERATION_H__
+#define __GIMP_DRAWABLE_OPERATION_H__
+
+
+void gimp_drawable_apply_operation (GimpDrawable *drawable,
+ GimpProgress *progress,
+ const gchar *undo_desc,
+ GeglNode *operation);
+void gimp_drawable_apply_operation_with_config (GimpDrawable *drawable,
+ GimpProgress *progress,
+ const gchar *undo_desc,
+ GeglNode *operation,
+ GObject *config);
+void gimp_drawable_apply_operation_by_name (GimpDrawable *drawable,
+ GimpProgress *progress,
+ const gchar *undo_desc,
+ const gchar *operation_type,
+ GObject *config);
+
+
+#endif /* __GIMP_DRAWABLE_OPERATION_H__ */
diff --git a/app/core/gimpdrawable-preview.c b/app/core/gimpdrawable-preview.c
new file mode 100644
index 0000000..28ae1e2
--- /dev/null
+++ b/app/core/gimpdrawable-preview.c
@@ -0,0 +1,492 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <cairo.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpcolor/gimpcolor.h"
+#include "libgimpmath/gimpmath.h"
+
+#include "core-types.h"
+
+#include "config/gimpcoreconfig.h"
+
+#include "gegl/gimp-babl.h"
+#include "gegl/gimp-gegl-loops.h"
+#include "gegl/gimptilehandlervalidate.h"
+
+#include "gimp.h"
+#include "gimp-parallel.h"
+#include "gimp-utils.h"
+#include "gimpasync.h"
+#include "gimpchannel.h"
+#include "gimpchunkiterator.h"
+#include "gimpimage.h"
+#include "gimpimage-color-profile.h"
+#include "gimpdrawable-preview.h"
+#include "gimpdrawable-private.h"
+#include "gimplayer.h"
+#include "gimptempbuf.h"
+
+#include "gimp-priorities.h"
+
+
+typedef struct
+{
+ const Babl *format;
+ GeglBuffer *buffer;
+ GeglRectangle rect;
+ gdouble scale;
+
+ GimpChunkIterator *iter;
+} SubPreviewData;
+
+
+/* local function prototypes */
+
+static SubPreviewData * sub_preview_data_new (const Babl *format,
+ GeglBuffer *buffer,
+ const GeglRectangle *rect,
+ gdouble scale);
+static void sub_preview_data_free (SubPreviewData *data);
+
+
+
+/* private functions */
+
+
+static SubPreviewData *
+sub_preview_data_new (const Babl *format,
+ GeglBuffer *buffer,
+ const GeglRectangle *rect,
+ gdouble scale)
+{
+ SubPreviewData *data = g_slice_new (SubPreviewData);
+
+ data->format = format;
+ data->buffer = g_object_ref (buffer);
+ data->rect = *rect;
+ data->scale = scale;
+
+ data->iter = NULL;
+
+ return data;
+}
+
+static void
+sub_preview_data_free (SubPreviewData *data)
+{
+ g_object_unref (data->buffer);
+
+ if (data->iter)
+ gimp_chunk_iterator_stop (data->iter, TRUE);
+
+ g_slice_free (SubPreviewData, data);
+}
+
+
+/* public functions */
+
+
+GimpTempBuf *
+gimp_drawable_get_new_preview (GimpViewable *viewable,
+ GimpContext *context,
+ gint width,
+ gint height)
+{
+ GimpItem *item = GIMP_ITEM (viewable);
+ GimpImage *image = gimp_item_get_image (item);
+
+ if (! image->gimp->config->layer_previews)
+ return NULL;
+
+ return gimp_drawable_get_sub_preview (GIMP_DRAWABLE (viewable),
+ 0, 0,
+ gimp_item_get_width (item),
+ gimp_item_get_height (item),
+ width,
+ height);
+}
+
+GdkPixbuf *
+gimp_drawable_get_new_pixbuf (GimpViewable *viewable,
+ GimpContext *context,
+ gint width,
+ gint height)
+{
+ GimpItem *item = GIMP_ITEM (viewable);
+ GimpImage *image = gimp_item_get_image (item);
+
+ if (! image->gimp->config->layer_previews)
+ return NULL;
+
+ return gimp_drawable_get_sub_pixbuf (GIMP_DRAWABLE (viewable),
+ 0, 0,
+ gimp_item_get_width (item),
+ gimp_item_get_height (item),
+ width,
+ height);
+}
+
+const Babl *
+gimp_drawable_get_preview_format (GimpDrawable *drawable)
+{
+ gboolean alpha;
+ gboolean linear;
+
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), NULL);
+
+ alpha = gimp_drawable_has_alpha (drawable);
+ linear = gimp_drawable_get_linear (drawable);
+
+ switch (gimp_drawable_get_base_type (drawable))
+ {
+ case GIMP_GRAY:
+ return gimp_babl_format (GIMP_GRAY,
+ gimp_babl_precision (GIMP_COMPONENT_TYPE_U8,
+ linear),
+ alpha);
+
+ case GIMP_RGB:
+ return gimp_babl_format (GIMP_RGB,
+ gimp_babl_precision (GIMP_COMPONENT_TYPE_U8,
+ linear),
+ alpha);
+
+ case GIMP_INDEXED:
+ if (alpha)
+ return babl_format ("R'G'B'A u8");
+ else
+ return babl_format ("R'G'B' u8");
+ }
+
+ g_return_val_if_reached (NULL);
+}
+
+GimpTempBuf *
+gimp_drawable_get_sub_preview (GimpDrawable *drawable,
+ gint src_x,
+ gint src_y,
+ gint src_width,
+ gint src_height,
+ gint dest_width,
+ gint dest_height)
+{
+ GimpItem *item;
+ GimpImage *image;
+ GeglBuffer *buffer;
+ GimpTempBuf *preview;
+ gdouble scale;
+ gint scaled_x;
+ gint scaled_y;
+
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), NULL);
+ g_return_val_if_fail (src_x >= 0, NULL);
+ g_return_val_if_fail (src_y >= 0, NULL);
+ g_return_val_if_fail (src_width > 0, NULL);
+ g_return_val_if_fail (src_height > 0, NULL);
+ g_return_val_if_fail (dest_width > 0, NULL);
+ g_return_val_if_fail (dest_height > 0, NULL);
+
+ item = GIMP_ITEM (drawable);
+
+ g_return_val_if_fail ((src_x + src_width) <= gimp_item_get_width (item), NULL);
+ g_return_val_if_fail ((src_y + src_height) <= gimp_item_get_height (item), NULL);
+
+ image = gimp_item_get_image (item);
+
+ if (! image->gimp->config->layer_previews)
+ return NULL;
+
+ buffer = gimp_drawable_get_buffer (drawable);
+
+ preview = gimp_temp_buf_new (dest_width, dest_height,
+ gimp_drawable_get_preview_format (drawable));
+
+ scale = MIN ((gdouble) dest_width / (gdouble) src_width,
+ (gdouble) dest_height / (gdouble) src_height);
+
+ scaled_x = RINT ((gdouble) src_x * scale);
+ scaled_y = RINT ((gdouble) src_y * scale);
+
+ gegl_buffer_get (buffer,
+ GEGL_RECTANGLE (scaled_x, scaled_y, dest_width, dest_height),
+ scale,
+ gimp_temp_buf_get_format (preview),
+ gimp_temp_buf_get_data (preview),
+ GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_CLAMP);
+
+ return preview;
+}
+
+GdkPixbuf *
+gimp_drawable_get_sub_pixbuf (GimpDrawable *drawable,
+ gint src_x,
+ gint src_y,
+ gint src_width,
+ gint src_height,
+ gint dest_width,
+ gint dest_height)
+{
+ GimpItem *item;
+ GimpImage *image;
+ GeglBuffer *buffer;
+ GdkPixbuf *pixbuf;
+ gdouble scale;
+ gint scaled_x;
+ gint scaled_y;
+ GimpColorTransform *transform;
+
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), NULL);
+ g_return_val_if_fail (src_x >= 0, NULL);
+ g_return_val_if_fail (src_y >= 0, NULL);
+ g_return_val_if_fail (src_width > 0, NULL);
+ g_return_val_if_fail (src_height > 0, NULL);
+ g_return_val_if_fail (dest_width > 0, NULL);
+ g_return_val_if_fail (dest_height > 0, NULL);
+
+ item = GIMP_ITEM (drawable);
+
+ g_return_val_if_fail ((src_x + src_width) <= gimp_item_get_width (item), NULL);
+ g_return_val_if_fail ((src_y + src_height) <= gimp_item_get_height (item), NULL);
+
+ image = gimp_item_get_image (item);
+
+ if (! image->gimp->config->layer_previews)
+ return NULL;
+
+ buffer = gimp_drawable_get_buffer (drawable);
+
+ pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8,
+ dest_width, dest_height);
+
+ scale = MIN ((gdouble) dest_width / (gdouble) src_width,
+ (gdouble) dest_height / (gdouble) src_height);
+
+ scaled_x = RINT ((gdouble) src_x * scale);
+ scaled_y = RINT ((gdouble) src_y * scale);
+
+ transform = gimp_image_get_color_transform_to_srgb_u8 (image);
+
+ if (transform)
+ {
+ GimpTempBuf *temp_buf;
+ GeglBuffer *src_buf;
+ GeglBuffer *dest_buf;
+
+ temp_buf = gimp_temp_buf_new (dest_width, dest_height,
+ gimp_drawable_get_format (drawable));
+
+ gegl_buffer_get (buffer,
+ GEGL_RECTANGLE (scaled_x, scaled_y,
+ dest_width, dest_height),
+ scale,
+ gimp_temp_buf_get_format (temp_buf),
+ gimp_temp_buf_get_data (temp_buf),
+ GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_CLAMP);
+
+ src_buf = gimp_temp_buf_create_buffer (temp_buf);
+ dest_buf = gimp_pixbuf_create_buffer (pixbuf);
+
+ gimp_temp_buf_unref (temp_buf);
+
+ gimp_color_transform_process_buffer (transform,
+ src_buf,
+ GEGL_RECTANGLE (0, 0,
+ dest_width, dest_height),
+ dest_buf,
+ GEGL_RECTANGLE (0, 0, 0, 0));
+
+ g_object_unref (src_buf);
+ g_object_unref (dest_buf);
+ }
+ else
+ {
+ gegl_buffer_get (buffer,
+ GEGL_RECTANGLE (scaled_x, scaled_y,
+ dest_width, dest_height),
+ scale,
+ gimp_pixbuf_get_format (pixbuf),
+ gdk_pixbuf_get_pixels (pixbuf),
+ gdk_pixbuf_get_rowstride (pixbuf),
+ GEGL_ABYSS_CLAMP);
+ }
+
+ return pixbuf;
+}
+
+static void
+gimp_drawable_get_sub_preview_async_func (GimpAsync *async,
+ SubPreviewData *data)
+{
+ GimpTempBuf *preview;
+ GimpTileHandlerValidate *validate;
+
+ preview = gimp_temp_buf_new (data->rect.width, data->rect.height,
+ data->format);
+
+ validate = gimp_tile_handler_validate_get_assigned (data->buffer);
+
+ if (validate)
+ {
+ if (! data->iter)
+ {
+ cairo_region_t *region;
+ cairo_rectangle_int_t rect;
+
+ rect.x = floor (data->rect.x / data->scale);
+ rect.y = floor (data->rect.y / data->scale);
+ rect.width = ceil ((data->rect.x + data->rect.width) /
+ data->scale) - rect.x;
+ rect.height = ceil ((data->rect.x + data->rect.height) /
+ data->scale) - rect.y;
+
+ region = cairo_region_copy (validate->dirty_region);
+
+ cairo_region_intersect_rectangle (region, &rect);
+
+ data->iter = gimp_chunk_iterator_new (region);
+ }
+
+ if (gimp_chunk_iterator_next (data->iter))
+ {
+ GeglRectangle rect;
+
+ gimp_tile_handler_validate_begin_validate (validate);
+
+ while (gimp_chunk_iterator_get_rect (data->iter, &rect))
+ {
+ gimp_tile_handler_validate_validate (validate,
+ data->buffer, &rect,
+ FALSE, FALSE);
+ }
+
+ gimp_tile_handler_validate_end_validate (validate);
+
+ return;
+ }
+
+ data->iter = NULL;
+ }
+
+ gegl_buffer_get (data->buffer, &data->rect, data->scale,
+ gimp_temp_buf_get_format (preview),
+ gimp_temp_buf_get_data (preview),
+ GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_CLAMP);
+
+ sub_preview_data_free (data);
+
+ gimp_async_finish_full (async,
+ preview,
+ (GDestroyNotify) gimp_temp_buf_unref);
+}
+
+GimpAsync *
+gimp_drawable_get_sub_preview_async (GimpDrawable *drawable,
+ gint src_x,
+ gint src_y,
+ gint src_width,
+ gint src_height,
+ gint dest_width,
+ gint dest_height)
+{
+ GimpItem *item;
+ GimpImage *image;
+ GeglBuffer *buffer;
+ SubPreviewData *data;
+ gdouble scale;
+ gint scaled_x;
+ gint scaled_y;
+ static gint no_async_drawable_previews = -1;
+
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), NULL);
+ g_return_val_if_fail (src_x >= 0, NULL);
+ g_return_val_if_fail (src_y >= 0, NULL);
+ g_return_val_if_fail (src_width > 0, NULL);
+ g_return_val_if_fail (src_height > 0, NULL);
+ g_return_val_if_fail (dest_width > 0, NULL);
+ g_return_val_if_fail (dest_height > 0, NULL);
+
+ item = GIMP_ITEM (drawable);
+
+ g_return_val_if_fail ((src_x + src_width) <= gimp_item_get_width (item), NULL);
+ g_return_val_if_fail ((src_y + src_height) <= gimp_item_get_height (item), NULL);
+
+ image = gimp_item_get_image (item);
+
+ if (! image->gimp->config->layer_previews)
+ return NULL;
+
+ buffer = gimp_drawable_get_buffer (drawable);
+
+ if (no_async_drawable_previews < 0)
+ {
+ no_async_drawable_previews =
+ (g_getenv ("GIMP_NO_ASYNC_DRAWABLE_PREVIEWS") != NULL);
+ }
+
+ if (no_async_drawable_previews)
+ {
+ GimpAsync *async = gimp_async_new ();
+
+ gimp_async_finish_full (async,
+ gimp_drawable_get_sub_preview (drawable,
+ src_x,
+ src_y,
+ src_width,
+ src_height,
+ dest_width,
+ dest_height),
+ (GDestroyNotify) gimp_temp_buf_unref);
+
+ return async;
+ }
+
+ scale = MIN ((gdouble) dest_width / (gdouble) src_width,
+ (gdouble) dest_height / (gdouble) src_height);
+
+ scaled_x = RINT ((gdouble) src_x * scale);
+ scaled_y = RINT ((gdouble) src_y * scale);
+
+ data = sub_preview_data_new (
+ gimp_drawable_get_preview_format (drawable),
+ buffer,
+ GEGL_RECTANGLE (scaled_x, scaled_y, dest_width, dest_height),
+ scale);
+
+ if (gimp_tile_handler_validate_get_assigned (buffer))
+ {
+ return gimp_idle_run_async_full (
+ GIMP_PRIORITY_VIEWABLE_IDLE,
+ (GimpRunAsyncFunc) gimp_drawable_get_sub_preview_async_func,
+ data,
+ (GDestroyNotify) sub_preview_data_free);
+ }
+ else
+ {
+ return gimp_parallel_run_async_full (
+ +1,
+ (GimpRunAsyncFunc) gimp_drawable_get_sub_preview_async_func,
+ data,
+ (GDestroyNotify) sub_preview_data_free);
+ }
+}
diff --git a/app/core/gimpdrawable-preview.h b/app/core/gimpdrawable-preview.h
new file mode 100644
index 0000000..31f8ade
--- /dev/null
+++ b/app/core/gimpdrawable-preview.h
@@ -0,0 +1,63 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_DRAWABLE__PREVIEW_H__
+#define __GIMP_DRAWABLE__PREVIEW_H__
+
+
+/*
+ * virtual functions of GimpDrawable -- don't call directly
+ */
+GimpTempBuf * gimp_drawable_get_new_preview (GimpViewable *viewable,
+ GimpContext *context,
+ gint width,
+ gint height);
+GdkPixbuf * gimp_drawable_get_new_pixbuf (GimpViewable *viewable,
+ GimpContext *context,
+ gint width,
+ gint height);
+
+/*
+ * normal functions (no virtuals)
+ */
+const Babl * gimp_drawable_get_preview_format (GimpDrawable *drawable);
+
+GimpTempBuf * gimp_drawable_get_sub_preview (GimpDrawable *drawable,
+ gint src_x,
+ gint src_y,
+ gint src_width,
+ gint src_height,
+ gint dest_width,
+ gint dest_height);
+GdkPixbuf * gimp_drawable_get_sub_pixbuf (GimpDrawable *drawable,
+ gint src_x,
+ gint src_y,
+ gint src_width,
+ gint src_height,
+ gint dest_width,
+ gint dest_height);
+
+GimpAsync * gimp_drawable_get_sub_preview_async (GimpDrawable *drawable,
+ gint src_x,
+ gint src_y,
+ gint src_width,
+ gint src_height,
+ gint dest_width,
+ gint dest_height);
+
+
+#endif /* __GIMP_DRAWABLE__PREVIEW_H__ */
diff --git a/app/core/gimpdrawable-private.h b/app/core/gimpdrawable-private.h
new file mode 100644
index 0000000..780d5bf
--- /dev/null
+++ b/app/core/gimpdrawable-private.h
@@ -0,0 +1,44 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_DRAWABLE_PRIVATE_H__
+#define __GIMP_DRAWABLE_PRIVATE_H__
+
+struct _GimpDrawablePrivate
+{
+ GeglBuffer *buffer; /* buffer for drawable data */
+ GeglBuffer *shadow; /* shadow buffer */
+
+ GeglNode *source_node;
+ GeglNode *buffer_source_node;
+ GimpContainer *filter_stack;
+ GeglRectangle bounding_box;
+
+ GimpLayer *floating_selection;
+ GimpFilter *fs_filter;
+ GeglNode *fs_crop_node;
+ GimpApplicator *fs_applicator;
+
+ GeglNode *mode_node;
+
+ gint paint_count;
+ GeglBuffer *paint_buffer;
+ cairo_region_t *paint_copy_region;
+ cairo_region_t *paint_update_region;
+};
+
+#endif /* __GIMP_DRAWABLE_PRIVATE_H__ */
diff --git a/app/core/gimpdrawable-shadow.c b/app/core/gimpdrawable-shadow.c
new file mode 100644
index 0000000..4c42655
--- /dev/null
+++ b/app/core/gimpdrawable-shadow.c
@@ -0,0 +1,110 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+#include <cairo.h>
+
+#include "core-types.h"
+
+#include "gegl/gimp-gegl-utils.h"
+
+#include "gimpdrawable.h"
+#include "gimpdrawable-private.h"
+#include "gimpdrawable-shadow.h"
+
+
+GeglBuffer *
+gimp_drawable_get_shadow_buffer (GimpDrawable *drawable)
+{
+ GimpItem *item;
+ gint width;
+ gint height;
+ const Babl *format;
+
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), NULL);
+
+ item = GIMP_ITEM (drawable);
+
+ width = gimp_item_get_width (item);
+ height = gimp_item_get_height (item);
+ format = gimp_drawable_get_format (drawable);
+
+ if (drawable->private->shadow)
+ {
+ if ((width != gegl_buffer_get_width (drawable->private->shadow)) ||
+ (height != gegl_buffer_get_height (drawable->private->shadow)) ||
+ (format != gegl_buffer_get_format (drawable->private->shadow)))
+ {
+ gimp_drawable_free_shadow_buffer (drawable);
+ }
+ else
+ {
+ return drawable->private->shadow;
+ }
+ }
+
+ drawable->private->shadow = gegl_buffer_new (GEGL_RECTANGLE (0, 0,
+ width, height),
+ format);
+
+ return drawable->private->shadow;
+}
+
+void
+gimp_drawable_free_shadow_buffer (GimpDrawable *drawable)
+{
+ g_return_if_fail (GIMP_IS_DRAWABLE (drawable));
+
+ g_clear_object (&drawable->private->shadow);
+}
+
+void
+gimp_drawable_merge_shadow_buffer (GimpDrawable *drawable,
+ gboolean push_undo,
+ const gchar *undo_desc)
+{
+ gint x, y;
+ gint width, height;
+
+ g_return_if_fail (GIMP_IS_DRAWABLE (drawable));
+ g_return_if_fail (gimp_item_is_attached (GIMP_ITEM (drawable)));
+ g_return_if_fail (GEGL_IS_BUFFER (drawable->private->shadow));
+
+ /* A useful optimization here is to limit the update to the
+ * extents of the selection mask, as it cannot extend beyond
+ * them.
+ */
+ if (gimp_item_mask_intersect (GIMP_ITEM (drawable), &x, &y, &width, &height))
+ {
+ GeglBuffer *buffer = g_object_ref (drawable->private->shadow);
+
+ gimp_drawable_apply_buffer (drawable, buffer,
+ GEGL_RECTANGLE (x, y, width, height),
+ push_undo, undo_desc,
+ GIMP_OPACITY_OPAQUE,
+ GIMP_LAYER_MODE_REPLACE,
+ GIMP_LAYER_COLOR_SPACE_AUTO,
+ GIMP_LAYER_COLOR_SPACE_AUTO,
+ GIMP_LAYER_COMPOSITE_AUTO,
+ NULL, x, y);
+
+ g_object_unref (buffer);
+ }
+}
diff --git a/app/core/gimpdrawable-shadow.h b/app/core/gimpdrawable-shadow.h
new file mode 100644
index 0000000..b3ab582
--- /dev/null
+++ b/app/core/gimpdrawable-shadow.h
@@ -0,0 +1,32 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpdrawable-shadow.h
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_DRAWABLE_SHADOW_H__
+#define __GIMP_DRAWABLE_SHADOW_H__
+
+
+GeglBuffer * gimp_drawable_get_shadow_buffer (GimpDrawable *drawable);
+void gimp_drawable_free_shadow_buffer (GimpDrawable *drawable);
+
+void gimp_drawable_merge_shadow_buffer (GimpDrawable *drawable,
+ gboolean push_undo,
+ const gchar *undo_desc);
+
+
+#endif /* __GIMP_DRAWABLE_SHADOW_H__ */
diff --git a/app/core/gimpdrawable-stroke.c b/app/core/gimpdrawable-stroke.c
new file mode 100644
index 0000000..5b35d51
--- /dev/null
+++ b/app/core/gimpdrawable-stroke.c
@@ -0,0 +1,161 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpdrawable-stroke.c
+ * Copyright (C) 2003 Simon Budig <simon@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <cairo.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpbase/gimpbase.h"
+
+#include "core-types.h"
+
+#include "gimpchannel.h"
+#include "gimpdrawable-fill.h"
+#include "gimpdrawable-stroke.h"
+#include "gimperror.h"
+#include "gimpimage.h"
+#include "gimpscanconvert.h"
+#include "gimpstrokeoptions.h"
+
+#include "vectors/gimpvectors.h"
+
+#include "gimp-intl.h"
+
+
+/* public functions */
+
+void
+gimp_drawable_stroke_boundary (GimpDrawable *drawable,
+ GimpStrokeOptions *options,
+ const GimpBoundSeg *bound_segs,
+ gint n_bound_segs,
+ gint offset_x,
+ gint offset_y,
+ gboolean push_undo)
+{
+ GimpScanConvert *scan_convert;
+
+ g_return_if_fail (GIMP_IS_DRAWABLE (drawable));
+ g_return_if_fail (gimp_item_is_attached (GIMP_ITEM (drawable)));
+ g_return_if_fail (GIMP_IS_STROKE_OPTIONS (options));
+ g_return_if_fail (bound_segs == NULL || n_bound_segs != 0);
+ g_return_if_fail (gimp_fill_options_get_style (GIMP_FILL_OPTIONS (options)) !=
+ GIMP_FILL_STYLE_PATTERN ||
+ gimp_context_get_pattern (GIMP_CONTEXT (options)) != NULL);
+
+ scan_convert = gimp_scan_convert_new_from_boundary (bound_segs, n_bound_segs,
+ offset_x, offset_y);
+
+ if (scan_convert)
+ {
+ gimp_drawable_stroke_scan_convert (drawable, options,
+ scan_convert, push_undo);
+ gimp_scan_convert_free (scan_convert);
+ }
+}
+
+gboolean
+gimp_drawable_stroke_vectors (GimpDrawable *drawable,
+ GimpStrokeOptions *options,
+ GimpVectors *vectors,
+ gboolean push_undo,
+ GError **error)
+{
+ const GimpBezierDesc *bezier;
+
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), FALSE);
+ g_return_val_if_fail (gimp_item_is_attached (GIMP_ITEM (drawable)), FALSE);
+ g_return_val_if_fail (GIMP_IS_STROKE_OPTIONS (options), FALSE);
+ g_return_val_if_fail (GIMP_IS_VECTORS (vectors), FALSE);
+ g_return_val_if_fail (gimp_fill_options_get_style (GIMP_FILL_OPTIONS (options)) !=
+ GIMP_FILL_STYLE_PATTERN ||
+ gimp_context_get_pattern (GIMP_CONTEXT (options)) != NULL,
+ FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ bezier = gimp_vectors_get_bezier (vectors);
+
+ if (bezier && bezier->num_data >= 2)
+ {
+ GimpScanConvert *scan_convert = gimp_scan_convert_new ();
+
+ gimp_scan_convert_add_bezier (scan_convert, bezier);
+ gimp_drawable_stroke_scan_convert (drawable, options,
+ scan_convert, push_undo);
+
+ gimp_scan_convert_free (scan_convert);
+
+ return TRUE;
+ }
+
+ g_set_error_literal (error, GIMP_ERROR, GIMP_FAILED,
+ _("Not enough points to stroke"));
+
+ return FALSE;
+}
+
+void
+gimp_drawable_stroke_scan_convert (GimpDrawable *drawable,
+ GimpStrokeOptions *options,
+ GimpScanConvert *scan_convert,
+ gboolean push_undo)
+{
+ gdouble width;
+ GimpUnit unit;
+
+ g_return_if_fail (GIMP_IS_DRAWABLE (drawable));
+ g_return_if_fail (gimp_item_is_attached (GIMP_ITEM (drawable)));
+ g_return_if_fail (GIMP_IS_STROKE_OPTIONS (options));
+ g_return_if_fail (scan_convert != NULL);
+ g_return_if_fail (gimp_fill_options_get_style (GIMP_FILL_OPTIONS (options)) !=
+ GIMP_FILL_STYLE_PATTERN ||
+ gimp_context_get_pattern (GIMP_CONTEXT (options)) != NULL);
+
+ if (! gimp_item_mask_intersect (GIMP_ITEM (drawable), NULL, NULL, NULL, NULL))
+ return;
+
+ width = gimp_stroke_options_get_width (options);
+ unit = gimp_stroke_options_get_unit (options);
+
+ if (unit != GIMP_UNIT_PIXEL)
+ {
+ GimpImage *image = gimp_item_get_image (GIMP_ITEM (drawable));
+ gdouble xres;
+ gdouble yres;
+
+ gimp_image_get_resolution (image, &xres, &yres);
+
+ gimp_scan_convert_set_pixel_ratio (scan_convert, yres / xres);
+
+ width = gimp_units_to_pixels (width, unit, yres);
+ }
+
+ gimp_scan_convert_stroke (scan_convert, width,
+ gimp_stroke_options_get_join_style (options),
+ gimp_stroke_options_get_cap_style (options),
+ gimp_stroke_options_get_miter_limit (options),
+ gimp_stroke_options_get_dash_offset (options),
+ gimp_stroke_options_get_dash_info (options));
+
+ gimp_drawable_fill_scan_convert (drawable, GIMP_FILL_OPTIONS (options),
+ scan_convert, push_undo);
+}
diff --git a/app/core/gimpdrawable-stroke.h b/app/core/gimpdrawable-stroke.h
new file mode 100644
index 0000000..5f16e5b
--- /dev/null
+++ b/app/core/gimpdrawable-stroke.h
@@ -0,0 +1,45 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpdrawable-stroke.h
+ * Copyright (C) 2003 Simon Budig <simon@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_DRAWABLE_STROKE_H__
+#define __GIMP_DRAWABLE_STROKE_H__
+
+
+void gimp_drawable_stroke_boundary (GimpDrawable *drawable,
+ GimpStrokeOptions *options,
+ const GimpBoundSeg *bound_segs,
+ gint n_bound_segs,
+ gint offset_x,
+ gint offset_y,
+ gboolean push_undo);
+
+gboolean gimp_drawable_stroke_vectors (GimpDrawable *drawable,
+ GimpStrokeOptions *options,
+ GimpVectors *vectors,
+ gboolean push_undo,
+ GError **error);
+
+void gimp_drawable_stroke_scan_convert (GimpDrawable *drawable,
+ GimpStrokeOptions *options,
+ GimpScanConvert *scan_convert,
+ gboolean push_undo);
+
+
+#endif /* __GIMP_DRAWABLE_STROKE_H__ */
diff --git a/app/core/gimpdrawable-transform.c b/app/core/gimpdrawable-transform.c
new file mode 100644
index 0000000..9cbedfd
--- /dev/null
+++ b/app/core/gimpdrawable-transform.c
@@ -0,0 +1,1070 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-2003 Spencer Kimball, Peter Mattis, and others
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <cairo.h>
+#include <gegl.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+#include "libgimpbase/gimpbase.h"
+#include "libgimpcolor/gimpcolor.h"
+#include "libgimpmath/gimpmath.h"
+
+#include "core-types.h"
+
+#include "gegl/gimp-gegl-apply-operation.h"
+#include "gegl/gimp-gegl-utils.h"
+
+#include "gimp.h"
+#include "gimp-transform-resize.h"
+#include "gimpchannel.h"
+#include "gimpcontext.h"
+#include "gimpdrawable-transform.h"
+#include "gimpimage.h"
+#include "gimpimage-undo.h"
+#include "gimpimage-undo-push.h"
+#include "gimplayer.h"
+#include "gimplayer-floating-selection.h"
+#include "gimplayer-new.h"
+#include "gimppickable.h"
+#include "gimpprogress.h"
+#include "gimpselection.h"
+
+#include "gimp-intl.h"
+
+
+#if defined (HAVE_FINITE)
+#define FINITE(x) finite(x)
+#elif defined (HAVE_ISFINITE)
+#define FINITE(x) isfinite(x)
+#elif defined (G_OS_WIN32)
+#define FINITE(x) _finite(x)
+#else
+#error "no FINITE() implementation available?!"
+#endif
+
+
+/* public functions */
+
+GeglBuffer *
+gimp_drawable_transform_buffer_affine (GimpDrawable *drawable,
+ GimpContext *context,
+ GeglBuffer *orig_buffer,
+ gint orig_offset_x,
+ gint orig_offset_y,
+ const GimpMatrix3 *matrix,
+ GimpTransformDirection direction,
+ GimpInterpolationType interpolation_type,
+ GimpTransformResize clip_result,
+ GimpColorProfile **buffer_profile,
+ gint *new_offset_x,
+ gint *new_offset_y,
+ GimpProgress *progress)
+{
+ GeglBuffer *new_buffer;
+ GimpMatrix3 m;
+ gint u1, v1, u2, v2; /* source bounding box */
+ gint x1, y1, x2, y2; /* target bounding box */
+ GimpMatrix3 gegl_matrix;
+
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), NULL);
+ g_return_val_if_fail (gimp_item_is_attached (GIMP_ITEM (drawable)), NULL);
+ g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL);
+ g_return_val_if_fail (GEGL_IS_BUFFER (orig_buffer), NULL);
+ g_return_val_if_fail (matrix != NULL, NULL);
+ g_return_val_if_fail (buffer_profile != NULL, NULL);
+ g_return_val_if_fail (new_offset_x != NULL, NULL);
+ g_return_val_if_fail (new_offset_y != NULL, NULL);
+ g_return_val_if_fail (progress == NULL || GIMP_IS_PROGRESS (progress), NULL);
+
+ *buffer_profile =
+ gimp_color_managed_get_color_profile (GIMP_COLOR_MANAGED (drawable));
+
+ m = *matrix;
+
+ if (direction == GIMP_TRANSFORM_BACKWARD)
+ {
+ /* Find the inverse of the transformation matrix */
+ gimp_matrix3_invert (&m);
+ }
+
+ u1 = orig_offset_x;
+ v1 = orig_offset_y;
+ u2 = u1 + gegl_buffer_get_width (orig_buffer);
+ v2 = v1 + gegl_buffer_get_height (orig_buffer);
+
+ /* Find the bounding coordinates of target */
+ gimp_transform_resize_boundary (&m, clip_result,
+ u1, v1, u2, v2,
+ &x1, &y1, &x2, &y2);
+
+ /* Get the new temporary buffer for the transformed result */
+ new_buffer = gegl_buffer_new (GEGL_RECTANGLE (0, 0, x2 - x1, y2 - y1),
+ gegl_buffer_get_format (orig_buffer));
+
+ gimp_matrix3_identity (&gegl_matrix);
+ gimp_matrix3_translate (&gegl_matrix, u1, v1);
+ gimp_matrix3_mult (&m, &gegl_matrix);
+ gimp_matrix3_translate (&gegl_matrix, -x1, -y1);
+
+ gimp_gegl_apply_transform (orig_buffer, progress, NULL,
+ new_buffer,
+ interpolation_type,
+ &gegl_matrix);
+
+ *new_offset_x = x1;
+ *new_offset_y = y1;
+
+ return new_buffer;
+}
+
+GeglBuffer *
+gimp_drawable_transform_buffer_flip (GimpDrawable *drawable,
+ GimpContext *context,
+ GeglBuffer *orig_buffer,
+ gint orig_offset_x,
+ gint orig_offset_y,
+ GimpOrientationType flip_type,
+ gdouble axis,
+ gboolean clip_result,
+ GimpColorProfile **buffer_profile,
+ gint *new_offset_x,
+ gint *new_offset_y)
+{
+ const Babl *format;
+ GeglBuffer *new_buffer;
+ GeglBufferIterator *iter;
+ GeglRectangle src_rect;
+ GeglRectangle dest_rect;
+ gint bpp;
+ gint orig_x, orig_y;
+ gint orig_width, orig_height;
+ gint new_x, new_y;
+ gint new_width, new_height;
+ gint x, y;
+
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), NULL);
+ g_return_val_if_fail (gimp_item_is_attached (GIMP_ITEM (drawable)), NULL);
+ g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL);
+ g_return_val_if_fail (GEGL_IS_BUFFER (orig_buffer), NULL);
+ g_return_val_if_fail (buffer_profile != NULL, NULL);
+ g_return_val_if_fail (new_offset_x != NULL, NULL);
+ g_return_val_if_fail (new_offset_y != NULL, NULL);
+
+ *buffer_profile =
+ gimp_color_managed_get_color_profile (GIMP_COLOR_MANAGED (drawable));
+
+ orig_x = orig_offset_x;
+ orig_y = orig_offset_y;
+ orig_width = gegl_buffer_get_width (orig_buffer);
+ orig_height = gegl_buffer_get_height (orig_buffer);
+
+ new_x = orig_x;
+ new_y = orig_y;
+ new_width = orig_width;
+ new_height = orig_height;
+
+ switch (flip_type)
+ {
+ case GIMP_ORIENTATION_HORIZONTAL:
+ new_x = RINT (-((gdouble) orig_x +
+ (gdouble) orig_width - axis) + axis);
+ break;
+
+ case GIMP_ORIENTATION_VERTICAL:
+ new_y = RINT (-((gdouble) orig_y +
+ (gdouble) orig_height - axis) + axis);
+ break;
+
+ case GIMP_ORIENTATION_UNKNOWN:
+ g_return_val_if_reached (NULL);
+ break;
+ }
+
+ format = gegl_buffer_get_format (orig_buffer);
+ bpp = babl_format_get_bytes_per_pixel (format);
+
+ new_buffer = gegl_buffer_new (GEGL_RECTANGLE (0, 0,
+ new_width, new_height),
+ format);
+
+ if (clip_result && (new_x != orig_x || new_y != orig_y))
+ {
+ GimpRGB bg;
+ GeglColor *color;
+ gint clip_x, clip_y;
+ gint clip_width, clip_height;
+
+ *new_offset_x = orig_x;
+ *new_offset_y = orig_y;
+
+ /* Use transparency, rather than the bg color, as the "outside" color of
+ * channels, and drawables with an alpha channel.
+ */
+ if (GIMP_IS_CHANNEL (drawable) || babl_format_has_alpha (format))
+ {
+ gimp_rgba_set (&bg, 0.0, 0.0, 0.0, 0.0);
+ }
+ else
+ {
+ gimp_context_get_background (context, &bg);
+ gimp_pickable_srgb_to_image_color (GIMP_PICKABLE (drawable),
+ &bg, &bg);
+ }
+
+ color = gimp_gegl_color_new (&bg);
+ gegl_buffer_set_color (new_buffer, NULL, color);
+ g_object_unref (color);
+
+ if (gimp_rectangle_intersect (orig_x, orig_y, orig_width, orig_height,
+ new_x, new_y, new_width, new_height,
+ &clip_x, &clip_y,
+ &clip_width, &clip_height))
+ {
+ orig_x = new_x = clip_x - orig_x;
+ orig_y = new_y = clip_y - orig_y;
+ }
+
+ orig_width = new_width = clip_width;
+ orig_height = new_height = clip_height;
+ }
+ else
+ {
+ *new_offset_x = new_x;
+ *new_offset_y = new_y;
+
+ orig_x = 0;
+ orig_y = 0;
+ new_x = 0;
+ new_y = 0;
+ }
+
+ if (new_width == 0 && new_height == 0)
+ return new_buffer;
+
+ dest_rect.x = new_x;
+ dest_rect.y = new_y;
+ dest_rect.width = new_width;
+ dest_rect.height = new_height;
+
+ iter = gegl_buffer_iterator_new (new_buffer, &dest_rect, 0, NULL,
+ GEGL_BUFFER_WRITE, GEGL_ABYSS_NONE, 1);
+
+ switch (flip_type)
+ {
+ case GIMP_ORIENTATION_HORIZONTAL:
+ while (gegl_buffer_iterator_next (iter))
+ {
+ gint stride = iter->items[0].roi.width * bpp;
+
+ src_rect = iter->items[0].roi;
+
+ src_rect.x = (orig_x + orig_width) -
+ (iter->items[0].roi.x - dest_rect.x) -
+ iter->items[0].roi.width;
+
+ gegl_buffer_get (orig_buffer, &src_rect, 1.0, NULL, iter->items[0].data,
+ stride, GEGL_ABYSS_NONE);
+
+ for (y = 0; y < iter->items[0].roi.height; y++)
+ {
+ guint8 *left = iter->items[0].data;
+ guint8 *right = iter->items[0].data;
+
+ left += y * stride;
+ right += y * stride + (iter->items[0].roi.width - 1) * bpp;
+
+ for (x = 0; x < iter->items[0].roi.width / 2; x++)
+ {
+ guint8 temp[bpp];
+
+ memcpy (temp, left, bpp);
+ memcpy (left, right, bpp);
+ memcpy (right, temp, bpp);
+
+ left += bpp;
+ right -= bpp;
+ }
+ }
+ }
+ break;
+
+ case GIMP_ORIENTATION_VERTICAL:
+ while (gegl_buffer_iterator_next (iter))
+ {
+ gint stride = iter->items[0].roi.width * bpp;
+
+ src_rect = iter->items[0].roi;
+
+ src_rect.y = (orig_y + orig_height) -
+ (iter->items[0].roi.y - dest_rect.y) -
+ iter->items[0].roi.height;
+
+ gegl_buffer_get (orig_buffer, &src_rect, 1.0, NULL, iter->items[0].data,
+ stride, GEGL_ABYSS_NONE);
+
+ for (x = 0; x < iter->items[0].roi.width; x++)
+ {
+ guint8 *top = iter->items[0].data;
+ guint8 *bottom = iter->items[0].data;
+
+ top += x * bpp;
+ bottom += x * bpp + (iter->items[0].roi.height - 1) * stride;
+
+ for (y = 0; y < iter->items[0].roi.height / 2; y++)
+ {
+ guint8 temp[bpp];
+
+ memcpy (temp, top, bpp);
+ memcpy (top, bottom, bpp);
+ memcpy (bottom, temp, bpp);
+
+ top += stride;
+ bottom -= stride;
+ }
+ }
+ }
+ break;
+
+ case GIMP_ORIENTATION_UNKNOWN:
+ gegl_buffer_iterator_stop (iter);
+ break;
+ }
+
+ return new_buffer;
+}
+
+static void
+gimp_drawable_transform_rotate_point (gint x,
+ gint y,
+ GimpRotationType rotate_type,
+ gdouble center_x,
+ gdouble center_y,
+ gint *new_x,
+ gint *new_y)
+{
+ g_return_if_fail (new_x != NULL);
+ g_return_if_fail (new_y != NULL);
+
+ switch (rotate_type)
+ {
+ case GIMP_ROTATE_90:
+ *new_x = RINT (center_x - (gdouble) y + center_y);
+ *new_y = RINT (center_y + (gdouble) x - center_x);
+ break;
+
+ case GIMP_ROTATE_180:
+ *new_x = RINT (center_x - ((gdouble) x - center_x));
+ *new_y = RINT (center_y - ((gdouble) y - center_y));
+ break;
+
+ case GIMP_ROTATE_270:
+ *new_x = RINT (center_x + (gdouble) y - center_y);
+ *new_y = RINT (center_y - (gdouble) x + center_x);
+ break;
+
+ default:
+ *new_x = x;
+ *new_y = y;
+ g_return_if_reached ();
+ }
+}
+
+GeglBuffer *
+gimp_drawable_transform_buffer_rotate (GimpDrawable *drawable,
+ GimpContext *context,
+ GeglBuffer *orig_buffer,
+ gint orig_offset_x,
+ gint orig_offset_y,
+ GimpRotationType rotate_type,
+ gdouble center_x,
+ gdouble center_y,
+ gboolean clip_result,
+ GimpColorProfile **buffer_profile,
+ gint *new_offset_x,
+ gint *new_offset_y)
+{
+ const Babl *format;
+ GeglBuffer *new_buffer;
+ GeglRectangle src_rect;
+ GeglRectangle dest_rect;
+ gint orig_x, orig_y;
+ gint orig_width, orig_height;
+ gint orig_bpp;
+ gint new_x, new_y;
+ gint new_width, new_height;
+
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), NULL);
+ g_return_val_if_fail (gimp_item_is_attached (GIMP_ITEM (drawable)), NULL);
+ g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL);
+ g_return_val_if_fail (GEGL_IS_BUFFER (orig_buffer), NULL);
+ g_return_val_if_fail (buffer_profile != NULL, NULL);
+ g_return_val_if_fail (new_offset_x != NULL, NULL);
+ g_return_val_if_fail (new_offset_y != NULL, NULL);
+
+ *buffer_profile =
+ gimp_color_managed_get_color_profile (GIMP_COLOR_MANAGED (drawable));
+
+ orig_x = orig_offset_x;
+ orig_y = orig_offset_y;
+ orig_width = gegl_buffer_get_width (orig_buffer);
+ orig_height = gegl_buffer_get_height (orig_buffer);
+ orig_bpp = babl_format_get_bytes_per_pixel (gegl_buffer_get_format (orig_buffer));
+
+ switch (rotate_type)
+ {
+ case GIMP_ROTATE_90:
+ gimp_drawable_transform_rotate_point (orig_x,
+ orig_y + orig_height,
+ rotate_type, center_x, center_y,
+ &new_x, &new_y);
+ new_width = orig_height;
+ new_height = orig_width;
+ break;
+
+ case GIMP_ROTATE_180:
+ gimp_drawable_transform_rotate_point (orig_x + orig_width,
+ orig_y + orig_height,
+ rotate_type, center_x, center_y,
+ &new_x, &new_y);
+ new_width = orig_width;
+ new_height = orig_height;
+ break;
+
+ case GIMP_ROTATE_270:
+ gimp_drawable_transform_rotate_point (orig_x + orig_width,
+ orig_y,
+ rotate_type, center_x, center_y,
+ &new_x, &new_y);
+ new_width = orig_height;
+ new_height = orig_width;
+ break;
+
+ default:
+ g_return_val_if_reached (NULL);
+ break;
+ }
+
+ format = gegl_buffer_get_format (orig_buffer);
+
+ if (clip_result && (new_x != orig_x || new_y != orig_y ||
+ new_width != orig_width || new_height != orig_height))
+
+ {
+ GimpRGB bg;
+ GeglColor *color;
+ gint clip_x, clip_y;
+ gint clip_width, clip_height;
+
+ new_buffer = gegl_buffer_new (GEGL_RECTANGLE (0, 0,
+ orig_width, orig_height),
+ format);
+
+ *new_offset_x = orig_x;
+ *new_offset_y = orig_y;
+
+ /* Use transparency, rather than the bg color, as the "outside" color of
+ * channels, and drawables with an alpha channel.
+ */
+ if (GIMP_IS_CHANNEL (drawable) || babl_format_has_alpha (format))
+ {
+ gimp_rgba_set (&bg, 0.0, 0.0, 0.0, 0.0);
+ }
+ else
+ {
+ gimp_context_get_background (context, &bg);
+ gimp_pickable_srgb_to_image_color (GIMP_PICKABLE (drawable),
+ &bg, &bg);
+ }
+
+ color = gimp_gegl_color_new (&bg);
+ gegl_buffer_set_color (new_buffer, NULL, color);
+ g_object_unref (color);
+
+ if (gimp_rectangle_intersect (orig_x, orig_y, orig_width, orig_height,
+ new_x, new_y, new_width, new_height,
+ &clip_x, &clip_y,
+ &clip_width, &clip_height))
+ {
+ gint saved_orig_x = orig_x;
+ gint saved_orig_y = orig_y;
+
+ new_x = clip_x - orig_x;
+ new_y = clip_y - orig_y;
+
+ switch (rotate_type)
+ {
+ case GIMP_ROTATE_90:
+ gimp_drawable_transform_rotate_point (clip_x + clip_width,
+ clip_y,
+ GIMP_ROTATE_270,
+ center_x,
+ center_y,
+ &orig_x,
+ &orig_y);
+ orig_x -= saved_orig_x;
+ orig_y -= saved_orig_y;
+ orig_width = clip_height;
+ orig_height = clip_width;
+ break;
+
+ case GIMP_ROTATE_180:
+ orig_x = clip_x - orig_x;
+ orig_y = clip_y - orig_y;
+ orig_width = clip_width;
+ orig_height = clip_height;
+ break;
+
+ case GIMP_ROTATE_270:
+ gimp_drawable_transform_rotate_point (clip_x,
+ clip_y + clip_height,
+ GIMP_ROTATE_90,
+ center_x,
+ center_y,
+ &orig_x,
+ &orig_y);
+ orig_x -= saved_orig_x;
+ orig_y -= saved_orig_y;
+ orig_width = clip_height;
+ orig_height = clip_width;
+ break;
+ }
+
+ new_width = clip_width;
+ new_height = clip_height;
+ }
+ else
+ {
+ new_width = 0;
+ new_height = 0;
+ }
+ }
+ else
+ {
+ new_buffer = gegl_buffer_new (GEGL_RECTANGLE (0, 0,
+ new_width, new_height),
+ format);
+
+ *new_offset_x = new_x;
+ *new_offset_y = new_y;
+
+ orig_x = 0;
+ orig_y = 0;
+ new_x = 0;
+ new_y = 0;
+ }
+
+ if (new_width < 1 || new_height < 1)
+ return new_buffer;
+
+ src_rect.x = orig_x;
+ src_rect.y = orig_y;
+ src_rect.width = orig_width;
+ src_rect.height = orig_height;
+
+ dest_rect.x = new_x;
+ dest_rect.y = new_y;
+ dest_rect.width = new_width;
+ dest_rect.height = new_height;
+
+ switch (rotate_type)
+ {
+ case GIMP_ROTATE_90:
+ {
+ guchar *buf = g_new (guchar, new_height * orig_bpp);
+ gint i;
+
+ /* Not cool, we leak memory if we return, but anyway that is
+ * never supposed to happen. If we see this warning, a bug has
+ * to be fixed!
+ */
+ g_return_val_if_fail (new_height == orig_width, NULL);
+
+ src_rect.y = orig_y + orig_height - 1;
+ src_rect.height = 1;
+
+ dest_rect.x = new_x;
+ dest_rect.width = 1;
+
+ for (i = 0; i < orig_height; i++)
+ {
+ src_rect.y = orig_y + orig_height - 1 - i;
+ dest_rect.x = new_x + i;
+
+ gegl_buffer_get (orig_buffer, &src_rect, 1.0, NULL, buf,
+ GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
+ gegl_buffer_set (new_buffer, &dest_rect, 0, NULL, buf,
+ GEGL_AUTO_ROWSTRIDE);
+ }
+
+ g_free (buf);
+ }
+ break;
+
+ case GIMP_ROTATE_180:
+ {
+ guchar *buf = g_new (guchar, new_width * orig_bpp);
+ gint i, j, k;
+
+ /* Not cool, we leak memory if we return, but anyway that is
+ * never supposed to happen. If we see this warning, a bug has
+ * to be fixed!
+ */
+ g_return_val_if_fail (new_width == orig_width, NULL);
+
+ src_rect.y = orig_y + orig_height - 1;
+ src_rect.height = 1;
+
+ dest_rect.y = new_y;
+ dest_rect.height = 1;
+
+ for (i = 0; i < orig_height; i++)
+ {
+ src_rect.y = orig_y + orig_height - 1 - i;
+ dest_rect.y = new_y + i;
+
+ gegl_buffer_get (orig_buffer, &src_rect, 1.0, NULL, buf,
+ GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
+
+ for (j = 0; j < orig_width / 2; j++)
+ {
+ guchar *left = buf + j * orig_bpp;
+ guchar *right = buf + (orig_width - 1 - j) * orig_bpp;
+
+ for (k = 0; k < orig_bpp; k++)
+ {
+ guchar tmp = left[k];
+ left[k] = right[k];
+ right[k] = tmp;
+ }
+ }
+
+ gegl_buffer_set (new_buffer, &dest_rect, 0, NULL, buf,
+ GEGL_AUTO_ROWSTRIDE);
+ }
+
+ g_free (buf);
+ }
+ break;
+
+ case GIMP_ROTATE_270:
+ {
+ guchar *buf = g_new (guchar, new_width * orig_bpp);
+ gint i;
+
+ /* Not cool, we leak memory if we return, but anyway that is
+ * never supposed to happen. If we see this warning, a bug has
+ * to be fixed!
+ */
+ g_return_val_if_fail (new_width == orig_height, NULL);
+
+ src_rect.x = orig_x + orig_width - 1;
+ src_rect.width = 1;
+
+ dest_rect.y = new_y;
+ dest_rect.height = 1;
+
+ for (i = 0; i < orig_width; i++)
+ {
+ src_rect.x = orig_x + orig_width - 1 - i;
+ dest_rect.y = new_y + i;
+
+ gegl_buffer_get (orig_buffer, &src_rect, 1.0, NULL, buf,
+ GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
+ gegl_buffer_set (new_buffer, &dest_rect, 0, NULL, buf,
+ GEGL_AUTO_ROWSTRIDE);
+ }
+
+ g_free (buf);
+ }
+ break;
+ }
+
+ return new_buffer;
+}
+
+GimpDrawable *
+gimp_drawable_transform_affine (GimpDrawable *drawable,
+ GimpContext *context,
+ const GimpMatrix3 *matrix,
+ GimpTransformDirection direction,
+ GimpInterpolationType interpolation_type,
+ GimpTransformResize clip_result,
+ GimpProgress *progress)
+{
+ GimpImage *image;
+ GeglBuffer *orig_buffer;
+ gint orig_offset_x;
+ gint orig_offset_y;
+ gboolean new_layer;
+ GimpDrawable *result = NULL;
+
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), NULL);
+ g_return_val_if_fail (gimp_item_is_attached (GIMP_ITEM (drawable)), NULL);
+ g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL);
+ g_return_val_if_fail (matrix != NULL, NULL);
+ g_return_val_if_fail (progress == NULL || GIMP_IS_PROGRESS (progress), NULL);
+
+ image = gimp_item_get_image (GIMP_ITEM (drawable));
+
+ /* Start a transform undo group */
+ gimp_image_undo_group_start (image,
+ GIMP_UNDO_GROUP_TRANSFORM,
+ C_("undo-type", "Transform"));
+
+ /* Cut/Copy from the specified drawable */
+ orig_buffer = gimp_drawable_transform_cut (drawable, context,
+ &orig_offset_x, &orig_offset_y,
+ &new_layer);
+
+ if (orig_buffer)
+ {
+ GeglBuffer *new_buffer;
+ gint new_offset_x;
+ gint new_offset_y;
+ GimpColorProfile *profile;
+
+ /* also transform the mask if we are transforming an entire layer */
+ if (GIMP_IS_LAYER (drawable) &&
+ gimp_layer_get_mask (GIMP_LAYER (drawable)) &&
+ gimp_channel_is_empty (gimp_image_get_mask (image)))
+ {
+ GimpLayerMask *mask = gimp_layer_get_mask (GIMP_LAYER (drawable));
+
+ gimp_item_transform (GIMP_ITEM (mask), context,
+ matrix,
+ direction,
+ interpolation_type,
+ clip_result,
+ progress);
+ }
+
+ /* transform the buffer */
+ new_buffer = gimp_drawable_transform_buffer_affine (drawable, context,
+ orig_buffer,
+ orig_offset_x,
+ orig_offset_y,
+ matrix,
+ direction,
+ interpolation_type,
+ clip_result,
+ &profile,
+ &new_offset_x,
+ &new_offset_y,
+ progress);
+
+ /* Free the cut/copied buffer */
+ g_object_unref (orig_buffer);
+
+ if (new_buffer)
+ {
+ result = gimp_drawable_transform_paste (drawable, new_buffer, profile,
+ new_offset_x, new_offset_y,
+ new_layer);
+ g_object_unref (new_buffer);
+ }
+ }
+
+ /* push the undo group end */
+ gimp_image_undo_group_end (image);
+
+ return result;
+}
+
+GimpDrawable *
+gimp_drawable_transform_flip (GimpDrawable *drawable,
+ GimpContext *context,
+ GimpOrientationType flip_type,
+ gdouble axis,
+ gboolean clip_result)
+{
+ GimpImage *image;
+ GeglBuffer *orig_buffer;
+ gint orig_offset_x;
+ gint orig_offset_y;
+ gboolean new_layer;
+ GimpDrawable *result = NULL;
+
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), NULL);
+ g_return_val_if_fail (gimp_item_is_attached (GIMP_ITEM (drawable)), NULL);
+ g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL);
+
+ image = gimp_item_get_image (GIMP_ITEM (drawable));
+
+ /* Start a transform undo group */
+ gimp_image_undo_group_start (image,
+ GIMP_UNDO_GROUP_TRANSFORM,
+ C_("undo-type", "Flip"));
+
+ /* Cut/Copy from the specified drawable */
+ orig_buffer = gimp_drawable_transform_cut (drawable, context,
+ &orig_offset_x, &orig_offset_y,
+ &new_layer);
+
+ if (orig_buffer)
+ {
+ GeglBuffer *new_buffer;
+ gint new_offset_x;
+ gint new_offset_y;
+ GimpColorProfile *profile;
+
+ /* also transform the mask if we are transforming an entire layer */
+ if (GIMP_IS_LAYER (drawable) &&
+ gimp_layer_get_mask (GIMP_LAYER (drawable)) &&
+ gimp_channel_is_empty (gimp_image_get_mask (image)))
+ {
+ GimpLayerMask *mask = gimp_layer_get_mask (GIMP_LAYER (drawable));
+
+ gimp_item_flip (GIMP_ITEM (mask), context,
+ flip_type,
+ axis,
+ clip_result);
+ }
+
+ /* transform the buffer */
+ new_buffer = gimp_drawable_transform_buffer_flip (drawable, context,
+ orig_buffer,
+ orig_offset_x,
+ orig_offset_y,
+ flip_type, axis,
+ clip_result,
+ &profile,
+ &new_offset_x,
+ &new_offset_y);
+
+ /* Free the cut/copied buffer */
+ g_object_unref (orig_buffer);
+
+ if (new_buffer)
+ {
+ result = gimp_drawable_transform_paste (drawable, new_buffer, profile,
+ new_offset_x, new_offset_y,
+ new_layer);
+ g_object_unref (new_buffer);
+ }
+ }
+
+ /* push the undo group end */
+ gimp_image_undo_group_end (image);
+
+ return result;
+}
+
+GimpDrawable *
+gimp_drawable_transform_rotate (GimpDrawable *drawable,
+ GimpContext *context,
+ GimpRotationType rotate_type,
+ gdouble center_x,
+ gdouble center_y,
+ gboolean clip_result)
+{
+ GimpImage *image;
+ GeglBuffer *orig_buffer;
+ gint orig_offset_x;
+ gint orig_offset_y;
+ gboolean new_layer;
+ GimpDrawable *result = NULL;
+
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), NULL);
+ g_return_val_if_fail (gimp_item_is_attached (GIMP_ITEM (drawable)), NULL);
+ g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL);
+
+ image = gimp_item_get_image (GIMP_ITEM (drawable));
+
+ /* Start a transform undo group */
+ gimp_image_undo_group_start (image,
+ GIMP_UNDO_GROUP_TRANSFORM,
+ C_("undo-type", "Rotate"));
+
+ /* Cut/Copy from the specified drawable */
+ orig_buffer = gimp_drawable_transform_cut (drawable, context,
+ &orig_offset_x, &orig_offset_y,
+ &new_layer);
+
+ if (orig_buffer)
+ {
+ GeglBuffer *new_buffer;
+ gint new_offset_x;
+ gint new_offset_y;
+ GimpColorProfile *profile;
+
+ /* also transform the mask if we are transforming an entire layer */
+ if (GIMP_IS_LAYER (drawable) &&
+ gimp_layer_get_mask (GIMP_LAYER (drawable)) &&
+ gimp_channel_is_empty (gimp_image_get_mask (image)))
+ {
+ GimpLayerMask *mask = gimp_layer_get_mask (GIMP_LAYER (drawable));
+
+ gimp_item_rotate (GIMP_ITEM (mask), context,
+ rotate_type,
+ center_x,
+ center_y,
+ clip_result);
+ }
+
+ /* transform the buffer */
+ new_buffer = gimp_drawable_transform_buffer_rotate (drawable, context,
+ orig_buffer,
+ orig_offset_x,
+ orig_offset_y,
+ rotate_type,
+ center_x, center_y,
+ clip_result,
+ &profile,
+ &new_offset_x,
+ &new_offset_y);
+
+ /* Free the cut/copied buffer */
+ g_object_unref (orig_buffer);
+
+ if (new_buffer)
+ {
+ result = gimp_drawable_transform_paste (drawable, new_buffer, profile,
+ new_offset_x, new_offset_y,
+ new_layer);
+ g_object_unref (new_buffer);
+ }
+ }
+
+ /* push the undo group end */
+ gimp_image_undo_group_end (image);
+
+ return result;
+}
+
+GeglBuffer *
+gimp_drawable_transform_cut (GimpDrawable *drawable,
+ GimpContext *context,
+ gint *offset_x,
+ gint *offset_y,
+ gboolean *new_layer)
+{
+ GimpImage *image;
+ GeglBuffer *buffer;
+
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), NULL);
+ g_return_val_if_fail (gimp_item_is_attached (GIMP_ITEM (drawable)), NULL);
+ g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL);
+ g_return_val_if_fail (offset_x != NULL, NULL);
+ g_return_val_if_fail (offset_y != NULL, NULL);
+ g_return_val_if_fail (new_layer != NULL, NULL);
+
+ image = gimp_item_get_image (GIMP_ITEM (drawable));
+
+ /* extract the selected mask if there is a selection */
+ if (! gimp_channel_is_empty (gimp_image_get_mask (image)))
+ {
+ gint x, y, w, h;
+
+ /* set the keep_indexed flag to FALSE here, since we use
+ * gimp_layer_new_from_gegl_buffer() later which assumes that
+ * the buffer are either RGB or GRAY. Eeek!!! (Sven)
+ */
+ if (gimp_item_mask_intersect (GIMP_ITEM (drawable), &x, &y, &w, &h))
+ {
+ buffer = gimp_selection_extract (GIMP_SELECTION (gimp_image_get_mask (image)),
+ GIMP_PICKABLE (drawable),
+ context,
+ TRUE, FALSE, TRUE,
+ offset_x, offset_y,
+ NULL);
+ /* clear the selection */
+ gimp_channel_clear (gimp_image_get_mask (image), NULL, TRUE);
+
+ *new_layer = TRUE;
+ }
+ else
+ {
+ buffer = NULL;
+ *new_layer = FALSE;
+ }
+ }
+ else /* otherwise, just copy the layer */
+ {
+ buffer = gimp_selection_extract (GIMP_SELECTION (gimp_image_get_mask (image)),
+ GIMP_PICKABLE (drawable),
+ context,
+ FALSE, TRUE, GIMP_IS_LAYER (drawable),
+ offset_x, offset_y,
+ NULL);
+
+ *new_layer = FALSE;
+ }
+
+ return buffer;
+}
+
+GimpDrawable *
+gimp_drawable_transform_paste (GimpDrawable *drawable,
+ GeglBuffer *buffer,
+ GimpColorProfile *buffer_profile,
+ gint offset_x,
+ gint offset_y,
+ gboolean new_layer)
+{
+ GimpImage *image;
+ GimpLayer *layer = NULL;
+ const gchar *undo_desc = NULL;
+
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), NULL);
+ g_return_val_if_fail (gimp_item_is_attached (GIMP_ITEM (drawable)), NULL);
+ g_return_val_if_fail (GEGL_IS_BUFFER (buffer), NULL);
+ g_return_val_if_fail (GIMP_IS_COLOR_PROFILE (buffer_profile), NULL);
+
+ image = gimp_item_get_image (GIMP_ITEM (drawable));
+
+ if (GIMP_IS_LAYER (drawable))
+ undo_desc = C_("undo-type", "Transform Layer");
+ else if (GIMP_IS_CHANNEL (drawable))
+ undo_desc = C_("undo-type", "Transform Channel");
+ else
+ return NULL;
+
+ gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_EDIT_PASTE, undo_desc);
+
+ if (new_layer)
+ {
+ layer =
+ gimp_layer_new_from_gegl_buffer (buffer, image,
+ gimp_drawable_get_format_with_alpha (drawable),
+ _("Transformation"),
+ GIMP_OPACITY_OPAQUE,
+ gimp_image_get_default_new_layer_mode (image),
+ buffer_profile);
+
+ gimp_item_set_offset (GIMP_ITEM (layer), offset_x, offset_y);
+
+ floating_sel_attach (layer, drawable);
+
+ drawable = GIMP_DRAWABLE (layer);
+ }
+ else
+ {
+ gimp_drawable_set_buffer_full (drawable, TRUE, NULL,
+ buffer,
+ GEGL_RECTANGLE (offset_x, offset_y, 0, 0),
+ TRUE);
+ }
+
+ gimp_image_undo_group_end (image);
+
+ return drawable;
+}
diff --git a/app/core/gimpdrawable-transform.h b/app/core/gimpdrawable-transform.h
new file mode 100644
index 0000000..8cdd53f
--- /dev/null
+++ b/app/core/gimpdrawable-transform.h
@@ -0,0 +1,94 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-2001 Spencer Kimball, Peter Mattis, and others
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_DRAWABLE_TRANSFORM_H__
+#define __GIMP_DRAWABLE_TRANSFORM_H__
+
+
+GeglBuffer * gimp_drawable_transform_buffer_affine (GimpDrawable *drawable,
+ GimpContext *context,
+ GeglBuffer *orig_buffer,
+ gint orig_offset_x,
+ gint orig_offset_y,
+ const GimpMatrix3 *matrix,
+ GimpTransformDirection direction,
+ GimpInterpolationType interpolation_type,
+ GimpTransformResize clip_result,
+ GimpColorProfile **buffer_profile,
+ gint *new_offset_x,
+ gint *new_offset_y,
+ GimpProgress *progress);
+GeglBuffer * gimp_drawable_transform_buffer_flip (GimpDrawable *drawable,
+ GimpContext *context,
+ GeglBuffer *orig_buffer,
+ gint orig_offset_x,
+ gint orig_offset_y,
+ GimpOrientationType flip_type,
+ gdouble axis,
+ gboolean clip_result,
+ GimpColorProfile **buffer_profile,
+ gint *new_offset_x,
+ gint *new_offset_y);
+
+GeglBuffer * gimp_drawable_transform_buffer_rotate (GimpDrawable *drawable,
+ GimpContext *context,
+ GeglBuffer *buffer,
+ gint orig_offset_x,
+ gint orig_offset_y,
+ GimpRotationType rotate_type,
+ gdouble center_x,
+ gdouble center_y,
+ gboolean clip_result,
+ GimpColorProfile **buffer_profile,
+ gint *new_offset_x,
+ gint *new_offset_y);
+
+GimpDrawable * gimp_drawable_transform_affine (GimpDrawable *drawable,
+ GimpContext *context,
+ const GimpMatrix3 *matrix,
+ GimpTransformDirection direction,
+ GimpInterpolationType interpolation_type,
+ GimpTransformResize clip_result,
+ GimpProgress *progress);
+
+GimpDrawable * gimp_drawable_transform_flip (GimpDrawable *drawable,
+ GimpContext *context,
+ GimpOrientationType flip_type,
+ gdouble axis,
+ gboolean clip_result);
+
+GimpDrawable * gimp_drawable_transform_rotate (GimpDrawable *drawable,
+ GimpContext *context,
+ GimpRotationType rotate_type,
+ gdouble center_x,
+ gdouble center_y,
+ gboolean clip_result);
+
+GeglBuffer * gimp_drawable_transform_cut (GimpDrawable *drawable,
+ GimpContext *context,
+ gint *offset_x,
+ gint *offset_y,
+ gboolean *new_layer);
+GimpDrawable * gimp_drawable_transform_paste (GimpDrawable *drawable,
+ GeglBuffer *buffer,
+ GimpColorProfile *buffer_profile,
+ gint offset_x,
+ gint offset_y,
+ gboolean new_layer);
+
+
+#endif /* __GIMP_DRAWABLE_TRANSFORM_H__ */
diff --git a/app/core/gimpdrawable.c b/app/core/gimpdrawable.c
new file mode 100644
index 0000000..9a85eee
--- /dev/null
+++ b/app/core/gimpdrawable.c
@@ -0,0 +1,1916 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <cairo.h>
+#include <gegl.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+#include "libgimpbase/gimpbase.h"
+#include "libgimpcolor/gimpcolor.h"
+#include "libgimpmath/gimpmath.h"
+
+#include "core-types.h"
+
+#include "gegl/gimp-babl.h"
+#include "gegl/gimp-gegl-apply-operation.h"
+#include "gegl/gimp-gegl-loops.h"
+#include "gegl/gimp-gegl-utils.h"
+
+#include "gimp-memsize.h"
+#include "gimp-utils.h"
+#include "gimpchannel.h"
+#include "gimpcontext.h"
+#include "gimpdrawable-combine.h"
+#include "gimpdrawable-fill.h"
+#include "gimpdrawable-floating-selection.h"
+#include "gimpdrawable-preview.h"
+#include "gimpdrawable-private.h"
+#include "gimpdrawable-shadow.h"
+#include "gimpdrawable-transform.h"
+#include "gimpfilterstack.h"
+#include "gimpimage.h"
+#include "gimpimage-colormap.h"
+#include "gimpimage-undo-push.h"
+#include "gimpmarshal.h"
+#include "gimppickable.h"
+#include "gimpprogress.h"
+
+#include "gimp-log.h"
+
+#include "gimp-intl.h"
+
+
+#define PAINT_UPDATE_CHUNK_WIDTH 32
+#define PAINT_UPDATE_CHUNK_HEIGHT 32
+
+
+enum
+{
+ UPDATE,
+ FORMAT_CHANGED,
+ ALPHA_CHANGED,
+ BOUNDING_BOX_CHANGED,
+ LAST_SIGNAL
+};
+
+enum
+{
+ PROP_0,
+ PROP_BUFFER
+};
+
+
+/* local function prototypes */
+
+static void gimp_color_managed_iface_init (GimpColorManagedInterface *iface);
+static void gimp_pickable_iface_init (GimpPickableInterface *iface);
+
+static void gimp_drawable_dispose (GObject *object);
+static void gimp_drawable_finalize (GObject *object);
+static void gimp_drawable_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_drawable_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static gint64 gimp_drawable_get_memsize (GimpObject *object,
+ gint64 *gui_size);
+
+static gboolean gimp_drawable_get_size (GimpViewable *viewable,
+ gint *width,
+ gint *height);
+static void gimp_drawable_preview_freeze (GimpViewable *viewable);
+static void gimp_drawable_preview_thaw (GimpViewable *viewable);
+
+static GeglNode * gimp_drawable_get_node (GimpFilter *filter);
+
+static void gimp_drawable_removed (GimpItem *item);
+static GimpItem * gimp_drawable_duplicate (GimpItem *item,
+ GType new_type);
+static void gimp_drawable_scale (GimpItem *item,
+ gint new_width,
+ gint new_height,
+ gint new_offset_x,
+ gint new_offset_y,
+ GimpInterpolationType interp_type,
+ GimpProgress *progress);
+static void gimp_drawable_resize (GimpItem *item,
+ GimpContext *context,
+ GimpFillType fill_type,
+ gint new_width,
+ gint new_height,
+ gint offset_x,
+ gint offset_y);
+static void gimp_drawable_flip (GimpItem *item,
+ GimpContext *context,
+ GimpOrientationType flip_type,
+ gdouble axis,
+ gboolean clip_result);
+static void gimp_drawable_rotate (GimpItem *item,
+ GimpContext *context,
+ GimpRotationType rotate_type,
+ gdouble center_x,
+ gdouble center_y,
+ gboolean clip_result);
+static void gimp_drawable_transform (GimpItem *item,
+ GimpContext *context,
+ const GimpMatrix3 *matrix,
+ GimpTransformDirection direction,
+ GimpInterpolationType interpolation_type,
+ GimpTransformResize clip_result,
+ GimpProgress *progress);
+
+static const guint8 *
+ gimp_drawable_get_icc_profile (GimpColorManaged *managed,
+ gsize *len);
+static GimpColorProfile *
+ gimp_drawable_get_color_profile (GimpColorManaged *managed);
+static void gimp_drawable_profile_changed (GimpColorManaged *managed);
+
+static gboolean gimp_drawable_get_pixel_at (GimpPickable *pickable,
+ gint x,
+ gint y,
+ const Babl *format,
+ gpointer pixel);
+static void gimp_drawable_get_pixel_average (GimpPickable *pickable,
+ const GeglRectangle *rect,
+ const Babl *format,
+ gpointer pixel);
+
+static void gimp_drawable_real_update (GimpDrawable *drawable,
+ gint x,
+ gint y,
+ gint width,
+ gint height);
+
+static gint64 gimp_drawable_real_estimate_memsize (GimpDrawable *drawable,
+ GimpComponentType component_type,
+ gint width,
+ gint height);
+
+static void gimp_drawable_real_update_all (GimpDrawable *drawable);
+
+static GimpComponentMask
+ gimp_drawable_real_get_active_mask (GimpDrawable *drawable);
+
+static gboolean gimp_drawable_real_supports_alpha
+ (GimpDrawable *drawable);
+
+static void gimp_drawable_real_convert_type (GimpDrawable *drawable,
+ GimpImage *dest_image,
+ const Babl *new_format,
+ GimpColorProfile *dest_profile,
+ GeglDitherMethod layer_dither_type,
+ GeglDitherMethod mask_dither_type,
+ gboolean push_undo,
+ GimpProgress *progress);
+
+static GeglBuffer * gimp_drawable_real_get_buffer (GimpDrawable *drawable);
+static void gimp_drawable_real_set_buffer (GimpDrawable *drawable,
+ gboolean push_undo,
+ const gchar *undo_desc,
+ GeglBuffer *buffer,
+ const GeglRectangle *bounds);
+
+static GeglRectangle gimp_drawable_real_get_bounding_box
+ (GimpDrawable *drawable);
+
+static void gimp_drawable_real_push_undo (GimpDrawable *drawable,
+ const gchar *undo_desc,
+ GeglBuffer *buffer,
+ gint x,
+ gint y,
+ gint width,
+ gint height);
+static void gimp_drawable_real_swap_pixels (GimpDrawable *drawable,
+ GeglBuffer *buffer,
+ gint x,
+ gint y);
+static GeglNode * gimp_drawable_real_get_source_node (GimpDrawable *drawable);
+
+static void gimp_drawable_format_changed (GimpDrawable *drawable);
+static void gimp_drawable_alpha_changed (GimpDrawable *drawable);
+
+
+G_DEFINE_TYPE_WITH_CODE (GimpDrawable, gimp_drawable, GIMP_TYPE_ITEM,
+ G_ADD_PRIVATE (GimpDrawable)
+ G_IMPLEMENT_INTERFACE (GIMP_TYPE_COLOR_MANAGED,
+ gimp_color_managed_iface_init)
+ G_IMPLEMENT_INTERFACE (GIMP_TYPE_PICKABLE,
+ gimp_pickable_iface_init))
+
+#define parent_class gimp_drawable_parent_class
+
+static guint gimp_drawable_signals[LAST_SIGNAL] = { 0 };
+
+
+static void
+gimp_drawable_class_init (GimpDrawableClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpObjectClass *gimp_object_class = GIMP_OBJECT_CLASS (klass);
+ GimpViewableClass *viewable_class = GIMP_VIEWABLE_CLASS (klass);
+ GimpFilterClass *filter_class = GIMP_FILTER_CLASS (klass);
+ GimpItemClass *item_class = GIMP_ITEM_CLASS (klass);
+
+ gimp_drawable_signals[UPDATE] =
+ g_signal_new ("update",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpDrawableClass, update),
+ NULL, NULL,
+ gimp_marshal_VOID__INT_INT_INT_INT,
+ G_TYPE_NONE, 4,
+ G_TYPE_INT,
+ G_TYPE_INT,
+ G_TYPE_INT,
+ G_TYPE_INT);
+
+ gimp_drawable_signals[FORMAT_CHANGED] =
+ g_signal_new ("format-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpDrawableClass, format_changed),
+ NULL, NULL,
+ gimp_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ gimp_drawable_signals[ALPHA_CHANGED] =
+ g_signal_new ("alpha-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpDrawableClass, alpha_changed),
+ NULL, NULL,
+ gimp_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ gimp_drawable_signals[BOUNDING_BOX_CHANGED] =
+ g_signal_new ("bounding-box-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpDrawableClass, bounding_box_changed),
+ NULL, NULL,
+ gimp_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ object_class->dispose = gimp_drawable_dispose;
+ object_class->finalize = gimp_drawable_finalize;
+ object_class->set_property = gimp_drawable_set_property;
+ object_class->get_property = gimp_drawable_get_property;
+
+ gimp_object_class->get_memsize = gimp_drawable_get_memsize;
+
+ viewable_class->get_size = gimp_drawable_get_size;
+ viewable_class->get_new_preview = gimp_drawable_get_new_preview;
+ viewable_class->get_new_pixbuf = gimp_drawable_get_new_pixbuf;
+ viewable_class->preview_freeze = gimp_drawable_preview_freeze;
+ viewable_class->preview_thaw = gimp_drawable_preview_thaw;
+
+ filter_class->get_node = gimp_drawable_get_node;
+
+ item_class->removed = gimp_drawable_removed;
+ item_class->duplicate = gimp_drawable_duplicate;
+ item_class->scale = gimp_drawable_scale;
+ item_class->resize = gimp_drawable_resize;
+ item_class->flip = gimp_drawable_flip;
+ item_class->rotate = gimp_drawable_rotate;
+ item_class->transform = gimp_drawable_transform;
+
+ klass->update = gimp_drawable_real_update;
+ klass->format_changed = NULL;
+ klass->alpha_changed = NULL;
+ klass->bounding_box_changed = NULL;
+ klass->estimate_memsize = gimp_drawable_real_estimate_memsize;
+ klass->update_all = gimp_drawable_real_update_all;
+ klass->invalidate_boundary = NULL;
+ klass->get_active_components = NULL;
+ klass->get_active_mask = gimp_drawable_real_get_active_mask;
+ klass->supports_alpha = gimp_drawable_real_supports_alpha;
+ klass->convert_type = gimp_drawable_real_convert_type;
+ klass->apply_buffer = gimp_drawable_real_apply_buffer;
+ klass->get_buffer = gimp_drawable_real_get_buffer;
+ klass->set_buffer = gimp_drawable_real_set_buffer;
+ klass->get_bounding_box = gimp_drawable_real_get_bounding_box;
+ klass->push_undo = gimp_drawable_real_push_undo;
+ klass->swap_pixels = gimp_drawable_real_swap_pixels;
+ klass->get_source_node = gimp_drawable_real_get_source_node;
+
+ g_object_class_override_property (object_class, PROP_BUFFER, "buffer");
+}
+
+static void
+gimp_drawable_init (GimpDrawable *drawable)
+{
+ drawable->private = gimp_drawable_get_instance_private (drawable);
+
+ drawable->private->filter_stack = gimp_filter_stack_new (GIMP_TYPE_FILTER);
+}
+
+/* sorry for the evil casts */
+
+static void
+gimp_color_managed_iface_init (GimpColorManagedInterface *iface)
+{
+ iface->get_icc_profile = gimp_drawable_get_icc_profile;
+ iface->get_color_profile = gimp_drawable_get_color_profile;
+ iface->profile_changed = gimp_drawable_profile_changed;
+}
+
+static void
+gimp_pickable_iface_init (GimpPickableInterface *iface)
+{
+ iface->get_image = (GimpImage * (*) (GimpPickable *pickable)) gimp_item_get_image;
+ iface->get_format = (const Babl * (*) (GimpPickable *pickable)) gimp_drawable_get_format;
+ iface->get_format_with_alpha = (const Babl * (*) (GimpPickable *pickable)) gimp_drawable_get_format_with_alpha;
+ iface->get_buffer = (GeglBuffer * (*) (GimpPickable *pickable)) gimp_drawable_get_buffer;
+ iface->get_pixel_at = gimp_drawable_get_pixel_at;
+ iface->get_pixel_average = gimp_drawable_get_pixel_average;
+}
+
+static void
+gimp_drawable_dispose (GObject *object)
+{
+ GimpDrawable *drawable = GIMP_DRAWABLE (object);
+
+ if (gimp_drawable_get_floating_sel (drawable))
+ gimp_drawable_detach_floating_sel (drawable);
+
+ G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+gimp_drawable_finalize (GObject *object)
+{
+ GimpDrawable *drawable = GIMP_DRAWABLE (object);
+
+ while (drawable->private->paint_count)
+ gimp_drawable_end_paint (drawable);
+
+ g_clear_object (&drawable->private->buffer);
+
+ gimp_drawable_free_shadow_buffer (drawable);
+
+ g_clear_object (&drawable->private->source_node);
+ g_clear_object (&drawable->private->buffer_source_node);
+ g_clear_object (&drawable->private->filter_stack);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gimp_drawable_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id)
+ {
+ case PROP_BUFFER:
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_drawable_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpDrawable *drawable = GIMP_DRAWABLE (object);
+
+ switch (property_id)
+ {
+ case PROP_BUFFER:
+ g_value_set_object (value, drawable->private->buffer);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static gint64
+gimp_drawable_get_memsize (GimpObject *object,
+ gint64 *gui_size)
+{
+ GimpDrawable *drawable = GIMP_DRAWABLE (object);
+ gint64 memsize = 0;
+
+ memsize += gimp_gegl_buffer_get_memsize (gimp_drawable_get_buffer (drawable));
+ memsize += gimp_gegl_buffer_get_memsize (drawable->private->shadow);
+
+ return memsize + GIMP_OBJECT_CLASS (parent_class)->get_memsize (object,
+ gui_size);
+}
+
+static gboolean
+gimp_drawable_get_size (GimpViewable *viewable,
+ gint *width,
+ gint *height)
+{
+ GimpItem *item = GIMP_ITEM (viewable);
+
+ *width = gimp_item_get_width (item);
+ *height = gimp_item_get_height (item);
+
+ return TRUE;
+}
+
+static void
+gimp_drawable_preview_freeze (GimpViewable *viewable)
+{
+ GimpViewable *parent = gimp_viewable_get_parent (viewable);
+
+ if (! parent && gimp_item_is_attached (GIMP_ITEM (viewable)))
+ parent = GIMP_VIEWABLE (gimp_item_get_image (GIMP_ITEM (viewable)));
+
+ if (parent)
+ gimp_viewable_preview_freeze (parent);
+}
+
+static void
+gimp_drawable_preview_thaw (GimpViewable *viewable)
+{
+ GimpViewable *parent = gimp_viewable_get_parent (viewable);
+
+ if (! parent && gimp_item_is_attached (GIMP_ITEM (viewable)))
+ parent = GIMP_VIEWABLE (gimp_item_get_image (GIMP_ITEM (viewable)));
+
+ if (parent)
+ gimp_viewable_preview_thaw (parent);
+}
+
+static GeglNode *
+gimp_drawable_get_node (GimpFilter *filter)
+{
+ GimpDrawable *drawable = GIMP_DRAWABLE (filter);
+ GeglNode *node;
+ GeglNode *input;
+ GeglNode *output;
+
+ node = GIMP_FILTER_CLASS (parent_class)->get_node (filter);
+
+ g_warn_if_fail (drawable->private->mode_node == NULL);
+
+ drawable->private->mode_node =
+ gegl_node_new_child (node,
+ "operation", "gimp:normal",
+ NULL);
+
+ input = gegl_node_get_input_proxy (node, "input");
+ output = gegl_node_get_output_proxy (node, "output");
+
+ gegl_node_connect_to (input, "output",
+ drawable->private->mode_node, "input");
+ gegl_node_connect_to (drawable->private->mode_node, "output",
+ output, "input");
+
+ return node;
+}
+
+static void
+gimp_drawable_removed (GimpItem *item)
+{
+ GimpDrawable *drawable = GIMP_DRAWABLE (item);
+
+ gimp_drawable_free_shadow_buffer (drawable);
+
+ if (GIMP_ITEM_CLASS (parent_class)->removed)
+ GIMP_ITEM_CLASS (parent_class)->removed (item);
+}
+
+static GimpItem *
+gimp_drawable_duplicate (GimpItem *item,
+ GType new_type)
+{
+ GimpItem *new_item;
+
+ g_return_val_if_fail (g_type_is_a (new_type, GIMP_TYPE_DRAWABLE), NULL);
+
+ new_item = GIMP_ITEM_CLASS (parent_class)->duplicate (item, new_type);
+
+ if (GIMP_IS_DRAWABLE (new_item))
+ {
+ GimpDrawable *drawable = GIMP_DRAWABLE (item);
+ GimpDrawable *new_drawable = GIMP_DRAWABLE (new_item);
+ GeglBuffer *new_buffer;
+
+ new_buffer = gimp_gegl_buffer_dup (gimp_drawable_get_buffer (drawable));
+
+ gimp_drawable_set_buffer (new_drawable, FALSE, NULL, new_buffer);
+ g_object_unref (new_buffer);
+ }
+
+ return new_item;
+}
+
+static void
+gimp_drawable_scale (GimpItem *item,
+ gint new_width,
+ gint new_height,
+ gint new_offset_x,
+ gint new_offset_y,
+ GimpInterpolationType interpolation_type,
+ GimpProgress *progress)
+{
+ GimpDrawable *drawable = GIMP_DRAWABLE (item);
+ GeglBuffer *new_buffer;
+
+ new_buffer = gegl_buffer_new (GEGL_RECTANGLE (0, 0,
+ new_width, new_height),
+ gimp_drawable_get_format (drawable));
+
+ gimp_gegl_apply_scale (gimp_drawable_get_buffer (drawable),
+ progress, C_("undo-type", "Scale"),
+ new_buffer,
+ interpolation_type,
+ ((gdouble) new_width /
+ gimp_item_get_width (item)),
+ ((gdouble) new_height /
+ gimp_item_get_height (item)));
+
+ gimp_drawable_set_buffer_full (drawable, gimp_item_is_attached (item), NULL,
+ new_buffer,
+ GEGL_RECTANGLE (new_offset_x, new_offset_y,
+ 0, 0),
+ TRUE);
+ g_object_unref (new_buffer);
+}
+
+static void
+gimp_drawable_resize (GimpItem *item,
+ GimpContext *context,
+ GimpFillType fill_type,
+ gint new_width,
+ gint new_height,
+ gint offset_x,
+ gint offset_y)
+{
+ GimpDrawable *drawable = GIMP_DRAWABLE (item);
+ GeglBuffer *new_buffer;
+ gint new_offset_x;
+ gint new_offset_y;
+ gint copy_x, copy_y;
+ gint copy_width, copy_height;
+ gboolean intersect;
+
+ /* if the size doesn't change, this is a nop */
+ if (new_width == gimp_item_get_width (item) &&
+ new_height == gimp_item_get_height (item) &&
+ offset_x == 0 &&
+ offset_y == 0)
+ return;
+
+ new_offset_x = gimp_item_get_offset_x (item) - offset_x;
+ new_offset_y = gimp_item_get_offset_y (item) - offset_y;
+
+ intersect = gimp_rectangle_intersect (gimp_item_get_offset_x (item),
+ gimp_item_get_offset_y (item),
+ gimp_item_get_width (item),
+ gimp_item_get_height (item),
+ new_offset_x,
+ new_offset_y,
+ new_width,
+ new_height,
+ &copy_x,
+ &copy_y,
+ &copy_width,
+ &copy_height);
+
+ new_buffer = gegl_buffer_new (GEGL_RECTANGLE (0, 0,
+ new_width, new_height),
+ gimp_drawable_get_format (drawable));
+
+ if (! intersect ||
+ copy_width != new_width ||
+ copy_height != new_height)
+ {
+ /* Clear the new buffer if needed */
+
+ GimpRGB color;
+ GimpPattern *pattern;
+
+ gimp_get_fill_params (context, fill_type, &color, &pattern, NULL);
+ gimp_drawable_fill_buffer (drawable, new_buffer,
+ &color, pattern, 0, 0);
+ }
+
+ if (intersect && copy_width && copy_height)
+ {
+ /* Copy the pixels in the intersection */
+ gimp_gegl_buffer_copy (
+ gimp_drawable_get_buffer (drawable),
+ GEGL_RECTANGLE (copy_x - gimp_item_get_offset_x (item),
+ copy_y - gimp_item_get_offset_y (item),
+ copy_width,
+ copy_height), GEGL_ABYSS_NONE,
+ new_buffer,
+ GEGL_RECTANGLE (copy_x - new_offset_x,
+ copy_y - new_offset_y, 0, 0));
+ }
+
+ gimp_drawable_set_buffer_full (drawable, gimp_item_is_attached (item), NULL,
+ new_buffer,
+ GEGL_RECTANGLE (new_offset_x, new_offset_y,
+ 0, 0),
+ TRUE);
+ g_object_unref (new_buffer);
+}
+
+static void
+gimp_drawable_flip (GimpItem *item,
+ GimpContext *context,
+ GimpOrientationType flip_type,
+ gdouble axis,
+ gboolean clip_result)
+{
+ GimpDrawable *drawable = GIMP_DRAWABLE (item);
+ GeglBuffer *buffer;
+ GimpColorProfile *buffer_profile;
+ gint off_x, off_y;
+ gint new_off_x, new_off_y;
+
+ gimp_item_get_offset (item, &off_x, &off_y);
+
+ buffer = gimp_drawable_transform_buffer_flip (drawable, context,
+ gimp_drawable_get_buffer (drawable),
+ off_x, off_y,
+ flip_type, axis,
+ clip_result,
+ &buffer_profile,
+ &new_off_x, &new_off_y);
+
+ if (buffer)
+ {
+ gimp_drawable_transform_paste (drawable, buffer, buffer_profile,
+ new_off_x, new_off_y, FALSE);
+ g_object_unref (buffer);
+ }
+}
+
+static void
+gimp_drawable_rotate (GimpItem *item,
+ GimpContext *context,
+ GimpRotationType rotate_type,
+ gdouble center_x,
+ gdouble center_y,
+ gboolean clip_result)
+{
+ GimpDrawable *drawable = GIMP_DRAWABLE (item);
+ GeglBuffer *buffer;
+ GimpColorProfile *buffer_profile;
+ gint off_x, off_y;
+ gint new_off_x, new_off_y;
+
+ gimp_item_get_offset (item, &off_x, &off_y);
+
+ buffer = gimp_drawable_transform_buffer_rotate (drawable, context,
+ gimp_drawable_get_buffer (drawable),
+ off_x, off_y,
+ rotate_type, center_x, center_y,
+ clip_result,
+ &buffer_profile,
+ &new_off_x, &new_off_y);
+
+ if (buffer)
+ {
+ gimp_drawable_transform_paste (drawable, buffer, buffer_profile,
+ new_off_x, new_off_y, FALSE);
+ g_object_unref (buffer);
+ }
+}
+
+static void
+gimp_drawable_transform (GimpItem *item,
+ GimpContext *context,
+ const GimpMatrix3 *matrix,
+ GimpTransformDirection direction,
+ GimpInterpolationType interpolation_type,
+ GimpTransformResize clip_result,
+ GimpProgress *progress)
+{
+ GimpDrawable *drawable = GIMP_DRAWABLE (item);
+ GeglBuffer *buffer;
+ GimpColorProfile *buffer_profile;
+ gint off_x, off_y;
+ gint new_off_x, new_off_y;
+
+ gimp_item_get_offset (item, &off_x, &off_y);
+
+ buffer = gimp_drawable_transform_buffer_affine (drawable, context,
+ gimp_drawable_get_buffer (drawable),
+ off_x, off_y,
+ matrix, direction,
+ interpolation_type,
+ clip_result,
+ &buffer_profile,
+ &new_off_x, &new_off_y,
+ progress);
+
+ if (buffer)
+ {
+ gimp_drawable_transform_paste (drawable, buffer, buffer_profile,
+ new_off_x, new_off_y, FALSE);
+ g_object_unref (buffer);
+ }
+}
+
+static const guint8 *
+gimp_drawable_get_icc_profile (GimpColorManaged *managed,
+ gsize *len)
+{
+ GimpColorProfile *profile = gimp_color_managed_get_color_profile (managed);
+
+ return gimp_color_profile_get_icc_profile (profile, len);
+}
+
+static GimpColorProfile *
+gimp_drawable_get_color_profile (GimpColorManaged *managed)
+{
+ const Babl *format = gimp_drawable_get_format (GIMP_DRAWABLE (managed));
+
+ return gimp_babl_format_get_color_profile (format);
+}
+
+static void
+gimp_drawable_profile_changed (GimpColorManaged *managed)
+{
+ gimp_viewable_invalidate_preview (GIMP_VIEWABLE (managed));
+}
+
+static gboolean
+gimp_drawable_get_pixel_at (GimpPickable *pickable,
+ gint x,
+ gint y,
+ const Babl *format,
+ gpointer pixel)
+{
+ GimpDrawable *drawable = GIMP_DRAWABLE (pickable);
+
+ /* do not make this a g_return_if_fail() */
+ if (x < 0 || x >= gimp_item_get_width (GIMP_ITEM (drawable)) ||
+ y < 0 || y >= gimp_item_get_height (GIMP_ITEM (drawable)))
+ return FALSE;
+
+ gegl_buffer_sample (gimp_drawable_get_buffer (drawable),
+ x, y, NULL, pixel, format,
+ GEGL_SAMPLER_NEAREST, GEGL_ABYSS_NONE);
+
+ return TRUE;
+}
+
+static void
+gimp_drawable_get_pixel_average (GimpPickable *pickable,
+ const GeglRectangle *rect,
+ const Babl *format,
+ gpointer pixel)
+{
+ GimpDrawable *drawable = GIMP_DRAWABLE (pickable);
+
+ return gimp_gegl_average_color (gimp_drawable_get_buffer (drawable),
+ rect, TRUE, GEGL_ABYSS_NONE, format, pixel);
+}
+
+static void
+gimp_drawable_real_update (GimpDrawable *drawable,
+ gint x,
+ gint y,
+ gint width,
+ gint height)
+{
+ gimp_viewable_invalidate_preview (GIMP_VIEWABLE (drawable));
+}
+
+static gint64
+gimp_drawable_real_estimate_memsize (GimpDrawable *drawable,
+ GimpComponentType component_type,
+ gint width,
+ gint height)
+{
+ GimpImage *image = gimp_item_get_image (GIMP_ITEM (drawable));
+ gboolean linear = gimp_drawable_get_linear (drawable);
+ const Babl *format;
+
+ format = gimp_image_get_format (image,
+ gimp_drawable_get_base_type (drawable),
+ gimp_babl_precision (component_type, linear),
+ gimp_drawable_has_alpha (drawable));
+
+ return (gint64) babl_format_get_bytes_per_pixel (format) * width * height;
+}
+
+static void
+gimp_drawable_real_update_all (GimpDrawable *drawable)
+{
+ gimp_drawable_update (drawable, 0, 0, -1, -1);
+}
+
+static GimpComponentMask
+gimp_drawable_real_get_active_mask (GimpDrawable *drawable)
+{
+ /* Return all, because that skips the component mask op when painting */
+ return GIMP_COMPONENT_MASK_ALL;
+}
+
+static gboolean
+gimp_drawable_real_supports_alpha (GimpDrawable *drawable)
+{
+ return FALSE;
+}
+
+/* FIXME: this default impl is currently unused because no subclass
+ * chains up. the goal is to handle the almost identical subclass code
+ * here again.
+ */
+static void
+gimp_drawable_real_convert_type (GimpDrawable *drawable,
+ GimpImage *dest_image,
+ const Babl *new_format,
+ GimpColorProfile *dest_profile,
+ GeglDitherMethod layer_dither_type,
+ GeglDitherMethod mask_dither_type,
+ gboolean push_undo,
+ GimpProgress *progress)
+{
+ GeglBuffer *dest_buffer;
+
+ dest_buffer =
+ gegl_buffer_new (GEGL_RECTANGLE (0, 0,
+ gimp_item_get_width (GIMP_ITEM (drawable)),
+ gimp_item_get_height (GIMP_ITEM (drawable))),
+ new_format);
+
+ gimp_gegl_buffer_copy (
+ gimp_drawable_get_buffer (drawable), NULL, GEGL_ABYSS_NONE,
+ dest_buffer, NULL);
+
+ gimp_drawable_set_buffer (drawable, push_undo, NULL, dest_buffer);
+ g_object_unref (dest_buffer);
+}
+
+static GeglBuffer *
+gimp_drawable_real_get_buffer (GimpDrawable *drawable)
+{
+ return drawable->private->buffer;
+}
+
+static void
+gimp_drawable_real_set_buffer (GimpDrawable *drawable,
+ gboolean push_undo,
+ const gchar *undo_desc,
+ GeglBuffer *buffer,
+ const GeglRectangle *bounds)
+{
+ GimpItem *item = GIMP_ITEM (drawable);
+ const Babl *old_format = NULL;
+ gint old_has_alpha = -1;
+
+ g_object_freeze_notify (G_OBJECT (drawable));
+
+ gimp_drawable_invalidate_boundary (drawable);
+
+ if (push_undo)
+ gimp_image_undo_push_drawable_mod (gimp_item_get_image (item), undo_desc,
+ drawable, FALSE);
+
+ if (drawable->private->buffer)
+ {
+ old_format = gimp_drawable_get_format (drawable);
+ old_has_alpha = gimp_drawable_has_alpha (drawable);
+ }
+
+ g_set_object (&drawable->private->buffer, buffer);
+
+ if (drawable->private->buffer_source_node)
+ gegl_node_set (drawable->private->buffer_source_node,
+ "buffer", gimp_drawable_get_buffer (drawable),
+ NULL);
+
+ gimp_item_set_offset (item, bounds->x, bounds->y);
+ gimp_item_set_size (item,
+ bounds->width ? bounds->width :
+ gegl_buffer_get_width (buffer),
+ bounds->height ? bounds->height :
+ gegl_buffer_get_height (buffer));
+
+ gimp_drawable_update_bounding_box (drawable);
+
+ if (gimp_drawable_get_format (drawable) != old_format)
+ gimp_drawable_format_changed (drawable);
+
+ if (gimp_drawable_has_alpha (drawable) != old_has_alpha)
+ gimp_drawable_alpha_changed (drawable);
+
+ g_object_notify (G_OBJECT (drawable), "buffer");
+
+ g_object_thaw_notify (G_OBJECT (drawable));
+}
+
+static GeglRectangle
+gimp_drawable_real_get_bounding_box (GimpDrawable *drawable)
+{
+ return gegl_node_get_bounding_box (gimp_drawable_get_source_node (drawable));
+}
+
+static void
+gimp_drawable_real_push_undo (GimpDrawable *drawable,
+ const gchar *undo_desc,
+ GeglBuffer *buffer,
+ gint x,
+ gint y,
+ gint width,
+ gint height)
+{
+ GimpImage *image;
+
+ if (! buffer)
+ {
+ GeglBuffer *drawable_buffer = gimp_drawable_get_buffer (drawable);
+ GeglRectangle drawable_rect;
+
+ gegl_rectangle_align_to_buffer (
+ &drawable_rect,
+ GEGL_RECTANGLE (x, y, width, height),
+ drawable_buffer,
+ GEGL_RECTANGLE_ALIGNMENT_SUPERSET);
+
+ x = drawable_rect.x;
+ y = drawable_rect.y;
+ width = drawable_rect.width;
+ height = drawable_rect.height;
+
+ buffer = gegl_buffer_new (GEGL_RECTANGLE (0, 0, width, height),
+ gimp_drawable_get_format (drawable));
+
+ gimp_gegl_buffer_copy (
+ drawable_buffer,
+ &drawable_rect, GEGL_ABYSS_NONE,
+ buffer,
+ GEGL_RECTANGLE (0, 0, 0, 0));
+ }
+ else
+ {
+ g_object_ref (buffer);
+ }
+
+ image = gimp_item_get_image (GIMP_ITEM (drawable));
+
+ gimp_image_undo_push_drawable (image,
+ undo_desc, drawable,
+ buffer, x, y);
+
+ g_object_unref (buffer);
+}
+
+static void
+gimp_drawable_real_swap_pixels (GimpDrawable *drawable,
+ GeglBuffer *buffer,
+ gint x,
+ gint y)
+{
+ GeglBuffer *tmp;
+ gint width = gegl_buffer_get_width (buffer);
+ gint height = gegl_buffer_get_height (buffer);
+
+ tmp = gimp_gegl_buffer_dup (buffer);
+
+ gimp_gegl_buffer_copy (gimp_drawable_get_buffer (drawable),
+ GEGL_RECTANGLE (x, y, width, height), GEGL_ABYSS_NONE,
+ buffer,
+ GEGL_RECTANGLE (0, 0, 0, 0));
+ gimp_gegl_buffer_copy (tmp,
+ GEGL_RECTANGLE (0, 0, width, height), GEGL_ABYSS_NONE,
+ gimp_drawable_get_buffer (drawable),
+ GEGL_RECTANGLE (x, y, 0, 0));
+
+ g_object_unref (tmp);
+
+ gimp_drawable_update (drawable, x, y, width, height);
+}
+
+static GeglNode *
+gimp_drawable_real_get_source_node (GimpDrawable *drawable)
+{
+ g_warn_if_fail (drawable->private->buffer_source_node == NULL);
+
+ drawable->private->buffer_source_node =
+ gegl_node_new_child (NULL,
+ "operation", "gimp:buffer-source-validate",
+ "buffer", gimp_drawable_get_buffer (drawable),
+ NULL);
+
+ return g_object_ref (drawable->private->buffer_source_node);
+}
+
+static void
+gimp_drawable_format_changed (GimpDrawable *drawable)
+{
+ g_signal_emit (drawable, gimp_drawable_signals[FORMAT_CHANGED], 0);
+}
+
+static void
+gimp_drawable_alpha_changed (GimpDrawable *drawable)
+{
+ g_signal_emit (drawable, gimp_drawable_signals[ALPHA_CHANGED], 0);
+}
+
+
+/* public functions */
+
+GimpDrawable *
+gimp_drawable_new (GType type,
+ GimpImage *image,
+ const gchar *name,
+ gint offset_x,
+ gint offset_y,
+ gint width,
+ gint height,
+ const Babl *format)
+{
+ GimpDrawable *drawable;
+ GeglBuffer *buffer;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+ g_return_val_if_fail (g_type_is_a (type, GIMP_TYPE_DRAWABLE), NULL);
+ g_return_val_if_fail (width > 0 && height > 0, NULL);
+ g_return_val_if_fail (format != NULL, NULL);
+
+ drawable = GIMP_DRAWABLE (gimp_item_new (type,
+ image, name,
+ offset_x, offset_y,
+ width, height));
+
+ buffer = gegl_buffer_new (GEGL_RECTANGLE (0, 0, width, height), format);
+
+ gimp_drawable_set_buffer (drawable, FALSE, NULL, buffer);
+ g_object_unref (buffer);
+
+ return drawable;
+}
+
+gint64
+gimp_drawable_estimate_memsize (GimpDrawable *drawable,
+ GimpComponentType component_type,
+ gint width,
+ gint height)
+{
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), 0);
+
+ return GIMP_DRAWABLE_GET_CLASS (drawable)->estimate_memsize (drawable,
+ component_type,
+ width, height);
+}
+
+void
+gimp_drawable_update (GimpDrawable *drawable,
+ gint x,
+ gint y,
+ gint width,
+ gint height)
+{
+ g_return_if_fail (GIMP_IS_DRAWABLE (drawable));
+
+ if (width < 0)
+ {
+ GeglRectangle bounding_box;
+
+ bounding_box = gimp_drawable_get_bounding_box (drawable);
+
+ x = bounding_box.x;
+ width = bounding_box.width;
+ }
+
+ if (height < 0)
+ {
+ GeglRectangle bounding_box;
+
+ bounding_box = gimp_drawable_get_bounding_box (drawable);
+
+ y = bounding_box.y;
+ height = bounding_box.height;
+ }
+
+ if (drawable->private->paint_count == 0)
+ {
+ g_signal_emit (drawable, gimp_drawable_signals[UPDATE], 0,
+ x, y, width, height);
+ }
+ else
+ {
+ GeglRectangle rect;
+
+ if (gegl_rectangle_intersect (
+ &rect,
+ GEGL_RECTANGLE (x, y, width, height),
+ GEGL_RECTANGLE (0, 0,
+ gimp_item_get_width (GIMP_ITEM (drawable)),
+ gimp_item_get_height (GIMP_ITEM (drawable)))))
+ {
+ GeglRectangle aligned_rect;
+
+ gegl_rectangle_align_to_buffer (&aligned_rect, &rect,
+ gimp_drawable_get_buffer (drawable),
+ GEGL_RECTANGLE_ALIGNMENT_SUPERSET);
+
+ if (drawable->private->paint_copy_region)
+ {
+ cairo_region_union_rectangle (
+ drawable->private->paint_copy_region,
+ (const cairo_rectangle_int_t *) &aligned_rect);
+ }
+ else
+ {
+ drawable->private->paint_copy_region =
+ cairo_region_create_rectangle (
+ (const cairo_rectangle_int_t *) &aligned_rect);
+ }
+
+ gegl_rectangle_align (&aligned_rect, &rect,
+ GEGL_RECTANGLE (0, 0,
+ PAINT_UPDATE_CHUNK_WIDTH,
+ PAINT_UPDATE_CHUNK_HEIGHT),
+ GEGL_RECTANGLE_ALIGNMENT_SUPERSET);
+
+ if (drawable->private->paint_update_region)
+ {
+ cairo_region_union_rectangle (
+ drawable->private->paint_update_region,
+ (const cairo_rectangle_int_t *) &aligned_rect);
+ }
+ else
+ {
+ drawable->private->paint_update_region =
+ cairo_region_create_rectangle (
+ (const cairo_rectangle_int_t *) &aligned_rect);
+ }
+ }
+ }
+}
+
+void
+gimp_drawable_update_all (GimpDrawable *drawable)
+{
+ g_return_if_fail (GIMP_IS_DRAWABLE (drawable));
+
+ GIMP_DRAWABLE_GET_CLASS (drawable)->update_all (drawable);
+}
+
+void
+gimp_drawable_invalidate_boundary (GimpDrawable *drawable)
+{
+ GimpDrawableClass *drawable_class;
+
+ g_return_if_fail (GIMP_IS_DRAWABLE (drawable));
+
+ drawable_class = GIMP_DRAWABLE_GET_CLASS (drawable);
+
+ if (drawable_class->invalidate_boundary)
+ drawable_class->invalidate_boundary (drawable);
+}
+
+void
+gimp_drawable_get_active_components (GimpDrawable *drawable,
+ gboolean *active)
+{
+ GimpDrawableClass *drawable_class;
+
+ g_return_if_fail (GIMP_IS_DRAWABLE (drawable));
+ g_return_if_fail (active != NULL);
+
+ drawable_class = GIMP_DRAWABLE_GET_CLASS (drawable);
+
+ if (drawable_class->get_active_components)
+ drawable_class->get_active_components (drawable, active);
+}
+
+GimpComponentMask
+gimp_drawable_get_active_mask (GimpDrawable *drawable)
+{
+ GimpComponentMask mask;
+
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), 0);
+
+ mask = GIMP_DRAWABLE_GET_CLASS (drawable)->get_active_mask (drawable);
+
+ /* if the drawable doesn't have an alpha channel, the value of the mask's
+ * alpha-bit doesn't matter, however, we'd like to have a fully-clear or
+ * fully-set mask whenever possible, since it allows us to skip component
+ * masking altogether. we therefore set or clear the alpha bit, depending on
+ * the state of the other bits, so that it never gets in the way of a uniform
+ * mask.
+ */
+ if (! gimp_drawable_has_alpha (drawable))
+ {
+ if (mask & ~GIMP_COMPONENT_MASK_ALPHA)
+ mask |= GIMP_COMPONENT_MASK_ALPHA;
+ else
+ mask &= ~GIMP_COMPONENT_MASK_ALPHA;
+ }
+
+ return mask;
+}
+
+gboolean
+gimp_drawable_supports_alpha (GimpDrawable *drawable)
+{
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), FALSE);
+
+ return GIMP_DRAWABLE_GET_CLASS (drawable)->supports_alpha (drawable);
+}
+
+void
+gimp_drawable_convert_type (GimpDrawable *drawable,
+ GimpImage *dest_image,
+ GimpImageBaseType new_base_type,
+ GimpPrecision new_precision,
+ gboolean new_has_alpha,
+ GimpColorProfile *dest_profile,
+ GeglDitherMethod layer_dither_type,
+ GeglDitherMethod mask_dither_type,
+ gboolean push_undo,
+ GimpProgress *progress)
+{
+ const Babl *old_format;
+ const Babl *new_format;
+ gint old_bits;
+ gint new_bits;
+
+ g_return_if_fail (GIMP_IS_DRAWABLE (drawable));
+ g_return_if_fail (GIMP_IS_IMAGE (dest_image));
+ g_return_if_fail (new_base_type != gimp_drawable_get_base_type (drawable) ||
+ new_precision != gimp_drawable_get_precision (drawable) ||
+ new_has_alpha != gimp_drawable_has_alpha (drawable) ||
+ dest_profile);
+ g_return_if_fail (dest_profile == NULL || GIMP_IS_COLOR_PROFILE (dest_profile));
+ g_return_if_fail (progress == NULL || GIMP_IS_PROGRESS (progress));
+
+ if (! gimp_item_is_attached (GIMP_ITEM (drawable)))
+ push_undo = FALSE;
+
+ old_format = gimp_drawable_get_format (drawable);
+ new_format = gimp_image_get_format (dest_image,
+ new_base_type,
+ new_precision,
+ new_has_alpha);
+
+ old_bits = (babl_format_get_bytes_per_pixel (old_format) * 8 /
+ babl_format_get_n_components (old_format));
+ new_bits = (babl_format_get_bytes_per_pixel (new_format) * 8 /
+ babl_format_get_n_components (new_format));
+
+ if (old_bits <= new_bits || new_bits > 16)
+ {
+ /* don't dither if we are converting to a higher bit depth,
+ * or to more than 16 bits (gegl:dither only does
+ * 16 bits).
+ */
+ layer_dither_type = GEGL_DITHER_NONE;
+ mask_dither_type = GEGL_DITHER_NONE;
+ }
+
+ GIMP_DRAWABLE_GET_CLASS (drawable)->convert_type (drawable, dest_image,
+ new_format,
+ dest_profile,
+ layer_dither_type,
+ mask_dither_type,
+ push_undo,
+ progress);
+
+ if (progress)
+ gimp_progress_set_value (progress, 1.0);
+}
+
+void
+gimp_drawable_apply_buffer (GimpDrawable *drawable,
+ GeglBuffer *buffer,
+ const GeglRectangle *buffer_region,
+ gboolean push_undo,
+ const gchar *undo_desc,
+ gdouble opacity,
+ GimpLayerMode mode,
+ GimpLayerColorSpace blend_space,
+ GimpLayerColorSpace composite_space,
+ GimpLayerCompositeMode composite_mode,
+ GeglBuffer *base_buffer,
+ gint base_x,
+ gint base_y)
+{
+ g_return_if_fail (GIMP_IS_DRAWABLE (drawable));
+ g_return_if_fail (gimp_item_is_attached (GIMP_ITEM (drawable)));
+ g_return_if_fail (GEGL_IS_BUFFER (buffer));
+ g_return_if_fail (buffer_region != NULL);
+ g_return_if_fail (base_buffer == NULL || GEGL_IS_BUFFER (base_buffer));
+
+ GIMP_DRAWABLE_GET_CLASS (drawable)->apply_buffer (drawable, buffer,
+ buffer_region,
+ push_undo, undo_desc,
+ opacity, mode,
+ blend_space,
+ composite_space,
+ composite_mode,
+ base_buffer,
+ base_x, base_y);
+}
+
+GeglBuffer *
+gimp_drawable_get_buffer (GimpDrawable *drawable)
+{
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), NULL);
+
+ if (drawable->private->paint_count == 0)
+ return GIMP_DRAWABLE_GET_CLASS (drawable)->get_buffer (drawable);
+ else
+ return drawable->private->paint_buffer;
+}
+
+void
+gimp_drawable_set_buffer (GimpDrawable *drawable,
+ gboolean push_undo,
+ const gchar *undo_desc,
+ GeglBuffer *buffer)
+{
+ g_return_if_fail (GIMP_IS_DRAWABLE (drawable));
+ g_return_if_fail (GEGL_IS_BUFFER (buffer));
+
+ if (! gimp_item_is_attached (GIMP_ITEM (drawable)))
+ push_undo = FALSE;
+
+ gimp_drawable_set_buffer_full (drawable, push_undo, undo_desc, buffer, NULL,
+ TRUE);
+}
+
+void
+gimp_drawable_set_buffer_full (GimpDrawable *drawable,
+ gboolean push_undo,
+ const gchar *undo_desc,
+ GeglBuffer *buffer,
+ const GeglRectangle *bounds,
+ gboolean update)
+{
+ GimpItem *item;
+ GeglRectangle curr_bounds;
+
+ g_return_if_fail (GIMP_IS_DRAWABLE (drawable));
+ g_return_if_fail (GEGL_IS_BUFFER (buffer));
+
+ item = GIMP_ITEM (drawable);
+
+ if (! gimp_item_is_attached (GIMP_ITEM (drawable)))
+ push_undo = FALSE;
+
+ if (! bounds)
+ {
+ gimp_item_get_offset (GIMP_ITEM (drawable),
+ &curr_bounds.x, &curr_bounds.y);
+
+ curr_bounds.width = 0;
+ curr_bounds.height = 0;
+
+ bounds = &curr_bounds;
+ }
+
+ if (update && gimp_drawable_get_buffer (drawable))
+ {
+ GeglBuffer *old_buffer = gimp_drawable_get_buffer (drawable);
+ GeglRectangle old_extent;
+ GeglRectangle new_extent;
+
+ old_extent = *gegl_buffer_get_extent (old_buffer);
+ old_extent.x += gimp_item_get_offset_x (item);
+ old_extent.y += gimp_item_get_offset_x (item);
+
+ new_extent = *gegl_buffer_get_extent (buffer);
+ new_extent.x += bounds->x;
+ new_extent.y += bounds->y;
+
+ if (! gegl_rectangle_equal (&old_extent, &new_extent))
+ gimp_drawable_update (drawable, 0, 0, -1, -1);
+ }
+
+ g_object_freeze_notify (G_OBJECT (drawable));
+
+ GIMP_DRAWABLE_GET_CLASS (drawable)->set_buffer (drawable,
+ push_undo, undo_desc,
+ buffer, bounds);
+
+ g_object_thaw_notify (G_OBJECT (drawable));
+
+ if (update)
+ gimp_drawable_update (drawable, 0, 0, -1, -1);
+}
+
+void
+gimp_drawable_steal_buffer (GimpDrawable *drawable,
+ GimpDrawable *src_drawable)
+{
+ GeglBuffer *buffer;
+ GeglBuffer *replacement_buffer;
+
+ g_return_if_fail (GIMP_IS_DRAWABLE (drawable));
+ g_return_if_fail (GIMP_IS_DRAWABLE (src_drawable));
+
+ buffer = gimp_drawable_get_buffer (src_drawable);
+
+ g_return_if_fail (buffer != NULL);
+
+ g_object_ref (buffer);
+
+ replacement_buffer = gegl_buffer_new (GEGL_RECTANGLE (0, 0, 1, 1),
+ gegl_buffer_get_format (buffer));
+
+ gimp_drawable_set_buffer (src_drawable, FALSE, NULL, replacement_buffer);
+ gimp_drawable_set_buffer (drawable, FALSE, NULL, buffer);
+
+ g_object_unref (replacement_buffer);
+ g_object_unref (buffer);
+}
+
+GeglNode *
+gimp_drawable_get_source_node (GimpDrawable *drawable)
+{
+ GeglNode *input;
+ GeglNode *source;
+ GeglNode *filter;
+ GeglNode *output;
+
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), NULL);
+
+ if (drawable->private->source_node)
+ return drawable->private->source_node;
+
+ drawable->private->source_node = gegl_node_new ();
+
+ input = gegl_node_get_input_proxy (drawable->private->source_node, "input");
+
+ source = GIMP_DRAWABLE_GET_CLASS (drawable)->get_source_node (drawable);
+
+ gegl_node_add_child (drawable->private->source_node, source);
+
+ g_object_unref (source);
+
+ if (gegl_node_has_pad (source, "input"))
+ {
+ gegl_node_connect_to (input, "output",
+ source, "input");
+ }
+
+ filter = gimp_filter_stack_get_graph (GIMP_FILTER_STACK (drawable->private->filter_stack));
+
+ gegl_node_add_child (drawable->private->source_node, filter);
+
+ gegl_node_connect_to (source, "output",
+ filter, "input");
+
+ output = gegl_node_get_output_proxy (drawable->private->source_node, "output");
+
+ gegl_node_connect_to (filter, "output",
+ output, "input");
+
+ if (gimp_drawable_get_floating_sel (drawable))
+ _gimp_drawable_add_floating_sel_filter (drawable);
+
+ return drawable->private->source_node;
+}
+
+GeglNode *
+gimp_drawable_get_mode_node (GimpDrawable *drawable)
+{
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), NULL);
+
+ if (! drawable->private->mode_node)
+ gimp_filter_get_node (GIMP_FILTER (drawable));
+
+ return drawable->private->mode_node;
+}
+
+GeglRectangle
+gimp_drawable_get_bounding_box (GimpDrawable *drawable)
+{
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable),
+ *GEGL_RECTANGLE (0, 0, 0, 0));
+
+ if (gegl_rectangle_is_empty (&drawable->private->bounding_box))
+ gimp_drawable_update_bounding_box (drawable);
+
+ return drawable->private->bounding_box;
+}
+
+gboolean
+gimp_drawable_update_bounding_box (GimpDrawable *drawable)
+{
+ GeglRectangle bounding_box;
+
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), FALSE);
+
+ bounding_box =
+ GIMP_DRAWABLE_GET_CLASS (drawable)->get_bounding_box (drawable);
+
+ if (! gegl_rectangle_equal (&bounding_box, &drawable->private->bounding_box))
+ {
+ GeglRectangle old_bounding_box = drawable->private->bounding_box;
+ GeglRectangle diff_rects[4];
+ gint n_diff_rects;
+ gint i;
+
+ n_diff_rects = gegl_rectangle_subtract (diff_rects,
+ &old_bounding_box,
+ &bounding_box);
+
+ for (i = 0; i < n_diff_rects; i++)
+ {
+ gimp_drawable_update (drawable,
+ diff_rects[i].x,
+ diff_rects[i].y,
+ diff_rects[i].width,
+ diff_rects[i].height);
+ }
+
+ drawable->private->bounding_box = bounding_box;
+
+ g_signal_emit (drawable, gimp_drawable_signals[BOUNDING_BOX_CHANGED], 0);
+
+ n_diff_rects = gegl_rectangle_subtract (diff_rects,
+ &bounding_box,
+ &old_bounding_box);
+
+ for (i = 0; i < n_diff_rects; i++)
+ {
+ gimp_drawable_update (drawable,
+ diff_rects[i].x,
+ diff_rects[i].y,
+ diff_rects[i].width,
+ diff_rects[i].height);
+ }
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+void
+gimp_drawable_swap_pixels (GimpDrawable *drawable,
+ GeglBuffer *buffer,
+ gint x,
+ gint y)
+{
+ g_return_if_fail (GIMP_IS_DRAWABLE (drawable));
+ g_return_if_fail (GEGL_IS_BUFFER (buffer));
+
+ GIMP_DRAWABLE_GET_CLASS (drawable)->swap_pixels (drawable, buffer, x, y);
+}
+
+void
+gimp_drawable_push_undo (GimpDrawable *drawable,
+ const gchar *undo_desc,
+ GeglBuffer *buffer,
+ gint x,
+ gint y,
+ gint width,
+ gint height)
+{
+ GimpItem *item;
+
+ g_return_if_fail (GIMP_IS_DRAWABLE (drawable));
+ g_return_if_fail (buffer == NULL || GEGL_IS_BUFFER (buffer));
+
+ item = GIMP_ITEM (drawable);
+
+ g_return_if_fail (gimp_item_is_attached (item));
+
+ if (! buffer &&
+ ! gimp_rectangle_intersect (x, y,
+ width, height,
+ 0, 0,
+ gimp_item_get_width (item),
+ gimp_item_get_height (item),
+ &x, &y, &width, &height))
+ {
+ g_warning ("%s: tried to push empty region", G_STRFUNC);
+ return;
+ }
+
+ GIMP_DRAWABLE_GET_CLASS (drawable)->push_undo (drawable, undo_desc,
+ buffer,
+ x, y, width, height);
+}
+
+const Babl *
+gimp_drawable_get_format (GimpDrawable *drawable)
+{
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), NULL);
+
+ return gegl_buffer_get_format (drawable->private->buffer);
+}
+
+const Babl *
+gimp_drawable_get_format_with_alpha (GimpDrawable *drawable)
+{
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), NULL);
+
+ return gimp_image_get_format (gimp_item_get_image (GIMP_ITEM (drawable)),
+ gimp_drawable_get_base_type (drawable),
+ gimp_drawable_get_precision (drawable),
+ TRUE);
+}
+
+const Babl *
+gimp_drawable_get_format_without_alpha (GimpDrawable *drawable)
+{
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), NULL);
+
+ return gimp_image_get_format (gimp_item_get_image (GIMP_ITEM (drawable)),
+ gimp_drawable_get_base_type (drawable),
+ gimp_drawable_get_precision (drawable),
+ FALSE);
+}
+
+gboolean
+gimp_drawable_get_linear (GimpDrawable *drawable)
+{
+ const Babl *format;
+
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), FALSE);
+
+ format = gegl_buffer_get_format (drawable->private->buffer);
+
+ return gimp_babl_format_get_linear (format);
+}
+
+gboolean
+gimp_drawable_has_alpha (GimpDrawable *drawable)
+{
+ const Babl *format;
+
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), FALSE);
+
+ format = gegl_buffer_get_format (drawable->private->buffer);
+
+ return babl_format_has_alpha (format);
+}
+
+GimpImageBaseType
+gimp_drawable_get_base_type (GimpDrawable *drawable)
+{
+ const Babl *format;
+
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), -1);
+
+ format = gegl_buffer_get_format (drawable->private->buffer);
+
+ return gimp_babl_format_get_base_type (format);
+}
+
+GimpComponentType
+gimp_drawable_get_component_type (GimpDrawable *drawable)
+{
+ const Babl *format;
+
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), -1);
+
+ format = gegl_buffer_get_format (drawable->private->buffer);
+
+ return gimp_babl_format_get_component_type (format);
+}
+
+GimpPrecision
+gimp_drawable_get_precision (GimpDrawable *drawable)
+{
+ const Babl *format;
+
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), -1);
+
+ format = gegl_buffer_get_format (drawable->private->buffer);
+
+ return gimp_babl_format_get_precision (format);
+}
+
+gboolean
+gimp_drawable_is_rgb (GimpDrawable *drawable)
+{
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), FALSE);
+
+ return (gimp_drawable_get_base_type (drawable) == GIMP_RGB);
+}
+
+gboolean
+gimp_drawable_is_gray (GimpDrawable *drawable)
+{
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), FALSE);
+
+ return (gimp_drawable_get_base_type (drawable) == GIMP_GRAY);
+}
+
+gboolean
+gimp_drawable_is_indexed (GimpDrawable *drawable)
+{
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), FALSE);
+
+ return (gimp_drawable_get_base_type (drawable) == GIMP_INDEXED);
+}
+
+const Babl *
+gimp_drawable_get_component_format (GimpDrawable *drawable,
+ GimpChannelType channel)
+{
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), NULL);
+
+ switch (channel)
+ {
+ case GIMP_CHANNEL_RED:
+ return gimp_babl_component_format (GIMP_RGB,
+ gimp_drawable_get_precision (drawable),
+ RED);
+
+ case GIMP_CHANNEL_GREEN:
+ return gimp_babl_component_format (GIMP_RGB,
+ gimp_drawable_get_precision (drawable),
+ GREEN);
+
+ case GIMP_CHANNEL_BLUE:
+ return gimp_babl_component_format (GIMP_RGB,
+ gimp_drawable_get_precision (drawable),
+ BLUE);
+
+ case GIMP_CHANNEL_ALPHA:
+ return gimp_babl_component_format (GIMP_RGB,
+ gimp_drawable_get_precision (drawable),
+ ALPHA);
+
+ case GIMP_CHANNEL_GRAY:
+ return gimp_babl_component_format (GIMP_GRAY,
+ gimp_drawable_get_precision (drawable),
+ GRAY);
+
+ case GIMP_CHANNEL_INDEXED:
+ return babl_format ("Y u8"); /* will extract grayscale, the best
+ * we can do here */
+ }
+
+ return NULL;
+}
+
+gint
+gimp_drawable_get_component_index (GimpDrawable *drawable,
+ GimpChannelType channel)
+{
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), -1);
+
+ switch (channel)
+ {
+ case GIMP_CHANNEL_RED: return RED;
+ case GIMP_CHANNEL_GREEN: return GREEN;
+ case GIMP_CHANNEL_BLUE: return BLUE;
+ case GIMP_CHANNEL_GRAY: return GRAY;
+ case GIMP_CHANNEL_INDEXED: return INDEXED;
+ case GIMP_CHANNEL_ALPHA:
+ switch (gimp_drawable_get_base_type (drawable))
+ {
+ case GIMP_RGB: return ALPHA;
+ case GIMP_GRAY: return ALPHA_G;
+ case GIMP_INDEXED: return ALPHA_I;
+ }
+ }
+
+ return -1;
+}
+
+const guchar *
+gimp_drawable_get_colormap (GimpDrawable *drawable)
+{
+ GimpImage *image;
+
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), NULL);
+
+ image = gimp_item_get_image (GIMP_ITEM (drawable));
+
+ return image ? gimp_image_get_colormap (image) : NULL;
+}
+
+void
+gimp_drawable_start_paint (GimpDrawable *drawable)
+{
+ g_return_if_fail (GIMP_IS_DRAWABLE (drawable));
+
+ if (drawable->private->paint_count == 0)
+ {
+ GeglBuffer *buffer = gimp_drawable_get_buffer (drawable);
+
+ g_return_if_fail (buffer != NULL);
+ g_return_if_fail (drawable->private->paint_buffer == NULL);
+ g_return_if_fail (drawable->private->paint_copy_region == NULL);
+ g_return_if_fail (drawable->private->paint_update_region == NULL);
+
+ drawable->private->paint_buffer = gimp_gegl_buffer_dup (buffer);
+ }
+
+ drawable->private->paint_count++;
+}
+
+gboolean
+gimp_drawable_end_paint (GimpDrawable *drawable)
+{
+ gboolean result = FALSE;
+
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), FALSE);
+ g_return_val_if_fail (drawable->private->paint_count > 0, FALSE);
+
+ if (drawable->private->paint_count == 1)
+ {
+ result = gimp_drawable_flush_paint (drawable);
+
+ g_clear_object (&drawable->private->paint_buffer);
+ }
+
+ drawable->private->paint_count--;
+
+ return result;
+}
+
+gboolean
+gimp_drawable_flush_paint (GimpDrawable *drawable)
+{
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), FALSE);
+ g_return_val_if_fail (drawable->private->paint_count > 0, FALSE);
+
+ if (drawable->private->paint_copy_region)
+ {
+ GeglBuffer *buffer;
+ gint n_rects;
+ gint i;
+
+ buffer = GIMP_DRAWABLE_GET_CLASS (drawable)->get_buffer (drawable);
+
+ g_return_val_if_fail (buffer != NULL, FALSE);
+ g_return_val_if_fail (drawable->private->paint_buffer != NULL, FALSE);
+
+ n_rects = cairo_region_num_rectangles (
+ drawable->private->paint_copy_region);
+
+ for (i = 0; i < n_rects; i++)
+ {
+ GeglRectangle rect;
+
+ cairo_region_get_rectangle (drawable->private->paint_copy_region,
+ i, (cairo_rectangle_int_t *) &rect);
+
+ gimp_gegl_buffer_copy (
+ drawable->private->paint_buffer, &rect, GEGL_ABYSS_NONE,
+ buffer, NULL);
+ }
+
+ g_clear_pointer (&drawable->private->paint_copy_region,
+ cairo_region_destroy);
+
+ n_rects = cairo_region_num_rectangles (
+ drawable->private->paint_update_region);
+
+ for (i = 0; i < n_rects; i++)
+ {
+ GeglRectangle rect;
+
+ cairo_region_get_rectangle (drawable->private->paint_update_region,
+ i, (cairo_rectangle_int_t *) &rect);
+
+ g_signal_emit (drawable, gimp_drawable_signals[UPDATE], 0,
+ rect.x, rect.y, rect.width, rect.height);
+ }
+
+ g_clear_pointer (&drawable->private->paint_update_region,
+ cairo_region_destroy);
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+gboolean
+gimp_drawable_is_painting (GimpDrawable *drawable)
+{
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), FALSE);
+
+ return drawable->private->paint_count > 0;
+}
diff --git a/app/core/gimpdrawable.h b/app/core/gimpdrawable.h
new file mode 100644
index 0000000..73c4283
--- /dev/null
+++ b/app/core/gimpdrawable.h
@@ -0,0 +1,227 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_DRAWABLE_H__
+#define __GIMP_DRAWABLE_H__
+
+
+#include "gimpitem.h"
+
+
+#define GIMP_TYPE_DRAWABLE (gimp_drawable_get_type ())
+#define GIMP_DRAWABLE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_DRAWABLE, GimpDrawable))
+#define GIMP_DRAWABLE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_DRAWABLE, GimpDrawableClass))
+#define GIMP_IS_DRAWABLE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_DRAWABLE))
+#define GIMP_IS_DRAWABLE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_DRAWABLE))
+#define GIMP_DRAWABLE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_DRAWABLE, GimpDrawableClass))
+
+
+typedef struct _GimpDrawablePrivate GimpDrawablePrivate;
+typedef struct _GimpDrawableClass GimpDrawableClass;
+
+struct _GimpDrawable
+{
+ GimpItem parent_instance;
+
+ GimpDrawablePrivate *private;
+};
+
+struct _GimpDrawableClass
+{
+ GimpItemClass parent_class;
+
+ /* signals */
+ void (* update) (GimpDrawable *drawable,
+ gint x,
+ gint y,
+ gint width,
+ gint height);
+ void (* format_changed) (GimpDrawable *drawable);
+ void (* alpha_changed) (GimpDrawable *drawable);
+ void (* bounding_box_changed) (GimpDrawable *drawable);
+
+ /* virtual functions */
+ gint64 (* estimate_memsize) (GimpDrawable *drawable,
+ GimpComponentType component_type,
+ gint width,
+ gint height);
+ void (* update_all) (GimpDrawable *drawable);
+ void (* invalidate_boundary) (GimpDrawable *drawable);
+ void (* get_active_components) (GimpDrawable *drawable,
+ gboolean *active);
+ GimpComponentMask (* get_active_mask) (GimpDrawable *drawable);
+ gboolean (* supports_alpha) (GimpDrawable *drawable);
+ void (* convert_type) (GimpDrawable *drawable,
+ GimpImage *dest_image,
+ const Babl *new_format,
+ GimpColorProfile *dest_profile,
+ GeglDitherMethod layer_dither_type,
+ GeglDitherMethod mask_dither_type,
+ gboolean push_undo,
+ GimpProgress *progress);
+ void (* apply_buffer) (GimpDrawable *drawable,
+ GeglBuffer *buffer,
+ const GeglRectangle *buffer_region,
+ gboolean push_undo,
+ const gchar *undo_desc,
+ gdouble opacity,
+ GimpLayerMode mode,
+ GimpLayerColorSpace blend_space,
+ GimpLayerColorSpace composite_space,
+ GimpLayerCompositeMode composite_mode,
+ GeglBuffer *base_buffer,
+ gint base_x,
+ gint base_y);
+ GeglBuffer * (* get_buffer) (GimpDrawable *drawable);
+ void (* set_buffer) (GimpDrawable *drawable,
+ gboolean push_undo,
+ const gchar *undo_desc,
+ GeglBuffer *buffer,
+ const GeglRectangle *bounds);
+ GeglRectangle (* get_bounding_box) (GimpDrawable *drawable);
+ void (* push_undo) (GimpDrawable *drawable,
+ const gchar *undo_desc,
+ GeglBuffer *buffer,
+ gint x,
+ gint y,
+ gint width,
+ gint height);
+ void (* swap_pixels) (GimpDrawable *drawable,
+ GeglBuffer *buffer,
+ gint x,
+ gint y);
+ GeglNode * (* get_source_node) (GimpDrawable *drawable);
+};
+
+
+GType gimp_drawable_get_type (void) G_GNUC_CONST;
+
+GimpDrawable * gimp_drawable_new (GType type,
+ GimpImage *image,
+ const gchar *name,
+ gint offset_x,
+ gint offset_y,
+ gint width,
+ gint height,
+ const Babl *format);
+
+gint64 gimp_drawable_estimate_memsize (GimpDrawable *drawable,
+ GimpComponentType component_type,
+ gint width,
+ gint height);
+
+void gimp_drawable_update (GimpDrawable *drawable,
+ gint x,
+ gint y,
+ gint width,
+ gint height);
+void gimp_drawable_update_all (GimpDrawable *drawable);
+
+void gimp_drawable_invalidate_boundary (GimpDrawable *drawable);
+void gimp_drawable_get_active_components (GimpDrawable *drawable,
+ gboolean *active);
+GimpComponentMask gimp_drawable_get_active_mask (GimpDrawable *drawable);
+
+gboolean gimp_drawable_supports_alpha (GimpDrawable *drawable);
+
+void gimp_drawable_convert_type (GimpDrawable *drawable,
+ GimpImage *dest_image,
+ GimpImageBaseType new_base_type,
+ GimpPrecision new_precision,
+ gboolean new_has_alpha,
+ GimpColorProfile *dest_profile,
+ GeglDitherMethod layer_dither_type,
+ GeglDitherMethod mask_dither_type,
+ gboolean push_undo,
+ GimpProgress *progress);
+
+void gimp_drawable_apply_buffer (GimpDrawable *drawable,
+ GeglBuffer *buffer,
+ const GeglRectangle *buffer_rect,
+ gboolean push_undo,
+ const gchar *undo_desc,
+ gdouble opacity,
+ GimpLayerMode mode,
+ GimpLayerColorSpace blend_space,
+ GimpLayerColorSpace composite_space,
+ GimpLayerCompositeMode composite_mode,
+ GeglBuffer *base_buffer,
+ gint base_x,
+ gint base_y);
+
+GeglBuffer * gimp_drawable_get_buffer (GimpDrawable *drawable);
+void gimp_drawable_set_buffer (GimpDrawable *drawable,
+ gboolean push_undo,
+ const gchar *undo_desc,
+ GeglBuffer *buffer);
+void gimp_drawable_set_buffer_full (GimpDrawable *drawable,
+ gboolean push_undo,
+ const gchar *undo_desc,
+ GeglBuffer *buffer,
+ const GeglRectangle *bounds,
+ gboolean update);
+
+void gimp_drawable_steal_buffer (GimpDrawable *drawable,
+ GimpDrawable *src_drawable);
+
+GeglNode * gimp_drawable_get_source_node (GimpDrawable *drawable);
+GeglNode * gimp_drawable_get_mode_node (GimpDrawable *drawable);
+
+GeglRectangle gimp_drawable_get_bounding_box (GimpDrawable *drawable);
+gboolean gimp_drawable_update_bounding_box
+ (GimpDrawable *drawable);
+
+void gimp_drawable_swap_pixels (GimpDrawable *drawable,
+ GeglBuffer *buffer,
+ gint x,
+ gint y);
+
+void gimp_drawable_push_undo (GimpDrawable *drawable,
+ const gchar *undo_desc,
+ GeglBuffer *buffer,
+ gint x,
+ gint y,
+ gint width,
+ gint height);
+
+const Babl * gimp_drawable_get_format (GimpDrawable *drawable);
+const Babl * gimp_drawable_get_format_with_alpha(GimpDrawable *drawable);
+const Babl * gimp_drawable_get_format_without_alpha
+ (GimpDrawable *drawable);
+gboolean gimp_drawable_get_linear (GimpDrawable *drawable);
+gboolean gimp_drawable_has_alpha (GimpDrawable *drawable);
+GimpImageBaseType gimp_drawable_get_base_type (GimpDrawable *drawable);
+GimpComponentType gimp_drawable_get_component_type (GimpDrawable *drawable);
+GimpPrecision gimp_drawable_get_precision (GimpDrawable *drawable);
+gboolean gimp_drawable_is_rgb (GimpDrawable *drawable);
+gboolean gimp_drawable_is_gray (GimpDrawable *drawable);
+gboolean gimp_drawable_is_indexed (GimpDrawable *drawable);
+
+const Babl * gimp_drawable_get_component_format (GimpDrawable *drawable,
+ GimpChannelType channel);
+gint gimp_drawable_get_component_index (GimpDrawable *drawable,
+ GimpChannelType channel);
+
+const guchar * gimp_drawable_get_colormap (GimpDrawable *drawable);
+
+void gimp_drawable_start_paint (GimpDrawable *drawable);
+gboolean gimp_drawable_end_paint (GimpDrawable *drawable);
+gboolean gimp_drawable_flush_paint (GimpDrawable *drawable);
+gboolean gimp_drawable_is_painting (GimpDrawable *drawable);
+
+
+#endif /* __GIMP_DRAWABLE_H__ */
diff --git a/app/core/gimpdrawablefilter.c b/app/core/gimpdrawablefilter.c
new file mode 100644
index 0000000..6cda4f1
--- /dev/null
+++ b/app/core/gimpdrawablefilter.c
@@ -0,0 +1,1364 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+/* This file contains the code necessary for generating on canvas
+ * previews, by connecting a specified GEGL operation to do the
+ * processing. It uses drawable filters that allow for non-destructive
+ * manipulation of drawable data, with live preview on screen.
+ *
+ * To create a tool that uses this, see app/tools/gimpfiltertool.c for
+ * the interface and e.g. app/tools/gimpcolorbalancetool.c for an
+ * example of using that interface.
+ */
+
+#include "config.h"
+
+#include <cairo.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpbase/gimpbase.h"
+#include "libgimpcolor/gimpcolor.h"
+
+#include "core-types.h"
+
+#include "gegl/gimp-babl.h"
+#include "gegl/gimpapplicator.h"
+#include "gegl/gimp-gegl-utils.h"
+
+#include "gimpchannel.h"
+#include "gimpdrawable-filters.h"
+#include "gimpdrawablefilter.h"
+#include "gimpimage.h"
+#include "gimplayer.h"
+#include "gimpmarshal.h"
+#include "gimpprogress.h"
+
+
+enum
+{
+ FLUSH,
+ LAST_SIGNAL
+};
+
+
+struct _GimpDrawableFilter
+{
+ GimpFilter parent_instance;
+
+ GimpDrawable *drawable;
+ GeglNode *operation;
+
+ gboolean has_input;
+
+ gboolean clip;
+ GimpFilterRegion region;
+ gboolean crop_enabled;
+ GeglRectangle crop_rect;
+ gboolean preview_enabled;
+ gboolean preview_split_enabled;
+ GimpAlignmentType preview_split_alignment;
+ gint preview_split_position;
+ gdouble opacity;
+ GimpLayerMode paint_mode;
+ GimpLayerColorSpace blend_space;
+ GimpLayerColorSpace composite_space;
+ GimpLayerCompositeMode composite_mode;
+ gboolean add_alpha;
+ gboolean color_managed;
+ gboolean gamma_hack;
+
+ gboolean override_constraints;
+
+ GeglRectangle filter_area;
+ gboolean filter_clip;
+
+ GeglNode *translate;
+ GeglNode *crop_before;
+ GeglNode *cast_before;
+ GeglNode *transform_before;
+ GeglNode *transform_after;
+ GeglNode *cast_after;
+ GeglNode *crop_after;
+ GimpApplicator *applicator;
+};
+
+
+static void gimp_drawable_filter_dispose (GObject *object);
+static void gimp_drawable_filter_finalize (GObject *object);
+
+static void gimp_drawable_filter_sync_active (GimpDrawableFilter *filter);
+static void gimp_drawable_filter_sync_clip (GimpDrawableFilter *filter,
+ gboolean sync_region);
+static void gimp_drawable_filter_sync_region (GimpDrawableFilter *filter);
+static void gimp_drawable_filter_sync_crop (GimpDrawableFilter *filter,
+ gboolean old_crop_enabled,
+ const GeglRectangle *old_crop_rect,
+ gboolean old_preview_split_enabled,
+ GimpAlignmentType old_preview_split_alignment,
+ gint old_preview_split_position,
+ gboolean update);
+static void gimp_drawable_filter_sync_opacity (GimpDrawableFilter *filter);
+static void gimp_drawable_filter_sync_mode (GimpDrawableFilter *filter);
+static void gimp_drawable_filter_sync_affect (GimpDrawableFilter *filter);
+static void gimp_drawable_filter_sync_format (GimpDrawableFilter *filter);
+static void gimp_drawable_filter_sync_mask (GimpDrawableFilter *filter);
+static void gimp_drawable_filter_sync_transform (GimpDrawableFilter *filter);
+static void gimp_drawable_filter_sync_gamma_hack (GimpDrawableFilter *filter);
+
+static gboolean gimp_drawable_filter_is_added (GimpDrawableFilter *filter);
+static gboolean gimp_drawable_filter_is_active (GimpDrawableFilter *filter);
+static gboolean gimp_drawable_filter_add_filter (GimpDrawableFilter *filter);
+static gboolean gimp_drawable_filter_remove_filter (GimpDrawableFilter *filter);
+
+static void gimp_drawable_filter_update_drawable (GimpDrawableFilter *filter,
+ const GeglRectangle *area);
+
+static void gimp_drawable_filter_affect_changed (GimpImage *image,
+ GimpChannelType channel,
+ GimpDrawableFilter *filter);
+static void gimp_drawable_filter_mask_changed (GimpImage *image,
+ GimpDrawableFilter *filter);
+static void gimp_drawable_filter_profile_changed (GimpColorManaged *managed,
+ GimpDrawableFilter *filter);
+static void gimp_drawable_filter_lock_position_changed (GimpDrawable *drawable,
+ GimpDrawableFilter *filter);
+static void gimp_drawable_filter_format_changed (GimpDrawable *drawable,
+ GimpDrawableFilter *filter);
+static void gimp_drawable_filter_drawable_removed (GimpDrawable *drawable,
+ GimpDrawableFilter *filter);
+static void gimp_drawable_filter_lock_alpha_changed (GimpLayer *layer,
+ GimpDrawableFilter *filter);
+
+
+G_DEFINE_TYPE (GimpDrawableFilter, gimp_drawable_filter, GIMP_TYPE_FILTER)
+
+#define parent_class gimp_drawable_filter_parent_class
+
+static guint drawable_filter_signals[LAST_SIGNAL] = { 0, };
+
+
+static void
+gimp_drawable_filter_class_init (GimpDrawableFilterClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ drawable_filter_signals[FLUSH] =
+ g_signal_new ("flush",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpDrawableFilterClass, flush),
+ NULL, NULL,
+ gimp_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ object_class->dispose = gimp_drawable_filter_dispose;
+ object_class->finalize = gimp_drawable_filter_finalize;
+}
+
+static void
+gimp_drawable_filter_init (GimpDrawableFilter *drawable_filter)
+{
+ drawable_filter->clip = TRUE;
+ drawable_filter->region = GIMP_FILTER_REGION_SELECTION;
+ drawable_filter->preview_enabled = TRUE;
+ drawable_filter->preview_split_enabled = FALSE;
+ drawable_filter->preview_split_alignment = GIMP_ALIGN_LEFT;
+ drawable_filter->preview_split_position = 0;
+ drawable_filter->opacity = GIMP_OPACITY_OPAQUE;
+ drawable_filter->paint_mode = GIMP_LAYER_MODE_REPLACE;
+ drawable_filter->blend_space = GIMP_LAYER_COLOR_SPACE_AUTO;
+ drawable_filter->composite_space = GIMP_LAYER_COLOR_SPACE_AUTO;
+ drawable_filter->composite_mode = GIMP_LAYER_COMPOSITE_AUTO;
+}
+
+static void
+gimp_drawable_filter_dispose (GObject *object)
+{
+ GimpDrawableFilter *drawable_filter = GIMP_DRAWABLE_FILTER (object);
+
+ if (drawable_filter->drawable)
+ gimp_drawable_filter_remove_filter (drawable_filter);
+
+ G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+gimp_drawable_filter_finalize (GObject *object)
+{
+ GimpDrawableFilter *drawable_filter = GIMP_DRAWABLE_FILTER (object);
+
+ g_clear_object (&drawable_filter->operation);
+ g_clear_object (&drawable_filter->applicator);
+ g_clear_object (&drawable_filter->drawable);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+GimpDrawableFilter *
+gimp_drawable_filter_new (GimpDrawable *drawable,
+ const gchar *undo_desc,
+ GeglNode *operation,
+ const gchar *icon_name)
+{
+ GimpDrawableFilter *filter;
+ GeglNode *node;
+
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), NULL);
+ g_return_val_if_fail (gimp_item_is_attached (GIMP_ITEM (drawable)), NULL);
+ g_return_val_if_fail (GEGL_IS_NODE (operation), NULL);
+ g_return_val_if_fail (gegl_node_has_pad (operation, "output"), NULL);
+
+ filter = g_object_new (GIMP_TYPE_DRAWABLE_FILTER,
+ "name", undo_desc,
+ "icon-name", icon_name,
+ NULL);
+
+ filter->drawable = g_object_ref (drawable);
+ filter->operation = g_object_ref (operation);
+
+ node = gimp_filter_get_node (GIMP_FILTER (filter));
+
+ gegl_node_add_child (node, operation);
+ gimp_gegl_node_set_underlying_operation (node, operation);
+
+ filter->applicator = gimp_applicator_new (node);
+
+ gimp_filter_set_applicator (GIMP_FILTER (filter), filter->applicator);
+
+ gimp_applicator_set_cache (filter->applicator, TRUE);
+
+ filter->has_input = gegl_node_has_pad (filter->operation, "input");
+
+ if (filter->has_input)
+ {
+ GeglNode *input;
+
+ input = gegl_node_get_input_proxy (node, "input");
+
+ filter->translate = gegl_node_new_child (node,
+ "operation", "gegl:translate",
+ NULL);
+
+ filter->crop_before = gegl_node_new_child (node,
+ "operation", "gegl:crop",
+ NULL);
+
+ filter->cast_before = gegl_node_new_child (node,
+ "operation", "gegl:nop",
+ NULL);
+
+ filter->transform_before = gegl_node_new_child (node,
+ "operation", "gegl:nop",
+ NULL);
+
+ gegl_node_link_many (input,
+ filter->translate,
+ filter->crop_before,
+ filter->cast_before,
+ filter->transform_before,
+ filter->operation,
+ NULL);
+ }
+
+ filter->transform_after = gegl_node_new_child (node,
+ "operation", "gegl:nop",
+ NULL);
+
+ filter->cast_after = gegl_node_new_child (node,
+ "operation", "gegl:nop",
+ NULL);
+
+ filter->crop_after = gegl_node_new_child (node,
+ "operation", "gegl:crop",
+ NULL);
+
+ gegl_node_link_many (filter->operation,
+ filter->transform_after,
+ filter->cast_after,
+ filter->crop_after,
+ NULL);
+
+ gegl_node_connect_to (filter->crop_after, "output",
+ node, "aux");
+
+ return filter;
+}
+
+GimpDrawable *
+gimp_drawable_filter_get_drawable (GimpDrawableFilter *filter)
+{
+ g_return_val_if_fail (GIMP_IS_DRAWABLE_FILTER (filter), NULL);
+
+ return filter->drawable;
+}
+
+GeglNode *
+gimp_drawable_filter_get_operation (GimpDrawableFilter *filter)
+{
+ g_return_val_if_fail (GIMP_IS_DRAWABLE_FILTER (filter), NULL);
+
+ return filter->operation;
+}
+
+void
+gimp_drawable_filter_set_clip (GimpDrawableFilter *filter,
+ gboolean clip)
+{
+ g_return_if_fail (GIMP_IS_DRAWABLE_FILTER (filter));
+
+ if (clip != filter->clip)
+ {
+ filter->clip = clip;
+
+ gimp_drawable_filter_sync_clip (filter, TRUE);
+ }
+}
+
+void
+gimp_drawable_filter_set_region (GimpDrawableFilter *filter,
+ GimpFilterRegion region)
+{
+ g_return_if_fail (GIMP_IS_DRAWABLE_FILTER (filter));
+
+ if (region != filter->region)
+ {
+ filter->region = region;
+
+ gimp_drawable_filter_sync_region (filter);
+
+ if (gimp_drawable_filter_is_active (filter))
+ gimp_drawable_filter_update_drawable (filter, NULL);
+ }
+}
+
+void
+gimp_drawable_filter_set_crop (GimpDrawableFilter *filter,
+ const GeglRectangle *rect,
+ gboolean update)
+{
+ g_return_if_fail (GIMP_IS_DRAWABLE_FILTER (filter));
+
+ if ((rect != NULL) != filter->crop_enabled ||
+ (rect && ! gegl_rectangle_equal (rect, &filter->crop_rect)))
+ {
+ gboolean old_enabled = filter->crop_enabled;
+ GeglRectangle old_rect = filter->crop_rect;
+
+ if (rect)
+ {
+ filter->crop_enabled = TRUE;
+ filter->crop_rect = *rect;
+ }
+ else
+ {
+ filter->crop_enabled = FALSE;
+ }
+
+ gimp_drawable_filter_sync_crop (filter,
+ old_enabled,
+ &old_rect,
+ filter->preview_split_enabled,
+ filter->preview_split_alignment,
+ filter->preview_split_position,
+ update);
+ }
+}
+
+void
+gimp_drawable_filter_set_preview (GimpDrawableFilter *filter,
+ gboolean enabled)
+{
+ g_return_if_fail (GIMP_IS_DRAWABLE_FILTER (filter));
+
+ if (enabled != filter->preview_enabled)
+ {
+ filter->preview_enabled = enabled;
+
+ gimp_drawable_filter_sync_active (filter);
+
+ if (gimp_drawable_filter_is_added (filter))
+ {
+ gimp_drawable_update_bounding_box (filter->drawable);
+
+ gimp_drawable_filter_update_drawable (filter, NULL);
+ }
+ }
+}
+
+void
+gimp_drawable_filter_set_preview_split (GimpDrawableFilter *filter,
+ gboolean enabled,
+ GimpAlignmentType alignment,
+ gint position)
+{
+ GimpItem *item;
+
+ g_return_if_fail (GIMP_IS_DRAWABLE_FILTER (filter));
+ g_return_if_fail (alignment == GIMP_ALIGN_LEFT ||
+ alignment == GIMP_ALIGN_RIGHT ||
+ alignment == GIMP_ALIGN_TOP ||
+ alignment == GIMP_ALIGN_BOTTOM);
+
+ item = GIMP_ITEM (filter->drawable);
+
+ switch (alignment)
+ {
+ case GIMP_ALIGN_LEFT:
+ case GIMP_ALIGN_RIGHT:
+ position = CLAMP (position, 0, gimp_item_get_width (item));
+ break;
+
+ case GIMP_ALIGN_TOP:
+ case GIMP_ALIGN_BOTTOM:
+ position = CLAMP (position, 0, gimp_item_get_height (item));
+ break;
+
+ default:
+ g_return_if_reached ();
+ }
+
+ if (enabled != filter->preview_split_enabled ||
+ alignment != filter->preview_split_alignment ||
+ position != filter->preview_split_position)
+ {
+ gboolean old_enabled = filter->preview_split_enabled;
+ GimpAlignmentType old_alignment = filter->preview_split_alignment;
+ gint old_position = filter->preview_split_position;
+
+ filter->preview_split_enabled = enabled;
+ filter->preview_split_alignment = alignment;
+ filter->preview_split_position = position;
+
+ gimp_drawable_filter_sync_crop (filter,
+ filter->crop_enabled,
+ &filter->crop_rect,
+ old_enabled,
+ old_alignment,
+ old_position,
+ TRUE);
+ }
+}
+
+void
+gimp_drawable_filter_set_opacity (GimpDrawableFilter *filter,
+ gdouble opacity)
+{
+ g_return_if_fail (GIMP_IS_DRAWABLE_FILTER (filter));
+
+ if (opacity != filter->opacity)
+ {
+ filter->opacity = opacity;
+
+ gimp_drawable_filter_sync_opacity (filter);
+
+ if (gimp_drawable_filter_is_active (filter))
+ gimp_drawable_filter_update_drawable (filter, NULL);
+ }
+}
+
+void
+gimp_drawable_filter_set_mode (GimpDrawableFilter *filter,
+ GimpLayerMode paint_mode,
+ GimpLayerColorSpace blend_space,
+ GimpLayerColorSpace composite_space,
+ GimpLayerCompositeMode composite_mode)
+{
+ g_return_if_fail (GIMP_IS_DRAWABLE_FILTER (filter));
+
+ if (paint_mode != filter->paint_mode ||
+ blend_space != filter->blend_space ||
+ composite_space != filter->composite_space ||
+ composite_mode != filter->composite_mode)
+ {
+ filter->paint_mode = paint_mode;
+ filter->blend_space = blend_space;
+ filter->composite_space = composite_space;
+ filter->composite_mode = composite_mode;
+
+ gimp_drawable_filter_sync_mode (filter);
+
+ if (gimp_drawable_filter_is_active (filter))
+ gimp_drawable_filter_update_drawable (filter, NULL);
+ }
+}
+
+void
+gimp_drawable_filter_set_add_alpha (GimpDrawableFilter *filter,
+ gboolean add_alpha)
+{
+ g_return_if_fail (GIMP_IS_DRAWABLE_FILTER (filter));
+
+ if (add_alpha != filter->add_alpha)
+ {
+ filter->add_alpha = add_alpha;
+
+ gimp_drawable_filter_sync_format (filter);
+
+ if (gimp_drawable_filter_is_active (filter))
+ gimp_drawable_filter_update_drawable (filter, NULL);
+ }
+}
+
+void
+gimp_drawable_filter_set_color_managed (GimpDrawableFilter *filter,
+ gboolean color_managed)
+{
+ g_return_if_fail (GIMP_IS_DRAWABLE_FILTER (filter));
+
+ if (color_managed != filter->color_managed)
+ {
+ filter->color_managed = color_managed;
+
+ gimp_drawable_filter_sync_transform (filter);
+
+ if (gimp_drawable_filter_is_active (filter))
+ gimp_drawable_filter_update_drawable (filter, NULL);
+ }
+}
+
+void
+gimp_drawable_filter_set_gamma_hack (GimpDrawableFilter *filter,
+ gboolean gamma_hack)
+{
+ g_return_if_fail (GIMP_IS_DRAWABLE_FILTER (filter));
+
+ if (gamma_hack != filter->gamma_hack)
+ {
+ filter->gamma_hack = gamma_hack;
+
+ gimp_drawable_filter_sync_gamma_hack (filter);
+ gimp_drawable_filter_sync_transform (filter);
+
+ if (gimp_drawable_filter_is_active (filter))
+ gimp_drawable_filter_update_drawable (filter, NULL);
+ }
+}
+
+void
+gimp_drawable_filter_set_override_constraints (GimpDrawableFilter *filter,
+ gboolean override_constraints)
+{
+ g_return_if_fail (GIMP_IS_DRAWABLE_FILTER (filter));
+
+ if (override_constraints != filter->override_constraints)
+ {
+ filter->override_constraints = override_constraints;
+
+ gimp_drawable_filter_sync_affect (filter);
+ gimp_drawable_filter_sync_format (filter);
+ gimp_drawable_filter_sync_clip (filter, TRUE);
+
+ if (gimp_drawable_filter_is_active (filter))
+ gimp_drawable_filter_update_drawable (filter, NULL);
+ }
+}
+
+const Babl *
+gimp_drawable_filter_get_format (GimpDrawableFilter *filter)
+{
+ const Babl *format;
+
+ g_return_val_if_fail (GIMP_IS_DRAWABLE_FILTER (filter), NULL);
+
+ format = gimp_applicator_get_output_format (filter->applicator);
+
+ if (! format)
+ format = gimp_drawable_get_format (filter->drawable);
+
+ return format;
+}
+
+void
+gimp_drawable_filter_apply (GimpDrawableFilter *filter,
+ const GeglRectangle *area)
+{
+ g_return_if_fail (GIMP_IS_DRAWABLE_FILTER (filter));
+ g_return_if_fail (gimp_item_is_attached (GIMP_ITEM (filter->drawable)));
+
+ gimp_drawable_filter_add_filter (filter);
+
+ gimp_drawable_filter_sync_clip (filter, TRUE);
+
+ if (gimp_drawable_filter_is_active (filter))
+ {
+ gimp_drawable_update_bounding_box (filter->drawable);
+
+ gimp_drawable_filter_update_drawable (filter, area);
+ }
+}
+
+gboolean
+gimp_drawable_filter_commit (GimpDrawableFilter *filter,
+ GimpProgress *progress,
+ gboolean cancellable)
+{
+ gboolean success = TRUE;
+
+ g_return_val_if_fail (GIMP_IS_DRAWABLE_FILTER (filter), FALSE);
+ g_return_val_if_fail (gimp_item_is_attached (GIMP_ITEM (filter->drawable)),
+ FALSE);
+ g_return_val_if_fail (progress == NULL || GIMP_IS_PROGRESS (progress), FALSE);
+
+ if (gimp_drawable_filter_is_added (filter))
+ {
+ const Babl *format;
+
+ format = gimp_drawable_filter_get_format (filter);
+
+ gimp_drawable_filter_set_preview_split (filter, FALSE,
+ filter->preview_split_alignment,
+ filter->preview_split_position);
+ gimp_drawable_filter_set_preview (filter, TRUE);
+
+ success = gimp_drawable_merge_filter (filter->drawable,
+ GIMP_FILTER (filter),
+ progress,
+ gimp_object_get_name (filter),
+ format,
+ filter->filter_clip,
+ cancellable,
+ FALSE);
+
+ gimp_drawable_filter_remove_filter (filter);
+
+ if (! success)
+ gimp_drawable_filter_update_drawable (filter, NULL);
+
+ g_signal_emit (filter, drawable_filter_signals[FLUSH], 0);
+ }
+
+ return success;
+}
+
+void
+gimp_drawable_filter_abort (GimpDrawableFilter *filter)
+{
+ g_return_if_fail (GIMP_IS_DRAWABLE_FILTER (filter));
+
+ if (gimp_drawable_filter_remove_filter (filter))
+ {
+ gimp_drawable_filter_update_drawable (filter, NULL);
+ }
+}
+
+
+/* private functions */
+
+static void
+gimp_drawable_filter_sync_active (GimpDrawableFilter *filter)
+{
+ gimp_applicator_set_active (filter->applicator, filter->preview_enabled);
+}
+
+static void
+gimp_drawable_filter_sync_clip (GimpDrawableFilter *filter,
+ gboolean sync_region)
+{
+ gboolean clip;
+
+ if (filter->override_constraints)
+ clip = filter->clip;
+ else
+ clip = gimp_item_get_clip (GIMP_ITEM (filter->drawable), filter->clip);
+
+ if (! clip)
+ {
+ GimpImage *image = gimp_item_get_image (GIMP_ITEM (filter->drawable));
+ GimpChannel *mask = gimp_image_get_mask (image);
+
+ if (! gimp_channel_is_empty (mask))
+ clip = TRUE;
+ }
+
+ if (! clip)
+ {
+ GeglRectangle bounding_box;
+
+ bounding_box = gegl_node_get_bounding_box (filter->operation);
+
+ if (gegl_rectangle_is_infinite_plane (&bounding_box))
+ clip = TRUE;
+ }
+
+ if (clip != filter->filter_clip)
+ {
+ filter->filter_clip = clip;
+
+ if (sync_region)
+ gimp_drawable_filter_sync_region (filter);
+ }
+}
+
+static void
+gimp_drawable_filter_sync_region (GimpDrawableFilter *filter)
+{
+ if (filter->region == GIMP_FILTER_REGION_SELECTION)
+ {
+ if (filter->has_input)
+ {
+ gegl_node_set (filter->translate,
+ "x", (gdouble) -filter->filter_area.x,
+ "y", (gdouble) -filter->filter_area.y,
+ NULL);
+
+ gegl_node_set (filter->crop_before,
+ "width", (gdouble) filter->filter_area.width,
+ "height", (gdouble) filter->filter_area.height,
+ NULL);
+ }
+
+ if (filter->filter_clip)
+ {
+ gegl_node_set (filter->crop_after,
+ "operation", "gegl:crop",
+ "x", 0.0,
+ "y", 0.0,
+ "width", (gdouble) filter->filter_area.width,
+ "height", (gdouble) filter->filter_area.height,
+ NULL);
+ }
+ else
+ {
+ gegl_node_set (filter->crop_after,
+ "operation", "gegl:nop",
+ NULL);
+ }
+
+ gimp_applicator_set_apply_offset (filter->applicator,
+ filter->filter_area.x,
+ filter->filter_area.y);
+ }
+ else
+ {
+ GimpItem *item = GIMP_ITEM (filter->drawable);
+ gdouble width = gimp_item_get_width (item);
+ gdouble height = gimp_item_get_height (item);
+
+ if (filter->has_input)
+ {
+ gegl_node_set (filter->translate,
+ "x", (gdouble) 0.0,
+ "y", (gdouble) 0.0,
+ NULL);
+
+ gegl_node_set (filter->crop_before,
+ "width", width,
+ "height", height,
+ NULL);
+ }
+
+ if (filter->filter_clip)
+ {
+ gegl_node_set (filter->crop_after,
+ "operation", "gegl:crop",
+ "x", (gdouble) filter->filter_area.x,
+ "y", (gdouble) filter->filter_area.y,
+ "width", (gdouble) filter->filter_area.width,
+ "height", (gdouble) filter->filter_area.height,
+ NULL);
+ }
+ else
+ {
+ gegl_node_set (filter->crop_after,
+ "operation", "gegl:nop",
+ NULL);
+ }
+
+ gimp_applicator_set_apply_offset (filter->applicator, 0, 0);
+ }
+
+ if (gimp_drawable_filter_is_active (filter))
+ {
+ if (gimp_drawable_update_bounding_box (filter->drawable))
+ g_signal_emit (filter, drawable_filter_signals[FLUSH], 0);
+ }
+}
+
+static gboolean
+gimp_drawable_filter_get_crop_rect (GimpDrawableFilter *filter,
+ gboolean crop_enabled,
+ const GeglRectangle *crop_rect,
+ gboolean preview_split_enabled,
+ GimpAlignmentType preview_split_alignment,
+ gint preview_split_position,
+ GeglRectangle *rect)
+{
+ GeglRectangle bounds;
+ gint x1, x2;
+ gint y1, y2;
+
+ bounds = gegl_rectangle_infinite_plane ();
+
+ x1 = bounds.x;
+ x2 = bounds.x + bounds.width;
+
+ y1 = bounds.y;
+ y2 = bounds.y + bounds.height;
+
+ if (preview_split_enabled)
+ {
+ switch (preview_split_alignment)
+ {
+ case GIMP_ALIGN_LEFT:
+ x2 = preview_split_position;
+ break;
+
+ case GIMP_ALIGN_RIGHT:
+ x1 = preview_split_position;
+ break;
+
+ case GIMP_ALIGN_TOP:
+ y2 = preview_split_position;
+ break;
+
+ case GIMP_ALIGN_BOTTOM:
+ y1 = preview_split_position;
+ break;
+
+ default:
+ g_return_val_if_reached (FALSE);
+ }
+ }
+
+ gegl_rectangle_set (rect, x1, y1, x2 - x1, y2 - y1);
+
+ if (crop_enabled)
+ gegl_rectangle_intersect (rect, rect, crop_rect);
+
+ return ! gegl_rectangle_equal (rect, &bounds);
+}
+
+static void
+gimp_drawable_filter_sync_crop (GimpDrawableFilter *filter,
+ gboolean old_crop_enabled,
+ const GeglRectangle *old_crop_rect,
+ gboolean old_preview_split_enabled,
+ GimpAlignmentType old_preview_split_alignment,
+ gint old_preview_split_position,
+ gboolean update)
+{
+ GeglRectangle old_rect;
+ GeglRectangle new_rect;
+ gboolean enabled;
+
+ gimp_drawable_filter_get_crop_rect (filter,
+ old_crop_enabled,
+ old_crop_rect,
+ old_preview_split_enabled,
+ old_preview_split_alignment,
+ old_preview_split_position,
+ &old_rect);
+
+ enabled = gimp_drawable_filter_get_crop_rect (filter,
+ filter->crop_enabled,
+ &filter->crop_rect,
+ filter->preview_split_enabled,
+ filter->preview_split_alignment,
+ filter->preview_split_position,
+ &new_rect);
+
+ gimp_applicator_set_crop (filter->applicator, enabled ? &new_rect : NULL);
+
+ if (update &&
+ gimp_drawable_filter_is_active (filter) &&
+ ! gegl_rectangle_equal (&old_rect, &new_rect))
+ {
+ GeglRectangle diff_rects[4];
+ gint n_diff_rects;
+ gint i;
+
+ gimp_drawable_update_bounding_box (filter->drawable);
+
+ n_diff_rects = gegl_rectangle_xor (diff_rects, &old_rect, &new_rect);
+
+ for (i = 0; i < n_diff_rects; i++)
+ gimp_drawable_filter_update_drawable (filter, &diff_rects[i]);
+ }
+}
+
+static void
+gimp_drawable_filter_sync_opacity (GimpDrawableFilter *filter)
+{
+ gimp_applicator_set_opacity (filter->applicator,
+ filter->opacity);
+}
+
+static void
+gimp_drawable_filter_sync_mode (GimpDrawableFilter *filter)
+{
+ GimpLayerMode paint_mode = filter->paint_mode;
+
+ if (! filter->has_input && paint_mode == GIMP_LAYER_MODE_REPLACE)
+ {
+ /* if the filter's op has no input, use NORMAL instead of REPLACE, so
+ * that we composite the op's output on top of the input, instead of
+ * completely replacing it.
+ */
+ paint_mode = GIMP_LAYER_MODE_NORMAL;
+ }
+
+ gimp_applicator_set_mode (filter->applicator,
+ paint_mode,
+ filter->blend_space,
+ filter->composite_space,
+ filter->composite_mode);
+}
+
+static void
+gimp_drawable_filter_sync_affect (GimpDrawableFilter *filter)
+{
+ gimp_applicator_set_affect (
+ filter->applicator,
+ filter->override_constraints ?
+
+ GIMP_COMPONENT_MASK_RED |
+ GIMP_COMPONENT_MASK_GREEN |
+ GIMP_COMPONENT_MASK_BLUE |
+ GIMP_COMPONENT_MASK_ALPHA :
+
+ gimp_drawable_get_active_mask (filter->drawable));
+}
+
+static void
+gimp_drawable_filter_sync_format (GimpDrawableFilter *filter)
+{
+ const Babl *format;
+
+ if (filter->add_alpha &&
+ (gimp_drawable_supports_alpha (filter->drawable) ||
+ filter->override_constraints))
+ {
+ format = gimp_drawable_get_format_with_alpha (filter->drawable);
+ }
+ else
+ {
+ format = gimp_drawable_get_format (filter->drawable);
+ }
+
+ gimp_applicator_set_output_format (filter->applicator, format);
+}
+
+static void
+gimp_drawable_filter_sync_mask (GimpDrawableFilter *filter)
+{
+ GimpImage *image = gimp_item_get_image (GIMP_ITEM (filter->drawable));
+ GimpChannel *mask = gimp_image_get_mask (image);
+
+ if (gimp_channel_is_empty (mask))
+ {
+ gimp_applicator_set_mask_buffer (filter->applicator, NULL);
+ }
+ else
+ {
+ GeglBuffer *mask_buffer;
+ gint offset_x, offset_y;
+
+ mask_buffer = gimp_drawable_get_buffer (GIMP_DRAWABLE (mask));
+ gimp_item_get_offset (GIMP_ITEM (filter->drawable),
+ &offset_x, &offset_y);
+
+ gimp_applicator_set_mask_buffer (filter->applicator, mask_buffer);
+ gimp_applicator_set_mask_offset (filter->applicator,
+ -offset_x, -offset_y);
+ }
+
+ gimp_item_mask_intersect (GIMP_ITEM (filter->drawable),
+ &filter->filter_area.x,
+ &filter->filter_area.y,
+ &filter->filter_area.width,
+ &filter->filter_area.height);
+}
+
+static void
+gimp_drawable_filter_sync_transform (GimpDrawableFilter *filter)
+{
+ GimpColorManaged *managed = GIMP_COLOR_MANAGED (filter->drawable);
+
+ if (filter->color_managed)
+ {
+ const Babl *drawable_format = NULL;
+ const Babl *input_format = NULL;
+ const Babl *output_format = NULL;
+ GimpColorProfile *drawable_profile = NULL;
+ GimpColorProfile *input_profile = NULL;
+ GimpColorProfile *output_profile = NULL;
+ guint32 dummy;
+
+ drawable_format = gimp_drawable_get_format (filter->drawable);
+ if (filter->has_input)
+ input_format = gimp_gegl_node_get_format (filter->operation, "input");
+ output_format = gimp_gegl_node_get_format (filter->operation, "output");
+
+ g_printerr ("drawable format: %s\n", babl_get_name (drawable_format));
+ if (filter->has_input)
+ g_printerr ("filter input format: %s\n", babl_get_name (input_format));
+ g_printerr ("filter output format: %s\n", babl_get_name (output_format));
+
+ /* convert the drawable format to float, so we get a precise
+ * color transform
+ */
+ drawable_format =
+ gimp_babl_format (gimp_babl_format_get_base_type (drawable_format),
+ gimp_babl_precision (GIMP_COMPONENT_TYPE_FLOAT,
+ gimp_babl_format_get_linear (drawable_format)),
+ babl_format_has_alpha (drawable_format));
+
+ /* convert the filter input/output formats to something we have
+ * built-in color profiles for (see the get_color_profile()
+ * calls below)
+ */
+ if (filter->has_input)
+ input_format = gimp_color_profile_get_lcms_format (input_format, &dummy);
+ output_format = gimp_color_profile_get_lcms_format (output_format, &dummy);
+
+ g_printerr ("profile transform drawable format: %s\n",
+ babl_get_name (drawable_format));
+ if (filter->has_input)
+ g_printerr ("profile transform input format: %s\n",
+ babl_get_name (input_format));
+ g_printerr ("profile transform output format: %s\n",
+ babl_get_name (output_format));
+
+ drawable_profile = gimp_color_managed_get_color_profile (managed);
+ if (filter->has_input)
+ input_profile = gimp_babl_format_get_color_profile (input_format);
+ output_profile = gimp_babl_format_get_color_profile (output_format);
+
+ if ((filter->has_input &&
+ ! gimp_color_transform_can_gegl_copy (drawable_profile,
+ input_profile)) ||
+ ! gimp_color_transform_can_gegl_copy (output_profile,
+ drawable_profile))
+ {
+ g_printerr ("using gimp:profile-transform\n");
+
+ if (filter->has_input)
+ {
+ gegl_node_set (filter->transform_before,
+ "operation", "gimp:profile-transform",
+ "src-profile", drawable_profile,
+ "src-format", drawable_format,
+ "dest-profile", input_profile,
+ "dest-format", input_format,
+ NULL);
+ }
+
+ gegl_node_set (filter->transform_after,
+ "operation", "gimp:profile-transform",
+ "src-profile", output_profile,
+ "src-format", output_format,
+ "dest-profile", drawable_profile,
+ "dest-format", drawable_format,
+ NULL);
+
+ return;
+ }
+ }
+
+ g_printerr ("using gegl copy\n");
+
+ if (filter->has_input)
+ {
+ gegl_node_set (filter->transform_before,
+ "operation", "gegl:nop",
+ NULL);
+ }
+
+ gegl_node_set (filter->transform_after,
+ "operation", "gegl:nop",
+ NULL);
+}
+
+static void
+gimp_drawable_filter_sync_gamma_hack (GimpDrawableFilter *filter)
+{
+ if (filter->gamma_hack)
+ {
+ const Babl *drawable_format;
+ const Babl *cast_format;
+
+ drawable_format =
+ gimp_drawable_get_format_with_alpha (filter->drawable);
+
+ cast_format =
+ gimp_babl_format (gimp_babl_format_get_base_type (drawable_format),
+ gimp_babl_precision (gimp_babl_format_get_component_type (drawable_format),
+ ! gimp_babl_format_get_linear (drawable_format)),
+ TRUE);
+
+ if (filter->has_input)
+ {
+ gegl_node_set (filter->cast_before,
+ "operation", "gegl:cast-format",
+ "input-format", drawable_format,
+ "output-format", cast_format,
+ NULL);
+ }
+
+ gegl_node_set (filter->cast_after,
+ "operation", "gegl:cast-format",
+ "input-format", cast_format,
+ "output-format", drawable_format,
+ NULL);
+ }
+ else
+ {
+ if (filter->has_input)
+ {
+ gegl_node_set (filter->cast_before,
+ "operation", "gegl:nop",
+ NULL);
+ }
+
+ gegl_node_set (filter->cast_after,
+ "operation", "gegl:nop",
+ NULL);
+ }
+}
+
+static gboolean
+gimp_drawable_filter_is_added (GimpDrawableFilter *filter)
+{
+ return gimp_drawable_has_filter (filter->drawable,
+ GIMP_FILTER (filter));
+}
+
+static gboolean
+gimp_drawable_filter_is_active (GimpDrawableFilter *filter)
+{
+ return gimp_drawable_filter_is_added (filter) &&
+ filter->preview_enabled;
+}
+
+static gboolean
+gimp_drawable_filter_add_filter (GimpDrawableFilter *filter)
+{
+ if (! gimp_drawable_filter_is_added (filter))
+ {
+ GimpImage *image = gimp_item_get_image (GIMP_ITEM (filter->drawable));
+
+ gimp_viewable_preview_freeze (GIMP_VIEWABLE (filter->drawable));
+
+ gimp_drawable_filter_sync_active (filter);
+ gimp_drawable_filter_sync_mask (filter);
+ gimp_drawable_filter_sync_clip (filter, FALSE);
+ gimp_drawable_filter_sync_region (filter);
+ gimp_drawable_filter_sync_crop (filter,
+ filter->crop_enabled,
+ &filter->crop_rect,
+ filter->preview_split_enabled,
+ filter->preview_split_alignment,
+ filter->preview_split_position,
+ TRUE);
+ gimp_drawable_filter_sync_opacity (filter);
+ gimp_drawable_filter_sync_mode (filter);
+ gimp_drawable_filter_sync_affect (filter);
+ gimp_drawable_filter_sync_format (filter);
+ gimp_drawable_filter_sync_transform (filter);
+ gimp_drawable_filter_sync_gamma_hack (filter);
+
+ gimp_drawable_add_filter (filter->drawable,
+ GIMP_FILTER (filter));
+
+ gimp_drawable_update_bounding_box (filter->drawable);
+
+ g_signal_connect (image, "component-active-changed",
+ G_CALLBACK (gimp_drawable_filter_affect_changed),
+ filter);
+ g_signal_connect (image, "mask-changed",
+ G_CALLBACK (gimp_drawable_filter_mask_changed),
+ filter);
+ g_signal_connect (image, "profile-changed",
+ G_CALLBACK (gimp_drawable_filter_profile_changed),
+ filter);
+ g_signal_connect (filter->drawable, "lock-position-changed",
+ G_CALLBACK (gimp_drawable_filter_lock_position_changed),
+ filter);
+ g_signal_connect (filter->drawable, "format-changed",
+ G_CALLBACK (gimp_drawable_filter_format_changed),
+ filter);
+ g_signal_connect (filter->drawable, "removed",
+ G_CALLBACK (gimp_drawable_filter_drawable_removed),
+ filter);
+
+ if (GIMP_IS_LAYER (filter->drawable))
+ {
+ g_signal_connect (filter->drawable, "lock-alpha-changed",
+ G_CALLBACK (gimp_drawable_filter_lock_alpha_changed),
+ filter);
+ }
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+gimp_drawable_filter_remove_filter (GimpDrawableFilter *filter)
+{
+ if (gimp_drawable_filter_is_added (filter))
+ {
+ GimpImage *image = gimp_item_get_image (GIMP_ITEM (filter->drawable));
+
+ if (GIMP_IS_LAYER (filter->drawable))
+ {
+ g_signal_handlers_disconnect_by_func (filter->drawable,
+ gimp_drawable_filter_lock_alpha_changed,
+ filter);
+ }
+
+ g_signal_handlers_disconnect_by_func (filter->drawable,
+ gimp_drawable_filter_drawable_removed,
+ filter);
+ g_signal_handlers_disconnect_by_func (filter->drawable,
+ gimp_drawable_filter_format_changed,
+ filter);
+ g_signal_handlers_disconnect_by_func (filter->drawable,
+ gimp_drawable_filter_lock_position_changed,
+ filter);
+ g_signal_handlers_disconnect_by_func (image,
+ gimp_drawable_filter_profile_changed,
+ filter);
+ g_signal_handlers_disconnect_by_func (image,
+ gimp_drawable_filter_mask_changed,
+ filter);
+ g_signal_handlers_disconnect_by_func (image,
+ gimp_drawable_filter_affect_changed,
+ filter);
+
+ gimp_drawable_remove_filter (filter->drawable,
+ GIMP_FILTER (filter));
+
+ gimp_drawable_update_bounding_box (filter->drawable);
+
+ gimp_viewable_preview_thaw (GIMP_VIEWABLE (filter->drawable));
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void
+gimp_drawable_filter_update_drawable (GimpDrawableFilter *filter,
+ const GeglRectangle *area)
+{
+ GeglRectangle bounding_box;
+ GeglRectangle update_area;
+
+ bounding_box = gimp_drawable_get_bounding_box (filter->drawable);
+
+ if (area)
+ {
+ if (! gegl_rectangle_intersect (&update_area,
+ area, &bounding_box))
+ {
+ return;
+ }
+ }
+ else
+ {
+ gimp_drawable_filter_get_crop_rect (filter,
+ filter->crop_enabled,
+ &filter->crop_rect,
+ filter->preview_split_enabled,
+ filter->preview_split_alignment,
+ filter->preview_split_position,
+ &update_area);
+
+ if (! gegl_rectangle_intersect (&update_area,
+ &update_area, &bounding_box))
+ {
+ return;
+ }
+ }
+
+ if (update_area.width > 0 &&
+ update_area.height > 0)
+ {
+ gimp_drawable_update (filter->drawable,
+ update_area.x,
+ update_area.y,
+ update_area.width,
+ update_area.height);
+
+ g_signal_emit (filter, drawable_filter_signals[FLUSH], 0);
+ }
+}
+
+static void
+gimp_drawable_filter_affect_changed (GimpImage *image,
+ GimpChannelType channel,
+ GimpDrawableFilter *filter)
+{
+ gimp_drawable_filter_sync_affect (filter);
+ gimp_drawable_filter_update_drawable (filter, NULL);
+}
+
+static void
+gimp_drawable_filter_mask_changed (GimpImage *image,
+ GimpDrawableFilter *filter)
+{
+ gimp_drawable_filter_update_drawable (filter, NULL);
+
+ gimp_drawable_filter_sync_mask (filter);
+ gimp_drawable_filter_sync_clip (filter, FALSE);
+ gimp_drawable_filter_sync_region (filter);
+
+ gimp_drawable_filter_update_drawable (filter, NULL);
+}
+
+static void
+gimp_drawable_filter_profile_changed (GimpColorManaged *managed,
+ GimpDrawableFilter *filter)
+{
+ gimp_drawable_filter_sync_transform (filter);
+ gimp_drawable_filter_update_drawable (filter, NULL);
+}
+
+static void
+gimp_drawable_filter_lock_position_changed (GimpDrawable *drawable,
+ GimpDrawableFilter *filter)
+{
+ gimp_drawable_filter_sync_clip (filter, TRUE);
+ gimp_drawable_filter_update_drawable (filter, NULL);
+}
+
+static void
+gimp_drawable_filter_format_changed (GimpDrawable *drawable,
+ GimpDrawableFilter *filter)
+{
+ gimp_drawable_filter_sync_format (filter);
+ gimp_drawable_filter_update_drawable (filter, NULL);
+}
+
+static void
+gimp_drawable_filter_drawable_removed (GimpDrawable *drawable,
+ GimpDrawableFilter *filter)
+{
+ gimp_drawable_filter_remove_filter (filter);
+}
+
+static void
+gimp_drawable_filter_lock_alpha_changed (GimpLayer *layer,
+ GimpDrawableFilter *filter)
+{
+ gimp_drawable_filter_sync_affect (filter);
+ gimp_drawable_filter_update_drawable (filter, NULL);
+}
diff --git a/app/core/gimpdrawablefilter.h b/app/core/gimpdrawablefilter.h
new file mode 100644
index 0000000..5aa8324
--- /dev/null
+++ b/app/core/gimpdrawablefilter.h
@@ -0,0 +1,108 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_DRAWABLE_FILTER_H__
+#define __GIMP_DRAWABLE_FILTER_H__
+
+
+#include "gimpfilter.h"
+
+
+#define GIMP_TYPE_DRAWABLE_FILTER (gimp_drawable_filter_get_type ())
+#define GIMP_DRAWABLE_FILTER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_DRAWABLE_FILTER, GimpDrawableFilter))
+#define GIMP_DRAWABLE_FILTER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_DRAWABLE_FILTER, GimpDrawableFilterClass))
+#define GIMP_IS_DRAWABLE_FILTER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_DRAWABLE_FILTER))
+#define GIMP_IS_DRAWABLE_FILTER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_DRAWABLE_FILTER))
+#define GIMP_DRAWABLE_FILTER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_DRAWABLE_FILTER, GimpDrawableFilterClass))
+
+
+typedef struct _GimpDrawableFilterClass GimpDrawableFilterClass;
+
+struct _GimpDrawableFilterClass
+{
+ GimpFilterClass parent_class;
+
+ void (* flush) (GimpDrawableFilter *filter);
+};
+
+
+/* Drawable Filter functions */
+
+/* Successive apply() functions can be called, but eventually MUST be
+ * followed with an commit() or an abort() call, both of which will
+ * remove the live filter from the drawable.
+ */
+
+GType gimp_drawable_filter_get_type (void) G_GNUC_CONST;
+
+GimpDrawableFilter *
+ gimp_drawable_filter_new (GimpDrawable *drawable,
+ const gchar *undo_desc,
+ GeglNode *operation,
+ const gchar *icon_name);
+
+GimpDrawable *
+ gimp_drawable_filter_get_drawable (GimpDrawableFilter *filter);
+GeglNode * gimp_drawable_filter_get_operation (GimpDrawableFilter *filter);
+
+void gimp_drawable_filter_set_clip (GimpDrawableFilter *filter,
+ gboolean clip);
+void gimp_drawable_filter_set_region (GimpDrawableFilter *filter,
+ GimpFilterRegion region);
+void gimp_drawable_filter_set_crop (GimpDrawableFilter *filter,
+ const GeglRectangle *rect,
+ gboolean update);
+void gimp_drawable_filter_set_preview (GimpDrawableFilter *filter,
+ gboolean enabled);
+void gimp_drawable_filter_set_preview_split
+ (GimpDrawableFilter *filter,
+ gboolean enabled,
+ GimpAlignmentType alignment,
+ gint split_position);
+void gimp_drawable_filter_set_opacity (GimpDrawableFilter *filter,
+ gdouble opacity);
+void gimp_drawable_filter_set_mode (GimpDrawableFilter *filter,
+ GimpLayerMode paint_mode,
+ GimpLayerColorSpace blend_space,
+ GimpLayerColorSpace composite_space,
+ GimpLayerCompositeMode composite_mode);
+void gimp_drawable_filter_set_add_alpha (GimpDrawableFilter *filter,
+ gboolean add_alpha);
+
+void gimp_drawable_filter_set_color_managed
+ (GimpDrawableFilter *filter,
+ gboolean managed);
+void gimp_drawable_filter_set_gamma_hack (GimpDrawableFilter *filter,
+ gboolean gamma_hack);
+
+void gimp_drawable_filter_set_override_constraints
+ (GimpDrawableFilter *filter,
+ gboolean override_constraints);
+
+const Babl *
+ gimp_drawable_filter_get_format (GimpDrawableFilter *filter);
+
+void gimp_drawable_filter_apply (GimpDrawableFilter *filter,
+ const GeglRectangle *area);
+
+gboolean gimp_drawable_filter_commit (GimpDrawableFilter *filter,
+ GimpProgress *progress,
+ gboolean cancellable);
+void gimp_drawable_filter_abort (GimpDrawableFilter *filter);
+
+
+#endif /* __GIMP_DRAWABLE_FILTER_H__ */
diff --git a/app/core/gimpdrawablemodundo.c b/app/core/gimpdrawablemodundo.c
new file mode 100644
index 0000000..5c3e897
--- /dev/null
+++ b/app/core/gimpdrawablemodundo.c
@@ -0,0 +1,216 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "core-types.h"
+
+#include "gegl/gimp-gegl-utils.h"
+
+#include "gimp-memsize.h"
+#include "gimpimage.h"
+#include "gimpdrawable.h"
+#include "gimpdrawablemodundo.h"
+
+
+enum
+{
+ PROP_0,
+ PROP_COPY_BUFFER
+};
+
+
+static void gimp_drawable_mod_undo_constructed (GObject *object);
+static void gimp_drawable_mod_undo_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_drawable_mod_undo_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static gint64 gimp_drawable_mod_undo_get_memsize (GimpObject *object,
+ gint64 *gui_size);
+
+static void gimp_drawable_mod_undo_pop (GimpUndo *undo,
+ GimpUndoMode undo_mode,
+ GimpUndoAccumulator *accum);
+static void gimp_drawable_mod_undo_free (GimpUndo *undo,
+ GimpUndoMode undo_mode);
+
+
+G_DEFINE_TYPE (GimpDrawableModUndo, gimp_drawable_mod_undo, GIMP_TYPE_ITEM_UNDO)
+
+#define parent_class gimp_drawable_mod_undo_parent_class
+
+
+static void
+gimp_drawable_mod_undo_class_init (GimpDrawableModUndoClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpObjectClass *gimp_object_class = GIMP_OBJECT_CLASS (klass);
+ GimpUndoClass *undo_class = GIMP_UNDO_CLASS (klass);
+
+ object_class->constructed = gimp_drawable_mod_undo_constructed;
+ object_class->set_property = gimp_drawable_mod_undo_set_property;
+ object_class->get_property = gimp_drawable_mod_undo_get_property;
+
+ gimp_object_class->get_memsize = gimp_drawable_mod_undo_get_memsize;
+
+ undo_class->pop = gimp_drawable_mod_undo_pop;
+ undo_class->free = gimp_drawable_mod_undo_free;
+
+ g_object_class_install_property (object_class, PROP_COPY_BUFFER,
+ g_param_spec_boolean ("copy-buffer",
+ NULL, NULL,
+ FALSE,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+}
+
+static void
+gimp_drawable_mod_undo_init (GimpDrawableModUndo *undo)
+{
+}
+
+static void
+gimp_drawable_mod_undo_constructed (GObject *object)
+{
+ GimpDrawableModUndo *drawable_mod_undo = GIMP_DRAWABLE_MOD_UNDO (object);
+ GimpItem *item;
+ GimpDrawable *drawable;
+
+ G_OBJECT_CLASS (parent_class)->constructed (object);
+
+ gimp_assert (GIMP_IS_DRAWABLE (GIMP_ITEM_UNDO (object)->item));
+
+ item = GIMP_ITEM_UNDO (object)->item;
+ drawable = GIMP_DRAWABLE (item);
+
+ if (drawable_mod_undo->copy_buffer)
+ {
+ drawable_mod_undo->buffer =
+ gimp_gegl_buffer_dup (gimp_drawable_get_buffer (drawable));
+ }
+ else
+ {
+ drawable_mod_undo->buffer =
+ g_object_ref (gimp_drawable_get_buffer (drawable));
+ }
+
+ gimp_item_get_offset (item,
+ &drawable_mod_undo->offset_x,
+ &drawable_mod_undo->offset_y);
+}
+
+static void
+gimp_drawable_mod_undo_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpDrawableModUndo *drawable_mod_undo = GIMP_DRAWABLE_MOD_UNDO (object);
+
+ switch (property_id)
+ {
+ case PROP_COPY_BUFFER:
+ drawable_mod_undo->copy_buffer = g_value_get_boolean (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_drawable_mod_undo_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpDrawableModUndo *drawable_mod_undo = GIMP_DRAWABLE_MOD_UNDO (object);
+
+ switch (property_id)
+ {
+ case PROP_COPY_BUFFER:
+ g_value_set_boolean (value, drawable_mod_undo->copy_buffer);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static gint64
+gimp_drawable_mod_undo_get_memsize (GimpObject *object,
+ gint64 *gui_size)
+{
+ GimpDrawableModUndo *drawable_mod_undo = GIMP_DRAWABLE_MOD_UNDO (object);
+ gint64 memsize = 0;
+
+ memsize += gimp_gegl_buffer_get_memsize (drawable_mod_undo->buffer);
+
+ return memsize + GIMP_OBJECT_CLASS (parent_class)->get_memsize (object,
+ gui_size);
+}
+
+static void
+gimp_drawable_mod_undo_pop (GimpUndo *undo,
+ GimpUndoMode undo_mode,
+ GimpUndoAccumulator *accum)
+{
+ GimpDrawableModUndo *drawable_mod_undo = GIMP_DRAWABLE_MOD_UNDO (undo);
+ GimpDrawable *drawable = GIMP_DRAWABLE (GIMP_ITEM_UNDO (undo)->item);
+ GeglBuffer *buffer;
+ gint offset_x;
+ gint offset_y;
+
+ GIMP_UNDO_CLASS (parent_class)->pop (undo, undo_mode, accum);
+
+ buffer = drawable_mod_undo->buffer;
+ offset_x = drawable_mod_undo->offset_x;
+ offset_y = drawable_mod_undo->offset_y;
+
+ drawable_mod_undo->buffer = g_object_ref (gimp_drawable_get_buffer (drawable));
+
+ gimp_item_get_offset (GIMP_ITEM (drawable),
+ &drawable_mod_undo->offset_x,
+ &drawable_mod_undo->offset_y);
+
+ gimp_drawable_set_buffer_full (drawable, FALSE, NULL,
+ buffer,
+ GEGL_RECTANGLE (offset_x, offset_y, 0, 0),
+ TRUE);
+ g_object_unref (buffer);
+}
+
+static void
+gimp_drawable_mod_undo_free (GimpUndo *undo,
+ GimpUndoMode undo_mode)
+{
+ GimpDrawableModUndo *drawable_mod_undo = GIMP_DRAWABLE_MOD_UNDO (undo);
+
+ g_clear_object (&drawable_mod_undo->buffer);
+
+ GIMP_UNDO_CLASS (parent_class)->free (undo, undo_mode);
+}
diff --git a/app/core/gimpdrawablemodundo.h b/app/core/gimpdrawablemodundo.h
new file mode 100644
index 0000000..30bf5a0
--- /dev/null
+++ b/app/core/gimpdrawablemodundo.h
@@ -0,0 +1,55 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_DRAWABLE_MOD_UNDO_H__
+#define __GIMP_DRAWABLE_MOD_UNDO_H__
+
+
+#include "gimpitemundo.h"
+
+
+#define GIMP_TYPE_DRAWABLE_MOD_UNDO (gimp_drawable_mod_undo_get_type ())
+#define GIMP_DRAWABLE_MOD_UNDO(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_DRAWABLE_MOD_UNDO, GimpDrawableModUndo))
+#define GIMP_DRAWABLE_MOD_UNDO_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_DRAWABLE_MOD_UNDO, GimpDrawableModUndoClass))
+#define GIMP_IS_DRAWABLE_MOD_UNDO(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_DRAWABLE_MOD_UNDO))
+#define GIMP_IS_DRAWABLE_MOD_UNDO_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_DRAWABLE_MOD_UNDO))
+#define GIMP_DRAWABLE_MOD_UNDO_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_DRAWABLE_MOD_UNDO, GimpDrawableModUndoClass))
+
+
+typedef struct _GimpDrawableModUndo GimpDrawableModUndo;
+typedef struct _GimpDrawableModUndoClass GimpDrawableModUndoClass;
+
+struct _GimpDrawableModUndo
+{
+ GimpItemUndo parent_instance;
+
+ GeglBuffer *buffer;
+ gboolean copy_buffer;
+ gint offset_x;
+ gint offset_y;
+};
+
+struct _GimpDrawableModUndoClass
+{
+ GimpItemUndoClass parent_class;
+};
+
+
+GType gimp_drawable_mod_undo_get_type (void) G_GNUC_CONST;
+
+
+#endif /* __GIMP_DRAWABLE_MOD_UNDO_H__ */
diff --git a/app/core/gimpdrawablestack.c b/app/core/gimpdrawablestack.c
new file mode 100644
index 0000000..c8e706a
--- /dev/null
+++ b/app/core/gimpdrawablestack.c
@@ -0,0 +1,224 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-1997 Spencer Kimball and Peter Mattis
+ *
+ * gimpdrawablestack.c
+ * Copyright (C) 2008 Michael Natterer <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "core-types.h"
+
+#include "gimpdrawable.h"
+#include "gimpdrawablestack.h"
+#include "gimpmarshal.h"
+
+
+enum
+{
+ UPDATE,
+ LAST_SIGNAL
+};
+
+
+/* local function prototypes */
+
+static void gimp_drawable_stack_constructed (GObject *object);
+
+static void gimp_drawable_stack_add (GimpContainer *container,
+ GimpObject *object);
+static void gimp_drawable_stack_remove (GimpContainer *container,
+ GimpObject *object);
+static void gimp_drawable_stack_reorder (GimpContainer *container,
+ GimpObject *object,
+ gint new_index);
+
+static void gimp_drawable_stack_drawable_update (GimpItem *item,
+ gint x,
+ gint y,
+ gint width,
+ gint height,
+ GimpDrawableStack *stack);
+static void gimp_drawable_stack_drawable_active (GimpItem *item,
+ GimpDrawableStack *stack);
+
+
+G_DEFINE_TYPE (GimpDrawableStack, gimp_drawable_stack, GIMP_TYPE_ITEM_STACK)
+
+#define parent_class gimp_drawable_stack_parent_class
+
+static guint stack_signals[LAST_SIGNAL] = { 0 };
+
+
+static void
+gimp_drawable_stack_class_init (GimpDrawableStackClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpContainerClass *container_class = GIMP_CONTAINER_CLASS (klass);
+
+ stack_signals[UPDATE] =
+ g_signal_new ("update",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpDrawableStackClass, update),
+ NULL, NULL,
+ gimp_marshal_VOID__INT_INT_INT_INT,
+ G_TYPE_NONE, 4,
+ G_TYPE_INT,
+ G_TYPE_INT,
+ G_TYPE_INT,
+ G_TYPE_INT);
+
+ object_class->constructed = gimp_drawable_stack_constructed;
+
+ container_class->add = gimp_drawable_stack_add;
+ container_class->remove = gimp_drawable_stack_remove;
+ container_class->reorder = gimp_drawable_stack_reorder;
+}
+
+static void
+gimp_drawable_stack_init (GimpDrawableStack *stack)
+{
+}
+
+static void
+gimp_drawable_stack_constructed (GObject *object)
+{
+ GimpContainer *container = GIMP_CONTAINER (object);
+
+ G_OBJECT_CLASS (parent_class)->constructed (object);
+
+ gimp_assert (g_type_is_a (gimp_container_get_children_type (container),
+ GIMP_TYPE_DRAWABLE));
+
+ gimp_container_add_handler (container, "update",
+ G_CALLBACK (gimp_drawable_stack_drawable_update),
+ container);
+ gimp_container_add_handler (container, "active-changed",
+ G_CALLBACK (gimp_drawable_stack_drawable_active),
+ container);
+}
+
+static void
+gimp_drawable_stack_add (GimpContainer *container,
+ GimpObject *object)
+{
+ GimpDrawableStack *stack = GIMP_DRAWABLE_STACK (container);
+
+ GIMP_CONTAINER_CLASS (parent_class)->add (container, object);
+
+ if (gimp_filter_get_active (GIMP_FILTER (object)))
+ gimp_drawable_stack_drawable_active (GIMP_ITEM (object), stack);
+}
+
+static void
+gimp_drawable_stack_remove (GimpContainer *container,
+ GimpObject *object)
+{
+ GimpDrawableStack *stack = GIMP_DRAWABLE_STACK (container);
+
+ GIMP_CONTAINER_CLASS (parent_class)->remove (container, object);
+
+ if (gimp_filter_get_active (GIMP_FILTER (object)))
+ gimp_drawable_stack_drawable_active (GIMP_ITEM (object), stack);
+}
+
+static void
+gimp_drawable_stack_reorder (GimpContainer *container,
+ GimpObject *object,
+ gint new_index)
+{
+ GimpDrawableStack *stack = GIMP_DRAWABLE_STACK (container);
+
+ GIMP_CONTAINER_CLASS (parent_class)->reorder (container, object, new_index);
+
+ if (gimp_filter_get_active (GIMP_FILTER (object)))
+ gimp_drawable_stack_drawable_active (GIMP_ITEM (object), stack);
+}
+
+
+/* public functions */
+
+GimpContainer *
+gimp_drawable_stack_new (GType drawable_type)
+{
+ g_return_val_if_fail (g_type_is_a (drawable_type, GIMP_TYPE_DRAWABLE), NULL);
+
+ return g_object_new (GIMP_TYPE_DRAWABLE_STACK,
+ "name", g_type_name (drawable_type),
+ "children-type", drawable_type,
+ "policy", GIMP_CONTAINER_POLICY_STRONG,
+ NULL);
+}
+
+
+/* protected functions */
+
+void
+gimp_drawable_stack_update (GimpDrawableStack *stack,
+ gint x,
+ gint y,
+ gint width,
+ gint height)
+{
+ g_return_if_fail (GIMP_IS_DRAWABLE_STACK (stack));
+
+ g_signal_emit (stack, stack_signals[UPDATE], 0,
+ x, y, width, height);
+}
+
+
+/* private functions */
+
+static void
+gimp_drawable_stack_drawable_update (GimpItem *item,
+ gint x,
+ gint y,
+ gint width,
+ gint height,
+ GimpDrawableStack *stack)
+{
+ if (gimp_filter_get_active (GIMP_FILTER (item)))
+ {
+ gint offset_x;
+ gint offset_y;
+
+ gimp_item_get_offset (item, &offset_x, &offset_y);
+
+ gimp_drawable_stack_update (stack,
+ x + offset_x, y + offset_y,
+ width, height);
+ }
+}
+
+static void
+gimp_drawable_stack_drawable_active (GimpItem *item,
+ GimpDrawableStack *stack)
+{
+ GeglRectangle bounding_box;
+
+ bounding_box = gimp_drawable_get_bounding_box (GIMP_DRAWABLE (item));
+
+ bounding_box.x += gimp_item_get_offset_x (item);
+ bounding_box.y += gimp_item_get_offset_y (item);
+
+ gimp_drawable_stack_update (stack,
+ bounding_box.x, bounding_box.y,
+ bounding_box.width, bounding_box.height);
+}
diff --git a/app/core/gimpdrawablestack.h b/app/core/gimpdrawablestack.h
new file mode 100644
index 0000000..28ee7c4
--- /dev/null
+++ b/app/core/gimpdrawablestack.h
@@ -0,0 +1,66 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-1997 Spencer Kimball and Peter Mattis
+ *
+ * gimpdrawablestack.h
+ * Copyright (C) 2008 Michael Natterer <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_DRAWABLE_STACK_H__
+#define __GIMP_DRAWABLE_STACK_H__
+
+#include "gimpitemstack.h"
+
+
+#define GIMP_TYPE_DRAWABLE_STACK (gimp_drawable_stack_get_type ())
+#define GIMP_DRAWABLE_STACK(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_DRAWABLE_STACK, GimpDrawableStack))
+#define GIMP_DRAWABLE_STACK_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_DRAWABLE_STACK, GimpDrawableStackClass))
+#define GIMP_IS_DRAWABLE_STACK(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_DRAWABLE_STACK))
+#define GIMP_IS_DRAWABLE_STACK_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_DRAWABLE_STACK))
+
+
+typedef struct _GimpDrawableStackClass GimpDrawableStackClass;
+
+struct _GimpDrawableStack
+{
+ GimpItemStack parent_instance;
+};
+
+struct _GimpDrawableStackClass
+{
+ GimpItemStackClass parent_class;
+
+ void (* update) (GimpDrawableStack *stack,
+ gint x,
+ gint y,
+ gint width,
+ gint height);
+};
+
+
+GType gimp_drawable_stack_get_type (void) G_GNUC_CONST;
+GimpContainer * gimp_drawable_stack_new (GType drawable_type);
+
+
+/* protected */
+
+void gimp_drawable_stack_update (GimpDrawableStack *stack,
+ gint x,
+ gint y,
+ gint width,
+ gint height);
+
+
+#endif /* __GIMP_DRAWABLE_STACK_H__ */
diff --git a/app/core/gimpdrawableundo.c b/app/core/gimpdrawableundo.c
new file mode 100644
index 0000000..78b8f39
--- /dev/null
+++ b/app/core/gimpdrawableundo.c
@@ -0,0 +1,207 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpbase/gimpbase.h"
+
+#include "core-types.h"
+
+#include "gimp-memsize.h"
+#include "gimpimage.h"
+#include "gimpdrawable.h"
+#include "gimpdrawableundo.h"
+
+
+enum
+{
+ PROP_0,
+ PROP_BUFFER,
+ PROP_X,
+ PROP_Y
+};
+
+
+static void gimp_drawable_undo_constructed (GObject *object);
+static void gimp_drawable_undo_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_drawable_undo_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static gint64 gimp_drawable_undo_get_memsize (GimpObject *object,
+ gint64 *gui_size);
+
+static void gimp_drawable_undo_pop (GimpUndo *undo,
+ GimpUndoMode undo_mode,
+ GimpUndoAccumulator *accum);
+static void gimp_drawable_undo_free (GimpUndo *undo,
+ GimpUndoMode undo_mode);
+
+
+G_DEFINE_TYPE (GimpDrawableUndo, gimp_drawable_undo, GIMP_TYPE_ITEM_UNDO)
+
+#define parent_class gimp_drawable_undo_parent_class
+
+
+static void
+gimp_drawable_undo_class_init (GimpDrawableUndoClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpObjectClass *gimp_object_class = GIMP_OBJECT_CLASS (klass);
+ GimpUndoClass *undo_class = GIMP_UNDO_CLASS (klass);
+
+ object_class->constructed = gimp_drawable_undo_constructed;
+ object_class->set_property = gimp_drawable_undo_set_property;
+ object_class->get_property = gimp_drawable_undo_get_property;
+
+ gimp_object_class->get_memsize = gimp_drawable_undo_get_memsize;
+
+ undo_class->pop = gimp_drawable_undo_pop;
+ undo_class->free = gimp_drawable_undo_free;
+
+ g_object_class_install_property (object_class, PROP_BUFFER,
+ g_param_spec_object ("buffer", NULL, NULL,
+ GEGL_TYPE_BUFFER,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+
+ g_object_class_install_property (object_class, PROP_X,
+ g_param_spec_int ("x", NULL, NULL,
+ 0, GIMP_MAX_IMAGE_SIZE, 0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+
+ g_object_class_install_property (object_class, PROP_Y,
+ g_param_spec_int ("y", NULL, NULL,
+ 0, GIMP_MAX_IMAGE_SIZE, 0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+}
+
+static void
+gimp_drawable_undo_init (GimpDrawableUndo *undo)
+{
+}
+
+static void
+gimp_drawable_undo_constructed (GObject *object)
+{
+ GimpDrawableUndo *drawable_undo = GIMP_DRAWABLE_UNDO (object);
+
+ G_OBJECT_CLASS (parent_class)->constructed (object);
+
+ gimp_assert (GIMP_IS_DRAWABLE (GIMP_ITEM_UNDO (object)->item));
+ gimp_assert (GEGL_IS_BUFFER (drawable_undo->buffer));
+}
+
+static void
+gimp_drawable_undo_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpDrawableUndo *drawable_undo = GIMP_DRAWABLE_UNDO (object);
+
+ switch (property_id)
+ {
+ case PROP_BUFFER:
+ drawable_undo->buffer = g_value_dup_object (value);
+ break;
+ case PROP_X:
+ drawable_undo->x = g_value_get_int (value);
+ break;
+ case PROP_Y:
+ drawable_undo->y = g_value_get_int (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_drawable_undo_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpDrawableUndo *drawable_undo = GIMP_DRAWABLE_UNDO (object);
+
+ switch (property_id)
+ {
+ case PROP_BUFFER:
+ g_value_set_object (value, drawable_undo->buffer);
+ break;
+ case PROP_X:
+ g_value_set_int (value, drawable_undo->x);
+ break;
+ case PROP_Y:
+ g_value_set_int (value, drawable_undo->y);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static gint64
+gimp_drawable_undo_get_memsize (GimpObject *object,
+ gint64 *gui_size)
+{
+ GimpDrawableUndo *drawable_undo = GIMP_DRAWABLE_UNDO (object);
+ gint64 memsize = 0;
+
+ memsize += gimp_gegl_buffer_get_memsize (drawable_undo->buffer);
+
+ return memsize + GIMP_OBJECT_CLASS (parent_class)->get_memsize (object,
+ gui_size);
+}
+
+static void
+gimp_drawable_undo_pop (GimpUndo *undo,
+ GimpUndoMode undo_mode,
+ GimpUndoAccumulator *accum)
+{
+ GimpDrawableUndo *drawable_undo = GIMP_DRAWABLE_UNDO (undo);
+
+ GIMP_UNDO_CLASS (parent_class)->pop (undo, undo_mode, accum);
+
+ gimp_drawable_swap_pixels (GIMP_DRAWABLE (GIMP_ITEM_UNDO (undo)->item),
+ drawable_undo->buffer,
+ drawable_undo->x,
+ drawable_undo->y);
+}
+
+static void
+gimp_drawable_undo_free (GimpUndo *undo,
+ GimpUndoMode undo_mode)
+{
+ GimpDrawableUndo *drawable_undo = GIMP_DRAWABLE_UNDO (undo);
+
+ g_clear_object (&drawable_undo->buffer);
+
+ GIMP_UNDO_CLASS (parent_class)->free (undo, undo_mode);
+}
diff --git a/app/core/gimpdrawableundo.h b/app/core/gimpdrawableundo.h
new file mode 100644
index 0000000..5d7269e
--- /dev/null
+++ b/app/core/gimpdrawableundo.h
@@ -0,0 +1,54 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_DRAWABLE_UNDO_H__
+#define __GIMP_DRAWABLE_UNDO_H__
+
+
+#include "gimpitemundo.h"
+
+
+#define GIMP_TYPE_DRAWABLE_UNDO (gimp_drawable_undo_get_type ())
+#define GIMP_DRAWABLE_UNDO(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_DRAWABLE_UNDO, GimpDrawableUndo))
+#define GIMP_DRAWABLE_UNDO_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_DRAWABLE_UNDO, GimpDrawableUndoClass))
+#define GIMP_IS_DRAWABLE_UNDO(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_DRAWABLE_UNDO))
+#define GIMP_IS_DRAWABLE_UNDO_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_DRAWABLE_UNDO))
+#define GIMP_DRAWABLE_UNDO_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_DRAWABLE_UNDO, GimpDrawableUndoClass))
+
+
+typedef struct _GimpDrawableUndo GimpDrawableUndo;
+typedef struct _GimpDrawableUndoClass GimpDrawableUndoClass;
+
+struct _GimpDrawableUndo
+{
+ GimpItemUndo parent_instance;
+
+ GeglBuffer *buffer;
+ gint x;
+ gint y;
+};
+
+struct _GimpDrawableUndoClass
+{
+ GimpItemUndoClass parent_class;
+};
+
+
+GType gimp_drawable_undo_get_type (void) G_GNUC_CONST;
+
+
+#endif /* __GIMP_DRAWABLE_UNDO_H__ */
diff --git a/app/core/gimpdynamics-load.c b/app/core/gimpdynamics-load.c
new file mode 100644
index 0000000..36c9f72
--- /dev/null
+++ b/app/core/gimpdynamics-load.c
@@ -0,0 +1,55 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpconfig/gimpconfig.h"
+
+#include "core-types.h"
+
+#include "gimpdynamics.h"
+#include "gimpdynamics-load.h"
+
+
+GList *
+gimp_dynamics_load (GimpContext *context,
+ GFile *file,
+ GInputStream *input,
+ GError **error)
+{
+ GimpDynamics *dynamics;
+
+ g_return_val_if_fail (G_IS_FILE (file), NULL);
+ g_return_val_if_fail (G_IS_INPUT_STREAM (input), NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ dynamics = g_object_new (GIMP_TYPE_DYNAMICS, NULL);
+
+ if (gimp_config_deserialize_stream (GIMP_CONFIG (dynamics),
+ input,
+ NULL, error))
+ {
+ return g_list_prepend (NULL, dynamics);
+ }
+
+ g_object_unref (dynamics);
+
+ return NULL;
+}
diff --git a/app/core/gimpdynamics-load.h b/app/core/gimpdynamics-load.h
new file mode 100644
index 0000000..374c6ed
--- /dev/null
+++ b/app/core/gimpdynamics-load.h
@@ -0,0 +1,31 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_DYNAMICS_LOAD_H__
+#define __GIMP_DYNAMICS_LOAD_H__
+
+
+#define GIMP_DYNAMICS_FILE_EXTENSION ".gdyn"
+
+
+GList * gimp_dynamics_load (GimpContext *context,
+ GFile *file,
+ GInputStream *input,
+ GError **error);
+
+
+#endif /* __GIMP_DYNAMICS_LOAD_H__ */
diff --git a/app/core/gimpdynamics-save.c b/app/core/gimpdynamics-save.c
new file mode 100644
index 0000000..d8bdf3d
--- /dev/null
+++ b/app/core/gimpdynamics-save.c
@@ -0,0 +1,44 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpconfig/gimpconfig.h"
+
+#include "core-types.h"
+
+#include "gimpdynamics.h"
+#include "gimpdynamics-save.h"
+
+
+gboolean
+gimp_dynamics_save (GimpData *data,
+ GOutputStream *output,
+ GError **error)
+{
+ g_return_val_if_fail (GIMP_IS_DYNAMICS (data), FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ return gimp_config_serialize_to_stream (GIMP_CONFIG (data),
+ output,
+ "GIMP dynamics file",
+ "end of GIMP dynamics file",
+ NULL, error);
+}
diff --git a/app/core/gimpdynamics-save.h b/app/core/gimpdynamics-save.h
new file mode 100644
index 0000000..7fe2df3
--- /dev/null
+++ b/app/core/gimpdynamics-save.h
@@ -0,0 +1,28 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_DYNAMICS_SAVE_H__
+#define __GIMP_DYNAMICS_SAVE_H__
+
+
+/* don't call this function directly, use gimp_data_save() instead */
+gboolean gimp_dynamics_save (GimpData *data,
+ GOutputStream *output,
+ GError **error);
+
+
+#endif /* __GIMP_DYNAMICS_SAVE_H__ */
diff --git a/app/core/gimpdynamics.c b/app/core/gimpdynamics.c
new file mode 100644
index 0000000..a1fb0ef
--- /dev/null
+++ b/app/core/gimpdynamics.c
@@ -0,0 +1,653 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-1999 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpmath/gimpmath.h"
+#include "libgimpconfig/gimpconfig.h"
+
+#include "core-types.h"
+
+#include "gimpcurve.h"
+#include "gimpdynamics.h"
+#include "gimpdynamics-load.h"
+#include "gimpdynamics-save.h"
+#include "gimpdynamicsoutput.h"
+
+#include "gimp-intl.h"
+
+
+#define DEFAULT_NAME "Nameless dynamics"
+
+enum
+{
+ PROP_0,
+
+ PROP_NAME,
+
+ PROP_OPACITY_OUTPUT,
+ PROP_SIZE_OUTPUT,
+ PROP_ANGLE_OUTPUT,
+ PROP_COLOR_OUTPUT,
+ PROP_FORCE_OUTPUT,
+ PROP_HARDNESS_OUTPUT,
+ PROP_ASPECT_RATIO_OUTPUT,
+ PROP_SPACING_OUTPUT,
+ PROP_RATE_OUTPUT,
+ PROP_FLOW_OUTPUT,
+ PROP_JITTER_OUTPUT
+};
+
+
+typedef struct _GimpDynamicsPrivate GimpDynamicsPrivate;
+
+struct _GimpDynamicsPrivate
+{
+ GimpDynamicsOutput *opacity_output;
+ GimpDynamicsOutput *hardness_output;
+ GimpDynamicsOutput *force_output;
+ GimpDynamicsOutput *rate_output;
+ GimpDynamicsOutput *flow_output;
+ GimpDynamicsOutput *size_output;
+ GimpDynamicsOutput *aspect_ratio_output;
+ GimpDynamicsOutput *color_output;
+ GimpDynamicsOutput *angle_output;
+ GimpDynamicsOutput *jitter_output;
+ GimpDynamicsOutput *spacing_output;
+};
+
+#define GET_PRIVATE(output) \
+ ((GimpDynamicsPrivate *) gimp_dynamics_get_instance_private ((GimpDynamics *) (output)))
+
+
+static void gimp_dynamics_finalize (GObject *object);
+static void gimp_dynamics_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_dynamics_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+static void
+ gimp_dynamics_dispatch_properties_changed (GObject *object,
+ guint n_pspecs,
+ GParamSpec **pspecs);
+
+static const gchar * gimp_dynamics_get_extension (GimpData *data);
+static void gimp_dynamics_copy (GimpData *data,
+ GimpData *src_data);
+
+static GimpDynamicsOutput *
+ gimp_dynamics_create_output (GimpDynamics *dynamics,
+ const gchar *name,
+ GimpDynamicsOutputType type);
+static void gimp_dynamics_output_notify (GObject *output,
+ const GParamSpec *pspec,
+ GimpDynamics *dynamics);
+
+
+G_DEFINE_TYPE_WITH_PRIVATE (GimpDynamics, gimp_dynamics, GIMP_TYPE_DATA)
+
+#define parent_class gimp_dynamics_parent_class
+
+
+static void
+gimp_dynamics_class_init (GimpDynamicsClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpDataClass *data_class = GIMP_DATA_CLASS (klass);
+ GimpViewableClass *viewable_class = GIMP_VIEWABLE_CLASS (klass);
+
+ object_class->finalize = gimp_dynamics_finalize;
+ object_class->set_property = gimp_dynamics_set_property;
+ object_class->get_property = gimp_dynamics_get_property;
+ object_class->dispatch_properties_changed = gimp_dynamics_dispatch_properties_changed;
+
+ viewable_class->default_icon_name = "gimp-dynamics";
+
+ data_class->save = gimp_dynamics_save;
+ data_class->get_extension = gimp_dynamics_get_extension;
+ data_class->copy = gimp_dynamics_copy;
+
+ GIMP_CONFIG_PROP_STRING (object_class, PROP_NAME,
+ "name",
+ NULL, NULL,
+ DEFAULT_NAME,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_OBJECT (object_class, PROP_OPACITY_OUTPUT,
+ "opacity-output",
+ NULL, NULL,
+ GIMP_TYPE_DYNAMICS_OUTPUT,
+ GIMP_CONFIG_PARAM_AGGREGATE);
+
+ GIMP_CONFIG_PROP_OBJECT (object_class, PROP_FORCE_OUTPUT,
+ "force-output",
+ NULL, NULL,
+ GIMP_TYPE_DYNAMICS_OUTPUT,
+ GIMP_CONFIG_PARAM_AGGREGATE);
+
+ GIMP_CONFIG_PROP_OBJECT (object_class, PROP_HARDNESS_OUTPUT,
+ "hardness-output",
+ NULL, NULL,
+ GIMP_TYPE_DYNAMICS_OUTPUT,
+ GIMP_CONFIG_PARAM_AGGREGATE);
+
+ GIMP_CONFIG_PROP_OBJECT (object_class, PROP_RATE_OUTPUT,
+ "rate-output",
+ NULL, NULL,
+ GIMP_TYPE_DYNAMICS_OUTPUT,
+ GIMP_CONFIG_PARAM_AGGREGATE);
+
+ GIMP_CONFIG_PROP_OBJECT (object_class, PROP_FLOW_OUTPUT,
+ "flow-output",
+ NULL, NULL,
+ GIMP_TYPE_DYNAMICS_OUTPUT,
+ GIMP_CONFIG_PARAM_AGGREGATE);
+
+ GIMP_CONFIG_PROP_OBJECT (object_class, PROP_SIZE_OUTPUT,
+ "size-output",
+ NULL, NULL,
+ GIMP_TYPE_DYNAMICS_OUTPUT,
+ GIMP_CONFIG_PARAM_AGGREGATE);
+
+ GIMP_CONFIG_PROP_OBJECT (object_class, PROP_ASPECT_RATIO_OUTPUT,
+ "aspect-ratio-output",
+ NULL, NULL,
+ GIMP_TYPE_DYNAMICS_OUTPUT,
+ GIMP_CONFIG_PARAM_AGGREGATE);
+
+ GIMP_CONFIG_PROP_OBJECT (object_class, PROP_COLOR_OUTPUT,
+ "color-output",
+ NULL, NULL,
+ GIMP_TYPE_DYNAMICS_OUTPUT,
+ GIMP_CONFIG_PARAM_AGGREGATE);
+
+ GIMP_CONFIG_PROP_OBJECT (object_class, PROP_ANGLE_OUTPUT,
+ "angle-output",
+ NULL, NULL,
+ GIMP_TYPE_DYNAMICS_OUTPUT,
+ GIMP_CONFIG_PARAM_AGGREGATE);
+
+ GIMP_CONFIG_PROP_OBJECT (object_class, PROP_JITTER_OUTPUT,
+ "jitter-output",
+ NULL, NULL,
+ GIMP_TYPE_DYNAMICS_OUTPUT,
+ GIMP_CONFIG_PARAM_AGGREGATE);
+
+ GIMP_CONFIG_PROP_OBJECT (object_class, PROP_SPACING_OUTPUT,
+ "spacing-output",
+ NULL, NULL,
+ GIMP_TYPE_DYNAMICS_OUTPUT,
+ GIMP_CONFIG_PARAM_AGGREGATE);
+}
+
+static void
+gimp_dynamics_init (GimpDynamics *dynamics)
+{
+ GimpDynamicsPrivate *private = GET_PRIVATE (dynamics);
+
+ private->opacity_output =
+ gimp_dynamics_create_output (dynamics,
+ "opacity-output",
+ GIMP_DYNAMICS_OUTPUT_OPACITY);
+
+ private->force_output =
+ gimp_dynamics_create_output (dynamics,
+ "force-output",
+ GIMP_DYNAMICS_OUTPUT_FORCE);
+
+ private->hardness_output =
+ gimp_dynamics_create_output (dynamics,
+ "hardness-output",
+ GIMP_DYNAMICS_OUTPUT_HARDNESS);
+
+ private->rate_output =
+ gimp_dynamics_create_output (dynamics,
+ "rate-output",
+ GIMP_DYNAMICS_OUTPUT_RATE);
+
+ private->flow_output =
+ gimp_dynamics_create_output (dynamics,
+ "flow-output",
+ GIMP_DYNAMICS_OUTPUT_FLOW);
+
+ private->size_output =
+ gimp_dynamics_create_output (dynamics,
+ "size-output",
+ GIMP_DYNAMICS_OUTPUT_SIZE);
+
+ private->aspect_ratio_output =
+ gimp_dynamics_create_output (dynamics,
+ "aspect-ratio-output",
+ GIMP_DYNAMICS_OUTPUT_ASPECT_RATIO);
+
+ private->color_output =
+ gimp_dynamics_create_output (dynamics,
+ "color-output",
+ GIMP_DYNAMICS_OUTPUT_COLOR);
+
+ private->angle_output =
+ gimp_dynamics_create_output (dynamics,
+ "angle-output",
+ GIMP_DYNAMICS_OUTPUT_ANGLE);
+
+ private->jitter_output =
+ gimp_dynamics_create_output (dynamics,
+ "jitter-output",
+ GIMP_DYNAMICS_OUTPUT_JITTER);
+
+ private->spacing_output =
+ gimp_dynamics_create_output (dynamics,
+ "spacing-output",
+ GIMP_DYNAMICS_OUTPUT_SPACING);
+}
+
+static void
+gimp_dynamics_finalize (GObject *object)
+{
+ GimpDynamicsPrivate *private = GET_PRIVATE (object);
+
+ g_clear_object (&private->opacity_output);
+ g_clear_object (&private->force_output);
+ g_clear_object (&private->hardness_output);
+ g_clear_object (&private->rate_output);
+ g_clear_object (&private->flow_output);
+ g_clear_object (&private->size_output);
+ g_clear_object (&private->aspect_ratio_output);
+ g_clear_object (&private->color_output);
+ g_clear_object (&private->angle_output);
+ g_clear_object (&private->jitter_output);
+ g_clear_object (&private->spacing_output);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gimp_dynamics_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpDynamicsPrivate *private = GET_PRIVATE (object);
+ GimpDynamicsOutput *src_output = NULL;
+ GimpDynamicsOutput *dest_output = NULL;
+
+ switch (property_id)
+ {
+ case PROP_NAME:
+ gimp_object_set_name (GIMP_OBJECT (object), g_value_get_string (value));
+ break;
+
+ case PROP_OPACITY_OUTPUT:
+ src_output = g_value_get_object (value);
+ dest_output = private->opacity_output;
+ break;
+
+ case PROP_FORCE_OUTPUT:
+ src_output = g_value_get_object (value);
+ dest_output = private->force_output;
+ break;
+
+ case PROP_HARDNESS_OUTPUT:
+ src_output = g_value_get_object (value);
+ dest_output = private->hardness_output;
+ break;
+
+ case PROP_RATE_OUTPUT:
+ src_output = g_value_get_object (value);
+ dest_output = private->rate_output;
+ break;
+
+ case PROP_FLOW_OUTPUT:
+ src_output = g_value_get_object (value);
+ dest_output = private->flow_output;
+ break;
+
+ case PROP_SIZE_OUTPUT:
+ src_output = g_value_get_object (value);
+ dest_output = private->size_output;
+ break;
+
+ case PROP_ASPECT_RATIO_OUTPUT:
+ src_output = g_value_get_object (value);
+ dest_output = private->aspect_ratio_output;
+ break;
+
+ case PROP_COLOR_OUTPUT:
+ src_output = g_value_get_object (value);
+ dest_output = private->color_output;
+ break;
+
+ case PROP_ANGLE_OUTPUT:
+ src_output = g_value_get_object (value);
+ dest_output = private->angle_output;
+ break;
+
+ case PROP_JITTER_OUTPUT:
+ src_output = g_value_get_object (value);
+ dest_output = private->jitter_output;
+ break;
+
+ case PROP_SPACING_OUTPUT:
+ src_output = g_value_get_object (value);
+ dest_output = private->spacing_output;
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+
+ if (src_output && dest_output)
+ {
+ gimp_config_copy (GIMP_CONFIG (src_output),
+ GIMP_CONFIG (dest_output),
+ GIMP_CONFIG_PARAM_SERIALIZE);
+ }
+}
+
+static void
+gimp_dynamics_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpDynamicsPrivate *private = GET_PRIVATE (object);
+
+ switch (property_id)
+ {
+ case PROP_NAME:
+ g_value_set_string (value, gimp_object_get_name (object));
+ break;
+
+ case PROP_OPACITY_OUTPUT:
+ g_value_set_object (value, private->opacity_output);
+ break;
+
+ case PROP_FORCE_OUTPUT:
+ g_value_set_object (value, private->force_output);
+ break;
+
+ case PROP_HARDNESS_OUTPUT:
+ g_value_set_object (value, private->hardness_output);
+ break;
+
+ case PROP_RATE_OUTPUT:
+ g_value_set_object (value, private->rate_output);
+ break;
+
+ case PROP_FLOW_OUTPUT:
+ g_value_set_object (value, private->flow_output);
+ break;
+
+ case PROP_SIZE_OUTPUT:
+ g_value_set_object (value, private->size_output);
+ break;
+
+ case PROP_ASPECT_RATIO_OUTPUT:
+ g_value_set_object (value, private->aspect_ratio_output);
+ break;
+
+ case PROP_COLOR_OUTPUT:
+ g_value_set_object (value, private->color_output);
+ break;
+
+ case PROP_ANGLE_OUTPUT:
+ g_value_set_object (value, private->angle_output);
+ break;
+
+ case PROP_JITTER_OUTPUT:
+ g_value_set_object (value, private->jitter_output);
+ break;
+
+ case PROP_SPACING_OUTPUT:
+ g_value_set_object (value, private->spacing_output);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_dynamics_dispatch_properties_changed (GObject *object,
+ guint n_pspecs,
+ GParamSpec **pspecs)
+{
+ gint i;
+
+ G_OBJECT_CLASS (parent_class)->dispatch_properties_changed (object,
+ n_pspecs, pspecs);
+
+ for (i = 0; i < n_pspecs; i++)
+ {
+ if (pspecs[i]->flags & GIMP_CONFIG_PARAM_SERIALIZE)
+ {
+ gimp_data_dirty (GIMP_DATA (object));
+ break;
+ }
+ }
+}
+
+static const gchar *
+gimp_dynamics_get_extension (GimpData *data)
+{
+ return GIMP_DYNAMICS_FILE_EXTENSION;
+}
+
+static void
+gimp_dynamics_copy (GimpData *data,
+ GimpData *src_data)
+{
+ gimp_data_freeze (data);
+
+ gimp_config_copy (GIMP_CONFIG (src_data),
+ GIMP_CONFIG (data), 0);
+
+ gimp_data_thaw (data);
+}
+
+
+/* public functions */
+
+GimpData *
+gimp_dynamics_new (GimpContext *context,
+ const gchar *name)
+{
+ g_return_val_if_fail (name != NULL, NULL);
+ g_return_val_if_fail (name[0] != '\0', NULL);
+
+ return g_object_new (GIMP_TYPE_DYNAMICS,
+ "name", name,
+ NULL);
+}
+
+GimpData *
+gimp_dynamics_get_standard (GimpContext *context)
+{
+ static GimpData *standard_dynamics = NULL;
+
+ if (! standard_dynamics)
+ {
+ standard_dynamics = gimp_dynamics_new (context, "Standard dynamics");
+
+ gimp_data_clean (standard_dynamics);
+ gimp_data_make_internal (standard_dynamics, "gimp-dynamics-standard");
+
+ g_object_add_weak_pointer (G_OBJECT (standard_dynamics),
+ (gpointer *) &standard_dynamics);
+ }
+
+ return standard_dynamics;
+}
+
+GimpDynamicsOutput *
+gimp_dynamics_get_output (GimpDynamics *dynamics,
+ GimpDynamicsOutputType type_id)
+{
+ GimpDynamicsPrivate *private;
+
+ g_return_val_if_fail (GIMP_IS_DYNAMICS (dynamics), NULL);
+
+ private = GET_PRIVATE (dynamics);
+
+ switch (type_id)
+ {
+ case GIMP_DYNAMICS_OUTPUT_OPACITY:
+ return private->opacity_output;
+ break;
+
+ case GIMP_DYNAMICS_OUTPUT_FORCE:
+ return private->force_output;
+ break;
+
+ case GIMP_DYNAMICS_OUTPUT_HARDNESS:
+ return private->hardness_output;
+ break;
+
+ case GIMP_DYNAMICS_OUTPUT_RATE:
+ return private->rate_output;
+ break;
+
+ case GIMP_DYNAMICS_OUTPUT_FLOW:
+ return private->flow_output;
+ break;
+
+ case GIMP_DYNAMICS_OUTPUT_SIZE:
+ return private->size_output;
+ break;
+
+ case GIMP_DYNAMICS_OUTPUT_ASPECT_RATIO:
+ return private->aspect_ratio_output;
+ break;
+
+ case GIMP_DYNAMICS_OUTPUT_COLOR:
+ return private->color_output;
+ break;
+
+ case GIMP_DYNAMICS_OUTPUT_ANGLE:
+ return private->angle_output;
+ break;
+
+ case GIMP_DYNAMICS_OUTPUT_JITTER:
+ return private->jitter_output;
+ break;
+
+ case GIMP_DYNAMICS_OUTPUT_SPACING:
+ return private->spacing_output;
+ break;
+
+ default:
+ g_return_val_if_reached (NULL);
+ break;
+ }
+}
+
+gboolean
+gimp_dynamics_is_output_enabled (GimpDynamics *dynamics,
+ GimpDynamicsOutputType type)
+{
+ GimpDynamicsOutput *output;
+
+ g_return_val_if_fail (GIMP_IS_DYNAMICS (dynamics), FALSE);
+
+ output = gimp_dynamics_get_output (dynamics, type);
+
+ return gimp_dynamics_output_is_enabled (output);
+}
+
+gdouble
+gimp_dynamics_get_linear_value (GimpDynamics *dynamics,
+ GimpDynamicsOutputType type,
+ const GimpCoords *coords,
+ GimpPaintOptions *options,
+ gdouble fade_point)
+{
+ GimpDynamicsOutput *output;
+
+ g_return_val_if_fail (GIMP_IS_DYNAMICS (dynamics), 0.0);
+
+ output = gimp_dynamics_get_output (dynamics, type);
+
+ return gimp_dynamics_output_get_linear_value (output, coords,
+ options, fade_point);
+}
+
+gdouble
+gimp_dynamics_get_angular_value (GimpDynamics *dynamics,
+ GimpDynamicsOutputType type,
+ const GimpCoords *coords,
+ GimpPaintOptions *options,
+ gdouble fade_point)
+{
+ GimpDynamicsOutput *output;
+
+ g_return_val_if_fail (GIMP_IS_DYNAMICS (dynamics), 0.0);
+
+ output = gimp_dynamics_get_output (dynamics, type);
+
+ return gimp_dynamics_output_get_angular_value (output, coords,
+ options, fade_point);
+}
+
+gdouble
+gimp_dynamics_get_aspect_value (GimpDynamics *dynamics,
+ GimpDynamicsOutputType type,
+ const GimpCoords *coords,
+ GimpPaintOptions *options,
+ gdouble fade_point)
+{
+ GimpDynamicsOutput *output;
+
+ g_return_val_if_fail (GIMP_IS_DYNAMICS (dynamics), 0.0);
+
+ output = gimp_dynamics_get_output (dynamics, type);
+
+ return gimp_dynamics_output_get_aspect_value (output, coords,
+ options, fade_point);
+}
+
+
+/* private functions */
+
+static GimpDynamicsOutput *
+gimp_dynamics_create_output (GimpDynamics *dynamics,
+ const gchar *name,
+ GimpDynamicsOutputType type)
+{
+ GimpDynamicsOutput *output = gimp_dynamics_output_new (name, type);
+
+ g_signal_connect (output, "notify",
+ G_CALLBACK (gimp_dynamics_output_notify),
+ dynamics);
+
+ return output;
+}
+
+static void
+gimp_dynamics_output_notify (GObject *output,
+ const GParamSpec *pspec,
+ GimpDynamics *dynamics)
+{
+ g_object_notify (G_OBJECT (dynamics), gimp_object_get_name (output));
+}
diff --git a/app/core/gimpdynamics.h b/app/core/gimpdynamics.h
new file mode 100644
index 0000000..e65fdf6
--- /dev/null
+++ b/app/core/gimpdynamics.h
@@ -0,0 +1,77 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-1999 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_DYNAMICS_H__
+#define __GIMP_DYNAMICS_H__
+
+
+#include "gimpdata.h"
+
+
+#define GIMP_TYPE_DYNAMICS (gimp_dynamics_get_type ())
+#define GIMP_DYNAMICS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_DYNAMICS, GimpDynamics))
+#define GIMP_DYNAMICS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_DYNAMICS, GimpDynamicsClass))
+#define GIMP_IS_DYNAMICS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_DYNAMICS))
+#define GIMP_IS_DYNAMICS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_DYNAMICS))
+#define GIMP_DYNAMICS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_DYNAMICS, GimpDynamicsClass))
+
+
+typedef struct _GimpDynamicsClass GimpDynamicsClass;
+
+struct _GimpDynamics
+{
+ GimpData parent_instance;
+};
+
+struct _GimpDynamicsClass
+{
+ GimpDataClass parent_class;
+};
+
+
+GType gimp_dynamics_get_type (void) G_GNUC_CONST;
+
+GimpData * gimp_dynamics_new (GimpContext *context,
+ const gchar *name);
+GimpData * gimp_dynamics_get_standard (GimpContext *context);
+
+GimpDynamicsOutput * gimp_dynamics_get_output (GimpDynamics *dynamics,
+ GimpDynamicsOutputType type);
+
+gboolean gimp_dynamics_is_output_enabled (GimpDynamics *dynamics,
+ GimpDynamicsOutputType type);
+
+gdouble gimp_dynamics_get_linear_value (GimpDynamics *dynamics,
+ GimpDynamicsOutputType type,
+ const GimpCoords *coords,
+ GimpPaintOptions *options,
+ gdouble fade_point);
+
+gdouble gimp_dynamics_get_angular_value (GimpDynamics *dynamics,
+ GimpDynamicsOutputType type,
+ const GimpCoords *coords,
+ GimpPaintOptions *options,
+ gdouble fade_point);
+
+gdouble gimp_dynamics_get_aspect_value (GimpDynamics *dynamics,
+ GimpDynamicsOutputType type,
+ const GimpCoords *coords,
+ GimpPaintOptions *options,
+ gdouble fade_point);
+
+
+#endif /* __GIMP_DYNAMICS_H__ */
diff --git a/app/core/gimpdynamicsoutput.c b/app/core/gimpdynamicsoutput.c
new file mode 100644
index 0000000..7a4f309
--- /dev/null
+++ b/app/core/gimpdynamicsoutput.c
@@ -0,0 +1,767 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-1999 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpmath/gimpmath.h"
+#include "libgimpconfig/gimpconfig.h"
+
+#include "core-types.h"
+
+#include "paint/gimppaintoptions.h"
+
+
+#include "gimpcurve.h"
+#include "gimpcurve-map.h"
+
+#include "gimpdynamicsoutput.h"
+
+#include "gimp-intl.h"
+
+
+#define DEFAULT_USE_PRESSURE FALSE
+#define DEFAULT_USE_VELOCITY FALSE
+#define DEFAULT_USE_DIRECTION FALSE
+#define DEFAULT_USE_TILT FALSE
+#define DEFAULT_USE_WHEEL FALSE
+#define DEFAULT_USE_RANDOM FALSE
+#define DEFAULT_USE_FADE FALSE
+
+
+enum
+{
+ PROP_0,
+
+ PROP_TYPE,
+ PROP_USE_PRESSURE,
+ PROP_USE_VELOCITY,
+ PROP_USE_DIRECTION,
+ PROP_USE_TILT,
+ PROP_USE_WHEEL,
+ PROP_USE_RANDOM,
+ PROP_USE_FADE,
+ PROP_PRESSURE_CURVE,
+ PROP_VELOCITY_CURVE,
+ PROP_DIRECTION_CURVE,
+ PROP_TILT_CURVE,
+ PROP_WHEEL_CURVE,
+ PROP_RANDOM_CURVE,
+ PROP_FADE_CURVE
+};
+
+
+typedef struct _GimpDynamicsOutputPrivate GimpDynamicsOutputPrivate;
+
+struct _GimpDynamicsOutputPrivate
+{
+ GimpDynamicsOutputType type;
+
+ gboolean use_pressure;
+ gboolean use_velocity;
+ gboolean use_direction;
+ gboolean use_tilt;
+ gboolean use_wheel;
+ gboolean use_random;
+ gboolean use_fade;
+
+ GimpCurve *pressure_curve;
+ GimpCurve *velocity_curve;
+ GimpCurve *direction_curve;
+ GimpCurve *tilt_curve;
+ GimpCurve *wheel_curve;
+ GimpCurve *random_curve;
+ GimpCurve *fade_curve;
+};
+
+#define GET_PRIVATE(output) \
+ ((GimpDynamicsOutputPrivate *) gimp_dynamics_output_get_instance_private ((GimpDynamicsOutput *) (output)))
+
+
+static void gimp_dynamics_output_finalize (GObject *object);
+static void gimp_dynamics_output_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_dynamics_output_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+static void gimp_dynamics_output_copy_curve (GimpCurve *src,
+ GimpCurve *dest);
+
+static GimpCurve *
+ gimp_dynamics_output_create_curve (GimpDynamicsOutput *output,
+ const gchar *name);
+static void gimp_dynamics_output_curve_dirty (GimpCurve *curve,
+ GimpDynamicsOutput *output);
+
+
+G_DEFINE_TYPE_WITH_CODE (GimpDynamicsOutput, gimp_dynamics_output,
+ GIMP_TYPE_OBJECT,
+ G_ADD_PRIVATE (GimpDynamicsOutput)
+ G_IMPLEMENT_INTERFACE (GIMP_TYPE_CONFIG, NULL))
+
+#define parent_class gimp_dynamics_output_parent_class
+
+
+static void
+gimp_dynamics_output_class_init (GimpDynamicsOutputClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = gimp_dynamics_output_finalize;
+ object_class->set_property = gimp_dynamics_output_set_property;
+ object_class->get_property = gimp_dynamics_output_get_property;
+
+ g_object_class_install_property (object_class, PROP_TYPE,
+ g_param_spec_enum ("type", NULL,
+ _("Output type"),
+ GIMP_TYPE_DYNAMICS_OUTPUT_TYPE,
+ GIMP_DYNAMICS_OUTPUT_OPACITY,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_USE_PRESSURE,
+ "use-pressure",
+ NULL, NULL,
+ DEFAULT_USE_PRESSURE,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_USE_VELOCITY,
+ "use-velocity",
+ NULL, NULL,
+ DEFAULT_USE_VELOCITY,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_USE_DIRECTION,
+ "use-direction",
+ NULL, NULL,
+ DEFAULT_USE_DIRECTION,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_USE_TILT,
+ "use-tilt",
+ NULL, NULL,
+ DEFAULT_USE_TILT,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_USE_WHEEL,
+ "use-wheel",
+ NULL, NULL,
+ DEFAULT_USE_TILT,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_USE_RANDOM,
+ "use-random",
+ NULL, NULL,
+ DEFAULT_USE_RANDOM,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_USE_FADE,
+ "use-fade",
+ NULL, NULL,
+ DEFAULT_USE_FADE,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_OBJECT (object_class, PROP_PRESSURE_CURVE,
+ "pressure-curve",
+ NULL, NULL,
+ GIMP_TYPE_CURVE,
+ GIMP_CONFIG_PARAM_AGGREGATE);
+
+ GIMP_CONFIG_PROP_OBJECT (object_class, PROP_VELOCITY_CURVE,
+ "velocity-curve",
+ NULL, NULL,
+ GIMP_TYPE_CURVE,
+ GIMP_CONFIG_PARAM_AGGREGATE);
+
+ GIMP_CONFIG_PROP_OBJECT (object_class, PROP_DIRECTION_CURVE,
+ "direction-curve",
+ NULL, NULL,
+ GIMP_TYPE_CURVE,
+ GIMP_CONFIG_PARAM_AGGREGATE);
+
+ GIMP_CONFIG_PROP_OBJECT (object_class, PROP_TILT_CURVE,
+ "tilt-curve",
+ NULL, NULL,
+ GIMP_TYPE_CURVE,
+ GIMP_CONFIG_PARAM_AGGREGATE);
+
+ GIMP_CONFIG_PROP_OBJECT (object_class, PROP_WHEEL_CURVE,
+ "wheel-curve",
+ NULL, NULL,
+ GIMP_TYPE_CURVE,
+ GIMP_CONFIG_PARAM_AGGREGATE);
+
+ GIMP_CONFIG_PROP_OBJECT (object_class, PROP_RANDOM_CURVE,
+ "random-curve",
+ NULL, NULL,
+ GIMP_TYPE_CURVE,
+ GIMP_CONFIG_PARAM_AGGREGATE);
+
+ GIMP_CONFIG_PROP_OBJECT (object_class, PROP_FADE_CURVE,
+ "fade-curve",
+ NULL, NULL,
+ GIMP_TYPE_CURVE,
+ GIMP_CONFIG_PARAM_AGGREGATE);
+}
+
+static void
+gimp_dynamics_output_init (GimpDynamicsOutput *output)
+{
+ GimpDynamicsOutputPrivate *private = GET_PRIVATE (output);
+
+ private->pressure_curve = gimp_dynamics_output_create_curve (output,
+ "pressure-curve");
+ private->velocity_curve = gimp_dynamics_output_create_curve (output,
+ "velocity-curve");
+ private->direction_curve = gimp_dynamics_output_create_curve (output,
+ "direction-curve");
+ private->tilt_curve = gimp_dynamics_output_create_curve (output,
+ "tilt-curve");
+ private->wheel_curve = gimp_dynamics_output_create_curve (output,
+ "wheel-curve");
+ private->random_curve = gimp_dynamics_output_create_curve (output,
+ "random-curve");
+ private->fade_curve = gimp_dynamics_output_create_curve (output,
+ "fade-curve");
+}
+
+static void
+gimp_dynamics_output_finalize (GObject *object)
+{
+ GimpDynamicsOutputPrivate *private = GET_PRIVATE (object);
+
+ g_clear_object (&private->pressure_curve);
+ g_clear_object (&private->velocity_curve);
+ g_clear_object (&private->direction_curve);
+ g_clear_object (&private->tilt_curve);
+ g_clear_object (&private->wheel_curve);
+ g_clear_object (&private->random_curve);
+ g_clear_object (&private->fade_curve);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gimp_dynamics_output_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpDynamicsOutputPrivate *private = GET_PRIVATE (object);
+
+ switch (property_id)
+ {
+ case PROP_TYPE:
+ private->type = g_value_get_enum (value);
+ break;
+
+ case PROP_USE_PRESSURE:
+ private->use_pressure = g_value_get_boolean (value);
+ break;
+
+ case PROP_USE_VELOCITY:
+ private->use_velocity = g_value_get_boolean (value);
+ break;
+
+ case PROP_USE_DIRECTION:
+ private->use_direction = g_value_get_boolean (value);
+ break;
+
+ case PROP_USE_TILT:
+ private->use_tilt = g_value_get_boolean (value);
+ break;
+
+ case PROP_USE_WHEEL:
+ private->use_wheel = g_value_get_boolean (value);
+ break;
+
+ case PROP_USE_RANDOM:
+ private->use_random = g_value_get_boolean (value);
+ break;
+
+ case PROP_USE_FADE:
+ private->use_fade = g_value_get_boolean (value);
+ break;
+
+ case PROP_PRESSURE_CURVE:
+ gimp_dynamics_output_copy_curve (g_value_get_object (value),
+ private->pressure_curve);
+ break;
+
+ case PROP_VELOCITY_CURVE:
+ gimp_dynamics_output_copy_curve (g_value_get_object (value),
+ private->velocity_curve);
+ break;
+
+ case PROP_DIRECTION_CURVE:
+ gimp_dynamics_output_copy_curve (g_value_get_object (value),
+ private->direction_curve);
+ break;
+
+ case PROP_TILT_CURVE:
+ gimp_dynamics_output_copy_curve (g_value_get_object (value),
+ private->tilt_curve);
+ break;
+
+ case PROP_WHEEL_CURVE:
+ gimp_dynamics_output_copy_curve (g_value_get_object (value),
+ private->wheel_curve);
+ break;
+
+ case PROP_RANDOM_CURVE:
+ gimp_dynamics_output_copy_curve (g_value_get_object (value),
+ private->random_curve);
+ break;
+
+ case PROP_FADE_CURVE:
+ gimp_dynamics_output_copy_curve (g_value_get_object (value),
+ private->fade_curve);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_dynamics_output_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpDynamicsOutputPrivate *private = GET_PRIVATE (object);
+
+ switch (property_id)
+ {
+ case PROP_TYPE:
+ g_value_set_enum (value, private->type);
+ break;
+
+ case PROP_USE_PRESSURE:
+ g_value_set_boolean (value, private->use_pressure);
+ break;
+
+ case PROP_USE_VELOCITY:
+ g_value_set_boolean (value, private->use_velocity);
+ break;
+
+ case PROP_USE_DIRECTION:
+ g_value_set_boolean (value, private->use_direction);
+ break;
+
+ case PROP_USE_TILT:
+ g_value_set_boolean (value, private->use_tilt);
+ break;
+
+ case PROP_USE_WHEEL:
+ g_value_set_boolean (value, private->use_wheel);
+ break;
+
+ case PROP_USE_RANDOM:
+ g_value_set_boolean (value, private->use_random);
+ break;
+
+ case PROP_USE_FADE:
+ g_value_set_boolean (value, private->use_fade);
+ break;
+
+ case PROP_PRESSURE_CURVE:
+ g_value_set_object (value, private->pressure_curve);
+ break;
+
+ case PROP_VELOCITY_CURVE:
+ g_value_set_object (value, private->velocity_curve);
+ break;
+
+ case PROP_DIRECTION_CURVE:
+ g_value_set_object (value, private->direction_curve);
+ break;
+
+ case PROP_TILT_CURVE:
+ g_value_set_object (value, private->tilt_curve);
+ break;
+
+ case PROP_WHEEL_CURVE:
+ g_value_set_object (value, private->wheel_curve);
+ break;
+
+ case PROP_RANDOM_CURVE:
+ g_value_set_object (value, private->random_curve);
+ break;
+
+ case PROP_FADE_CURVE:
+ g_value_set_object (value, private->fade_curve);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+
+/* public functions */
+
+GimpDynamicsOutput *
+gimp_dynamics_output_new (const gchar *name,
+ GimpDynamicsOutputType type)
+{
+ g_return_val_if_fail (name != NULL, NULL);
+
+ return g_object_new (GIMP_TYPE_DYNAMICS_OUTPUT,
+ "name", name,
+ "type", type,
+ NULL);
+}
+
+gboolean
+gimp_dynamics_output_is_enabled (GimpDynamicsOutput *output)
+{
+ GimpDynamicsOutputPrivate *private = GET_PRIVATE (output);
+
+ return (private->use_pressure ||
+ private->use_velocity ||
+ private->use_direction ||
+ private->use_tilt ||
+ private->use_wheel ||
+ private->use_random ||
+ private->use_fade);
+}
+
+gdouble
+gimp_dynamics_output_get_linear_value (GimpDynamicsOutput *output,
+ const GimpCoords *coords,
+ GimpPaintOptions *options,
+ gdouble fade_point)
+{
+ GimpDynamicsOutputPrivate *private = GET_PRIVATE (output);
+ gdouble total = 0.0;
+ gdouble result = 1.0;
+ gint factors = 0;
+
+ if (private->use_pressure)
+ {
+ total += gimp_curve_map_value (private->pressure_curve,
+ coords->pressure);
+ factors++;
+ }
+
+ if (private->use_velocity)
+ {
+ total += gimp_curve_map_value (private->velocity_curve,
+ (1.0 - coords->velocity));
+ factors++;
+ }
+
+ if (private->use_direction)
+ {
+ total += gimp_curve_map_value (private->direction_curve,
+ fmod (coords->direction + 0.5, 1));
+ factors++;
+ }
+
+ if (private->use_tilt)
+ {
+ total += gimp_curve_map_value (private->tilt_curve,
+ (1.0 - sqrt (SQR (coords->xtilt) +
+ SQR (coords->ytilt))));
+ factors++;
+ }
+
+ if (private->use_wheel)
+ {
+ gdouble wheel;
+
+ wheel = coords->wheel;
+
+ total += gimp_curve_map_value (private->wheel_curve, wheel);
+ factors++;
+ }
+
+ if (private->use_random)
+ {
+ total += gimp_curve_map_value (private->random_curve,
+ g_random_double_range (0.0, 1.0));
+ factors++;
+ }
+
+ if (private->use_fade)
+ {
+ total += gimp_curve_map_value (private->fade_curve, fade_point);
+
+ factors++;
+ }
+
+ if (factors > 0)
+ result = total / factors;
+
+#if 0
+ g_printerr ("Dynamics queried(linear). Result: %f, factors: %d, total: %f\n",
+ result, factors, total);
+#endif
+
+ return result;
+}
+
+gdouble
+gimp_dynamics_output_get_angular_value (GimpDynamicsOutput *output,
+ const GimpCoords *coords,
+ GimpPaintOptions *options,
+ gdouble fade_point)
+{
+ GimpDynamicsOutputPrivate *private = GET_PRIVATE (output);
+ gdouble total = 0.0;
+ gdouble result = 0.0; /* angles are additive, so we return zero for no change. */
+ gint factors = 0;
+
+ if (private->use_pressure)
+ {
+ total += gimp_curve_map_value (private->pressure_curve,
+ coords->pressure);
+ factors++;
+ }
+
+ if (private->use_velocity)
+ {
+ total += gimp_curve_map_value (private->velocity_curve,
+ (1.0 - coords->velocity));
+ factors++;
+ }
+
+ if (private->use_direction)
+ {
+ gdouble angle = gimp_curve_map_value (private->direction_curve,
+ coords->direction);
+
+ if (options->brush_lock_to_view)
+ {
+ if (coords->reflect)
+ angle = 0.5 - angle;
+
+ angle -= coords->angle;
+ angle = fmod (fmod (angle, 1.0) + 1.0, 1.0);
+ }
+
+ total += angle;
+ factors++;
+ }
+
+ /* For tilt to make sense, it needs to be converted to an angle, not
+ * just a vector
+ */
+ if (private->use_tilt)
+ {
+ gdouble tilt_x = coords->xtilt;
+ gdouble tilt_y = coords->ytilt;
+ gdouble tilt = 0.0;
+
+ if (tilt_x == 0.0)
+ {
+ if (tilt_y > 0.0)
+ tilt = 0.25;
+ else if (tilt_y < 0.0)
+ tilt = 0.75;
+ else
+ tilt = 0.0;
+ }
+ else
+ {
+ tilt = atan ((- 1.0 * tilt_y) /
+ tilt_x) / (2 * G_PI);
+
+ if (tilt_x > 0.0)
+ tilt = tilt + 0.5;
+ }
+
+ tilt = tilt + 0.5; /* correct the angle, its wrong by 180 degrees */
+
+ while (tilt > 1.0)
+ tilt -= 1.0;
+
+ while (tilt < 0.0)
+ tilt += 1.0;
+
+ total += gimp_curve_map_value (private->tilt_curve, tilt);
+ factors++;
+ }
+
+ if (private->use_wheel)
+ {
+ gdouble angle = 1.0 - fmod(0.5 + coords->wheel, 1);
+
+ total += gimp_curve_map_value (private->wheel_curve, angle);
+ factors++;
+ }
+
+ if (private->use_random)
+ {
+ total += gimp_curve_map_value (private->random_curve,
+ g_random_double_range (0.0, 1.0));
+ factors++;
+ }
+
+ if (private->use_fade)
+ {
+ total += gimp_curve_map_value (private->fade_curve, fade_point);
+
+ factors++;
+ }
+
+ if (factors > 0)
+ result = total / factors;
+
+#if 0
+ g_printerr ("Dynamics queried(angle). Result: %f, factors: %d, total: %f\n",
+ result, factors, total);
+#endif
+
+ return result;
+}
+
+gdouble
+gimp_dynamics_output_get_aspect_value (GimpDynamicsOutput *output,
+ const GimpCoords *coords,
+ GimpPaintOptions *options,
+ gdouble fade_point)
+{
+ GimpDynamicsOutputPrivate *private = GET_PRIVATE (output);
+ gdouble total = 0.0;
+ gint factors = 0;
+ gdouble sign = 1.0;
+ gdouble result = 1.0;
+
+ if (private->use_pressure)
+ {
+ total += gimp_curve_map_value (private->pressure_curve,
+ coords->pressure);
+ factors++;
+ }
+
+ if (private->use_velocity)
+ {
+ total += gimp_curve_map_value (private->velocity_curve,
+ coords->velocity);
+ factors++;
+ }
+
+ if (private->use_direction)
+ {
+ gdouble direction = gimp_curve_map_value (private->direction_curve,
+ coords->direction);
+
+ if (((direction > 0.875) && (direction <= 1.0)) ||
+ ((direction > 0.0) && (direction < 0.125)) ||
+ ((direction > 0.375) && (direction < 0.625)))
+ sign = -1.0;
+
+ total += 1.0;
+ factors++;
+ }
+
+ if (private->use_tilt)
+ {
+ gdouble tilt_value = MAX (fabs (coords->xtilt), fabs (coords->ytilt));
+
+ tilt_value = gimp_curve_map_value (private->tilt_curve,
+ tilt_value);
+
+ total += tilt_value;
+
+ factors++;
+ }
+
+ if (private->use_wheel)
+ {
+ gdouble wheel = gimp_curve_map_value (private->wheel_curve,
+ coords->wheel);
+
+ if (((wheel > 0.875) && (wheel <= 1.0)) ||
+ ((wheel > 0.0) && (wheel < 0.125)) ||
+ ((wheel > 0.375) && (wheel < 0.625)))
+ sign = -1.0;
+
+ total += 1.0;
+ factors++;
+
+ }
+
+ if (private->use_random)
+ {
+ gdouble random = gimp_curve_map_value (private->random_curve,
+ g_random_double_range (0.0, 1.0));
+
+ total += random;
+ factors++;
+ }
+
+ if (private->use_fade)
+ {
+ total += gimp_curve_map_value (private->fade_curve, fade_point);
+
+ factors++;
+ }
+
+ if (factors > 0)
+ result = total / factors;
+
+
+#if 0
+ g_printerr ("Dynamics queried(aspect). Result: %f, factors: %d, total: %f sign: %f\n",
+ result, factors, total, sign);
+#endif
+ result = CLAMP (result * sign, -1.0, 1.0);
+
+ return result;
+}
+
+static void
+gimp_dynamics_output_copy_curve (GimpCurve *src,
+ GimpCurve *dest)
+{
+ if (src && dest)
+ {
+ gimp_config_copy (GIMP_CONFIG (src),
+ GIMP_CONFIG (dest),
+ GIMP_CONFIG_PARAM_SERIALIZE);
+ }
+}
+
+static GimpCurve *
+gimp_dynamics_output_create_curve (GimpDynamicsOutput *output,
+ const gchar *name)
+{
+ GimpCurve *curve = GIMP_CURVE (gimp_curve_new (name));
+
+ g_signal_connect_object (curve, "dirty",
+ G_CALLBACK (gimp_dynamics_output_curve_dirty),
+ output, 0);
+
+ return curve;
+}
+
+static void
+gimp_dynamics_output_curve_dirty (GimpCurve *curve,
+ GimpDynamicsOutput *output)
+{
+ g_object_notify (G_OBJECT (output), gimp_object_get_name (curve));
+}
diff --git a/app/core/gimpdynamicsoutput.h b/app/core/gimpdynamicsoutput.h
new file mode 100644
index 0000000..855cc4a
--- /dev/null
+++ b/app/core/gimpdynamicsoutput.h
@@ -0,0 +1,68 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-1999 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_DYNAMICS_OUTPUT_H__
+#define __GIMP_DYNAMICS_OUTPUT_H__
+
+
+#include "gimpobject.h"
+
+
+#define GIMP_TYPE_DYNAMICS_OUTPUT (gimp_dynamics_output_get_type ())
+#define GIMP_DYNAMICS_OUTPUT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_DYNAMICS_OUTPUT, GimpDynamicsOutput))
+#define GIMP_DYNAMICS_OUTPUT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_DYNAMICS_OUTPUT, GimpDynamicsOutputClass))
+#define GIMP_IS_DYNAMICS_OUTPUT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_DYNAMICS_OUTPUT))
+#define GIMP_IS_DYNAMICS_OUTPUT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_DYNAMICS_OUTPUT))
+#define GIMP_DYNAMICS_OUTPUT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_DYNAMICS_OUTPUT, GimpDynamicsOutputClass))
+
+
+typedef struct _GimpDynamicsOutputClass GimpDynamicsOutputClass;
+
+struct _GimpDynamicsOutput
+{
+ GimpObject parent_instance;
+};
+
+struct _GimpDynamicsOutputClass
+{
+ GimpObjectClass parent_class;
+};
+
+
+GType gimp_dynamics_output_get_type (void) G_GNUC_CONST;
+
+GimpDynamicsOutput * gimp_dynamics_output_new (const gchar *name,
+ GimpDynamicsOutputType type);
+
+gboolean gimp_dynamics_output_is_enabled (GimpDynamicsOutput *output);
+
+gdouble gimp_dynamics_output_get_linear_value (GimpDynamicsOutput *output,
+ const GimpCoords *coords,
+ GimpPaintOptions *options,
+ gdouble fade_point);
+
+gdouble gimp_dynamics_output_get_angular_value (GimpDynamicsOutput *output,
+ const GimpCoords *coords,
+ GimpPaintOptions *options,
+ gdouble fade_point);
+gdouble gimp_dynamics_output_get_aspect_value (GimpDynamicsOutput *output,
+ const GimpCoords *coords,
+ GimpPaintOptions *options,
+ gdouble fade_point);
+
+
+#endif /* __GIMP_DYNAMICS_OUTPUT_H__ */
diff --git a/app/core/gimperror.c b/app/core/gimperror.c
new file mode 100644
index 0000000..29394cb
--- /dev/null
+++ b/app/core/gimperror.c
@@ -0,0 +1,36 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <glib-object.h>
+
+#include "gimperror.h"
+
+
+/**
+ * gimp_error_quark:
+ *
+ * This function is never called directly. Use GIMP_ERROR() instead.
+ *
+ * Return value: the #GQuark that defines the general GIMP error domain.
+ **/
+GQuark
+gimp_error_quark (void)
+{
+ return g_quark_from_static_string ("gimp-error-quark");
+}
diff --git a/app/core/gimperror.h b/app/core/gimperror.h
new file mode 100644
index 0000000..f8088ec
--- /dev/null
+++ b/app/core/gimperror.h
@@ -0,0 +1,33 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_ERROR_H__
+#define __GIMP_ERROR_H__
+
+
+typedef enum
+{
+ GIMP_FAILED, /* generic error condition */
+} GimpErrorCode;
+
+
+#define GIMP_ERROR (gimp_error_quark ())
+
+GQuark gimp_error_quark (void) G_GNUC_CONST;
+
+
+#endif /* __GIMP_ERROR_H__ */
diff --git a/app/core/gimpfilloptions.c b/app/core/gimpfilloptions.c
new file mode 100644
index 0000000..d10b6e4
--- /dev/null
+++ b/app/core/gimpfilloptions.c
@@ -0,0 +1,548 @@
+/* The GIMP -- an image manipulation program
+ * Copyright (C) 1995-1999 Spencer Kimball and Peter Mattis
+ *
+ * gimpfilloptions.c
+ * Copyright (C) 2003 Simon Budig
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <cairo.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpcolor/gimpcolor.h"
+#include "libgimpconfig/gimpconfig.h"
+
+#include "core-types.h"
+
+#include "operations/layer-modes/gimp-layer-modes.h"
+
+#include "gimp.h"
+#include "gimp-palettes.h"
+#include "gimpdrawable.h"
+#include "gimpdrawable-fill.h"
+#include "gimperror.h"
+#include "gimpfilloptions.h"
+#include "gimppattern.h"
+
+#include "gimp-intl.h"
+
+
+enum
+{
+ PROP_0,
+ PROP_STYLE,
+ PROP_ANTIALIAS,
+ PROP_FEATHER,
+ PROP_FEATHER_RADIUS,
+ PROP_PATTERN_VIEW_TYPE,
+ PROP_PATTERN_VIEW_SIZE
+};
+
+
+typedef struct _GimpFillOptionsPrivate GimpFillOptionsPrivate;
+
+struct _GimpFillOptionsPrivate
+{
+ GimpFillStyle style;
+ gboolean antialias;
+ gboolean feather;
+ gdouble feather_radius;
+
+ GimpViewType pattern_view_type;
+ GimpViewSize pattern_view_size;
+
+ const gchar *undo_desc;
+};
+
+#define GET_PRIVATE(options) \
+ ((GimpFillOptionsPrivate *) gimp_fill_options_get_instance_private ((GimpFillOptions *) (options)))
+
+
+static void gimp_fill_options_config_init (GimpConfigInterface *iface);
+
+static void gimp_fill_options_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_fill_options_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static gboolean gimp_fill_options_serialize (GimpConfig *config,
+ GimpConfigWriter *writer,
+ gpointer data);
+
+
+G_DEFINE_TYPE_WITH_CODE (GimpFillOptions, gimp_fill_options, GIMP_TYPE_CONTEXT,
+ G_ADD_PRIVATE (GimpFillOptions)
+ G_IMPLEMENT_INTERFACE (GIMP_TYPE_CONFIG,
+ gimp_fill_options_config_init))
+
+
+static void
+gimp_fill_options_class_init (GimpFillOptionsClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->set_property = gimp_fill_options_set_property;
+ object_class->get_property = gimp_fill_options_get_property;
+
+ GIMP_CONFIG_PROP_ENUM (object_class, PROP_STYLE,
+ "style",
+ _("Style"),
+ NULL,
+ GIMP_TYPE_FILL_STYLE,
+ GIMP_FILL_STYLE_SOLID,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_ANTIALIAS,
+ "antialias",
+ _("Antialiasing"),
+ NULL,
+ TRUE,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_FEATHER,
+ "feather",
+ _("Feather edges"),
+ _("Enable feathering of fill edges"),
+ FALSE,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_DOUBLE (object_class, PROP_FEATHER_RADIUS,
+ "feather-radius",
+ _("Radius"),
+ _("Radius of feathering"),
+ 0.0, 100.0, 10.0,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_property (object_class, PROP_PATTERN_VIEW_TYPE,
+ g_param_spec_enum ("pattern-view-type",
+ NULL, NULL,
+ GIMP_TYPE_VIEW_TYPE,
+ GIMP_VIEW_TYPE_GRID,
+ G_PARAM_CONSTRUCT |
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_PATTERN_VIEW_SIZE,
+ g_param_spec_int ("pattern-view-size",
+ NULL, NULL,
+ GIMP_VIEW_SIZE_TINY,
+ GIMP_VIEWABLE_MAX_BUTTON_SIZE,
+ GIMP_VIEW_SIZE_SMALL,
+ G_PARAM_CONSTRUCT |
+ GIMP_PARAM_READWRITE));
+}
+
+static void
+gimp_fill_options_config_init (GimpConfigInterface *iface)
+{
+ iface->serialize = gimp_fill_options_serialize;
+}
+
+static void
+gimp_fill_options_init (GimpFillOptions *options)
+{
+}
+
+static void
+gimp_fill_options_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpFillOptionsPrivate *private = GET_PRIVATE (object);
+
+ switch (property_id)
+ {
+ case PROP_STYLE:
+ private->style = g_value_get_enum (value);
+ private->undo_desc = NULL;
+ break;
+ case PROP_ANTIALIAS:
+ private->antialias = g_value_get_boolean (value);
+ break;
+ case PROP_FEATHER:
+ private->feather = g_value_get_boolean (value);
+ break;
+ case PROP_FEATHER_RADIUS:
+ private->feather_radius = g_value_get_double (value);
+ break;
+
+ case PROP_PATTERN_VIEW_TYPE:
+ private->pattern_view_type = g_value_get_enum (value);
+ break;
+ case PROP_PATTERN_VIEW_SIZE:
+ private->pattern_view_size = g_value_get_int (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_fill_options_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpFillOptionsPrivate *private = GET_PRIVATE (object);
+
+ switch (property_id)
+ {
+ case PROP_STYLE:
+ g_value_set_enum (value, private->style);
+ break;
+ case PROP_ANTIALIAS:
+ g_value_set_boolean (value, private->antialias);
+ break;
+ case PROP_FEATHER:
+ g_value_set_boolean (value, private->feather);
+ break;
+ case PROP_FEATHER_RADIUS:
+ g_value_set_double (value, private->feather_radius);
+ break;
+
+ case PROP_PATTERN_VIEW_TYPE:
+ g_value_set_enum (value, private->pattern_view_type);
+ break;
+ case PROP_PATTERN_VIEW_SIZE:
+ g_value_set_int (value, private->pattern_view_size);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static gboolean
+gimp_fill_options_serialize (GimpConfig *config,
+ GimpConfigWriter *writer,
+ gpointer data)
+{
+ return gimp_config_serialize_properties (config, writer);
+}
+
+
+/* public functions */
+
+GimpFillOptions *
+gimp_fill_options_new (Gimp *gimp,
+ GimpContext *context,
+ gboolean use_context_color)
+{
+ GimpFillOptions *options;
+
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
+ g_return_val_if_fail (context == NULL || GIMP_IS_CONTEXT (context), NULL);
+ g_return_val_if_fail (use_context_color == FALSE || context != NULL, NULL);
+
+ options = g_object_new (GIMP_TYPE_FILL_OPTIONS,
+ "gimp", gimp,
+ NULL);
+
+ if (use_context_color)
+ {
+ gimp_context_define_properties (GIMP_CONTEXT (options),
+ GIMP_CONTEXT_PROP_MASK_FOREGROUND |
+ GIMP_CONTEXT_PROP_MASK_PATTERN,
+ FALSE);
+
+ gimp_context_set_parent (GIMP_CONTEXT (options), context);
+ }
+
+ return options;
+}
+
+GimpFillStyle
+gimp_fill_options_get_style (GimpFillOptions *options)
+{
+ g_return_val_if_fail (GIMP_IS_FILL_OPTIONS (options), GIMP_FILL_STYLE_SOLID);
+
+ return GET_PRIVATE (options)->style;
+}
+
+void
+gimp_fill_options_set_style (GimpFillOptions *options,
+ GimpFillStyle style)
+{
+ g_return_if_fail (GIMP_IS_FILL_OPTIONS (options));
+
+ g_object_set (options, "style", style, NULL);
+}
+
+gboolean
+gimp_fill_options_get_antialias (GimpFillOptions *options)
+{
+ g_return_val_if_fail (GIMP_IS_FILL_OPTIONS (options), FALSE);
+
+ return GET_PRIVATE (options)->antialias;
+}
+
+void
+gimp_fill_options_set_antialias (GimpFillOptions *options,
+ gboolean antialias)
+{
+ g_return_if_fail (GIMP_IS_FILL_OPTIONS (options));
+
+ g_object_set (options, "antialias", antialias, NULL);
+}
+
+gboolean
+gimp_fill_options_get_feather (GimpFillOptions *options,
+ gdouble *radius)
+{
+ g_return_val_if_fail (GIMP_IS_FILL_OPTIONS (options), FALSE);
+
+ if (radius)
+ *radius = GET_PRIVATE (options)->feather_radius;
+
+ return GET_PRIVATE (options)->feather;
+}
+
+void
+gimp_fill_options_set_feather (GimpFillOptions *options,
+ gboolean feather,
+ gdouble radius)
+{
+ g_return_if_fail (GIMP_IS_FILL_OPTIONS (options));
+
+ g_object_set (options, "feather", feather, NULL);
+ g_object_set (options, "feather-radius", radius, NULL);
+}
+
+gboolean
+gimp_fill_options_set_by_fill_type (GimpFillOptions *options,
+ GimpContext *context,
+ GimpFillType fill_type,
+ GError **error)
+{
+ GimpFillOptionsPrivate *private;
+ GimpRGB color;
+ const gchar *undo_desc;
+
+ g_return_val_if_fail (GIMP_IS_FILL_OPTIONS (options), FALSE);
+ g_return_val_if_fail (GIMP_IS_CONTEXT (context), FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ private = GET_PRIVATE (options);
+
+ private->undo_desc = NULL;
+
+ switch (fill_type)
+ {
+ case GIMP_FILL_FOREGROUND:
+ gimp_context_get_foreground (context, &color);
+ undo_desc = C_("undo-type", "Fill with Foreground Color");
+ break;
+
+ case GIMP_FILL_BACKGROUND:
+ gimp_context_get_background (context, &color);
+ undo_desc = C_("undo-type", "Fill with Background Color");
+ break;
+
+ case GIMP_FILL_WHITE:
+ gimp_rgba_set (&color, 1.0, 1.0, 1.0, GIMP_OPACITY_OPAQUE);
+ undo_desc = C_("undo-type", "Fill with White");
+ break;
+
+ case GIMP_FILL_TRANSPARENT:
+ gimp_context_get_background (context, &color);
+ gimp_context_set_paint_mode (GIMP_CONTEXT (options),
+ GIMP_LAYER_MODE_ERASE);
+ undo_desc = C_("undo-type", "Fill with Transparency");
+ break;
+
+ case GIMP_FILL_PATTERN:
+ {
+ GimpPattern *pattern = gimp_context_get_pattern (context);
+
+ if (! pattern)
+ {
+ g_set_error_literal (error, GIMP_ERROR, GIMP_FAILED,
+ _("No patterns available for this operation."));
+ return FALSE;
+ }
+
+ gimp_fill_options_set_style (options, GIMP_FILL_STYLE_PATTERN);
+ gimp_context_set_pattern (GIMP_CONTEXT (options), pattern);
+ private->undo_desc = C_("undo-type", "Fill with Pattern");
+
+ return TRUE;
+ }
+ break;
+
+ default:
+ g_warning ("%s: invalid fill_type %d", G_STRFUNC, fill_type);
+ return FALSE;
+ }
+
+ gimp_fill_options_set_style (options, GIMP_FILL_STYLE_SOLID);
+ gimp_context_set_foreground (GIMP_CONTEXT (options), &color);
+ private->undo_desc = undo_desc;
+
+ return TRUE;
+}
+
+gboolean
+gimp_fill_options_set_by_fill_mode (GimpFillOptions *options,
+ GimpContext *context,
+ GimpBucketFillMode fill_mode,
+ GError **error)
+{
+ GimpFillType fill_type;
+
+ g_return_val_if_fail (GIMP_IS_FILL_OPTIONS (options), FALSE);
+ g_return_val_if_fail (GIMP_IS_CONTEXT (context), FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ switch (fill_mode)
+ {
+ default:
+ case GIMP_BUCKET_FILL_FG:
+ fill_type = GIMP_FILL_FOREGROUND;
+ break;
+
+ case GIMP_BUCKET_FILL_BG:
+ fill_type = GIMP_FILL_BACKGROUND;
+ break;
+
+ case GIMP_BUCKET_FILL_PATTERN:
+ fill_type = GIMP_FILL_PATTERN;
+ break;
+ }
+
+ return gimp_fill_options_set_by_fill_type (options, context,
+ fill_type, error);
+}
+
+const gchar *
+gimp_fill_options_get_undo_desc (GimpFillOptions *options)
+{
+ GimpFillOptionsPrivate *private;
+
+ g_return_val_if_fail (GIMP_IS_FILL_OPTIONS (options), NULL);
+
+ private = GET_PRIVATE (options);
+
+ if (private->undo_desc)
+ return private->undo_desc;
+
+ switch (private->style)
+ {
+ case GIMP_FILL_STYLE_SOLID:
+ return C_("undo-type", "Fill with Solid Color");
+
+ case GIMP_FILL_STYLE_PATTERN:
+ return C_("undo-type", "Fill with Pattern");
+ }
+
+ g_return_val_if_reached (NULL);
+}
+
+const Babl *
+gimp_fill_options_get_format (GimpFillOptions *options,
+ GimpDrawable *drawable)
+{
+ GimpContext *context;
+
+ g_return_val_if_fail (GIMP_IS_FILL_OPTIONS (options), NULL);
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), NULL);
+
+ context = GIMP_CONTEXT (options);
+
+ return gimp_layer_mode_get_format (gimp_context_get_paint_mode (context),
+ GIMP_LAYER_COLOR_SPACE_AUTO,
+ GIMP_LAYER_COLOR_SPACE_AUTO,
+ gimp_layer_mode_get_paint_composite_mode (
+ gimp_context_get_paint_mode (context)),
+ gimp_drawable_get_format (drawable));
+}
+
+GeglBuffer *
+gimp_fill_options_create_buffer (GimpFillOptions *options,
+ GimpDrawable *drawable,
+ const GeglRectangle *rect,
+ gint pattern_offset_x,
+ gint pattern_offset_y)
+{
+ GeglBuffer *buffer;
+
+ g_return_val_if_fail (GIMP_IS_FILL_OPTIONS (options), NULL);
+ g_return_val_if_fail (gimp_fill_options_get_style (options) !=
+ GIMP_FILL_STYLE_PATTERN ||
+ gimp_context_get_pattern (GIMP_CONTEXT (options)) != NULL,
+ NULL);
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), NULL);
+ g_return_val_if_fail (rect != NULL, NULL);
+
+ buffer = gegl_buffer_new (rect,
+ gimp_fill_options_get_format (options, drawable));
+
+ gimp_fill_options_fill_buffer (options, drawable, buffer,
+ pattern_offset_x, pattern_offset_y);
+
+ return buffer;
+}
+
+void
+gimp_fill_options_fill_buffer (GimpFillOptions *options,
+ GimpDrawable *drawable,
+ GeglBuffer *buffer,
+ gint pattern_offset_x,
+ gint pattern_offset_y)
+{
+ g_return_if_fail (GIMP_IS_FILL_OPTIONS (options));
+ g_return_if_fail (gimp_fill_options_get_style (options) !=
+ GIMP_FILL_STYLE_PATTERN ||
+ gimp_context_get_pattern (GIMP_CONTEXT (options)) != NULL);
+ g_return_if_fail (GIMP_IS_DRAWABLE (drawable));
+ g_return_if_fail (GEGL_IS_BUFFER (buffer));
+
+ switch (gimp_fill_options_get_style (options))
+ {
+ case GIMP_FILL_STYLE_SOLID:
+ {
+ GimpRGB color;
+
+ gimp_context_get_foreground (GIMP_CONTEXT (options), &color);
+ gimp_palettes_add_color_history (GIMP_CONTEXT (options)->gimp, &color);
+
+ gimp_drawable_fill_buffer (drawable, buffer,
+ &color, NULL, 0, 0);
+ }
+ break;
+
+ case GIMP_FILL_STYLE_PATTERN:
+ {
+ GimpPattern *pattern;
+
+ pattern = gimp_context_get_pattern (GIMP_CONTEXT (options));
+
+ gimp_drawable_fill_buffer (drawable, buffer,
+ NULL, pattern,
+ pattern_offset_x,
+ pattern_offset_y);
+ }
+ break;
+ }
+}
diff --git a/app/core/gimpfilloptions.h b/app/core/gimpfilloptions.h
new file mode 100644
index 0000000..434530b
--- /dev/null
+++ b/app/core/gimpfilloptions.h
@@ -0,0 +1,95 @@
+/* The GIMP -- an image manipulation program
+ * Copyright (C) 1995-1999 Spencer Kimball and Peter Mattis
+ *
+ * gimpfilloptions.h
+ * Copyright (C) 2003 Simon Budig
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_FILL_OPTIONS_H__
+#define __GIMP_FILL_OPTIONS_H__
+
+
+#include "gimpcontext.h"
+
+
+#define GIMP_TYPE_FILL_OPTIONS (gimp_fill_options_get_type ())
+#define GIMP_FILL_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_FILL_OPTIONS, GimpFillOptions))
+#define GIMP_FILL_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_FILL_OPTIONS, GimpFillOptionsClass))
+#define GIMP_IS_FILL_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_FILL_OPTIONS))
+#define GIMP_IS_FILL_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_FILL_OPTIONS))
+#define GIMP_FILL_OPTIONS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_FILL_OPTIONS, GimpFillOptionsClass))
+
+
+typedef struct _GimpFillOptionsClass GimpFillOptionsClass;
+
+struct _GimpFillOptions
+{
+ GimpContext parent_instance;
+};
+
+struct _GimpFillOptionsClass
+{
+ GimpContextClass parent_class;
+};
+
+
+GType gimp_fill_options_get_type (void) G_GNUC_CONST;
+
+GimpFillOptions * gimp_fill_options_new (Gimp *gimp,
+ GimpContext *context,
+ gboolean use_context_color);
+
+GimpFillStyle gimp_fill_options_get_style (GimpFillOptions *options);
+void gimp_fill_options_set_style (GimpFillOptions *options,
+ GimpFillStyle style);
+
+gboolean gimp_fill_options_get_antialias (GimpFillOptions *options);
+void gimp_fill_options_set_antialias (GimpFillOptions *options,
+ gboolean antialias);
+
+gboolean gimp_fill_options_get_feather (GimpFillOptions *options,
+ gdouble *radius);
+void gimp_fill_options_set_feather (GimpFillOptions *options,
+ gboolean feather,
+ gdouble radius);
+
+gboolean gimp_fill_options_set_by_fill_type (GimpFillOptions *options,
+ GimpContext *context,
+ GimpFillType fill_type,
+ GError **error);
+gboolean gimp_fill_options_set_by_fill_mode (GimpFillOptions *options,
+ GimpContext *context,
+ GimpBucketFillMode fill_mode,
+ GError **error);
+
+const gchar * gimp_fill_options_get_undo_desc (GimpFillOptions *options);
+
+const Babl * gimp_fill_options_get_format (GimpFillOptions *options,
+ GimpDrawable *drawable);
+
+GeglBuffer * gimp_fill_options_create_buffer (GimpFillOptions *options,
+ GimpDrawable *drawable,
+ const GeglRectangle *rect,
+ gint pattern_offset_x,
+ gint pattern_offset_y);
+void gimp_fill_options_fill_buffer (GimpFillOptions *options,
+ GimpDrawable *drawable,
+ GeglBuffer *buffer,
+ gint pattern_offset_x,
+ gint pattern_offset_y);
+
+
+#endif /* __GIMP_FILL_OPTIONS_H__ */
diff --git a/app/core/gimpfilter.c b/app/core/gimpfilter.c
new file mode 100644
index 0000000..f565d49
--- /dev/null
+++ b/app/core/gimpfilter.c
@@ -0,0 +1,315 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpfilter.c
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "core-types.h"
+
+#include "gimp.h"
+#include "gimp-memsize.h"
+#include "gimpfilter.h"
+#include "gimpmarshal.h"
+
+
+enum
+{
+ ACTIVE_CHANGED,
+ LAST_SIGNAL
+};
+
+enum
+{
+ PROP_0,
+ PROP_ACTIVE,
+ PROP_IS_LAST_NODE
+};
+
+
+typedef struct _GimpFilterPrivate GimpFilterPrivate;
+
+struct _GimpFilterPrivate
+{
+ GeglNode *node;
+
+ guint active : 1;
+ guint is_last_node : 1;
+
+ GimpApplicator *applicator;
+};
+
+#define GET_PRIVATE(filter) ((GimpFilterPrivate *) gimp_filter_get_instance_private ((GimpFilter *) (filter)))
+
+
+/* local function prototypes */
+
+static void gimp_filter_finalize (GObject *object);
+static void gimp_filter_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_filter_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static gint64 gimp_filter_get_memsize (GimpObject *object,
+ gint64 *gui_size);
+
+static GeglNode * gimp_filter_real_get_node (GimpFilter *filter);
+
+
+G_DEFINE_TYPE_WITH_PRIVATE (GimpFilter, gimp_filter, GIMP_TYPE_VIEWABLE)
+
+#define parent_class gimp_filter_parent_class
+
+static guint gimp_filter_signals[LAST_SIGNAL] = { 0 };
+
+
+static void
+gimp_filter_class_init (GimpFilterClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpObjectClass *gimp_object_class = GIMP_OBJECT_CLASS (klass);
+
+ gimp_filter_signals[ACTIVE_CHANGED] =
+ g_signal_new ("active-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpFilterClass, active_changed),
+ NULL, NULL,
+ gimp_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ object_class->finalize = gimp_filter_finalize;
+ object_class->set_property = gimp_filter_set_property;
+ object_class->get_property = gimp_filter_get_property;
+
+ gimp_object_class->get_memsize = gimp_filter_get_memsize;
+
+ klass->active_changed = NULL;
+ klass->get_node = gimp_filter_real_get_node;
+
+ g_object_class_install_property (object_class, PROP_ACTIVE,
+ g_param_spec_boolean ("active", NULL, NULL,
+ TRUE,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_IS_LAST_NODE,
+ g_param_spec_boolean ("is-last-node",
+ NULL, NULL,
+ FALSE,
+ GIMP_PARAM_READWRITE));
+}
+
+static void
+gimp_filter_init (GimpFilter *filter)
+{
+ GimpFilterPrivate *private = GET_PRIVATE (filter);
+
+ private->active = TRUE;
+}
+
+static void
+gimp_filter_finalize (GObject *object)
+{
+ GimpFilterPrivate *private = GET_PRIVATE (object);
+
+ g_clear_object (&private->node);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gimp_filter_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpFilter *filter = GIMP_FILTER (object);
+
+ switch (property_id)
+ {
+ case PROP_ACTIVE:
+ gimp_filter_set_active (filter, g_value_get_boolean (value));
+ break;
+ case PROP_IS_LAST_NODE:
+ gimp_filter_set_is_last_node (filter, g_value_get_boolean (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_filter_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpFilterPrivate *private = GET_PRIVATE (object);
+
+ switch (property_id)
+ {
+ case PROP_ACTIVE:
+ g_value_set_boolean (value, private->active);
+ break;
+ case PROP_IS_LAST_NODE:
+ g_value_set_boolean (value, private->is_last_node);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static gint64
+gimp_filter_get_memsize (GimpObject *object,
+ gint64 *gui_size)
+{
+ GimpFilterPrivate *private = GET_PRIVATE (object);
+ gint64 memsize = 0;
+
+ memsize += gimp_g_object_get_memsize (G_OBJECT (private->node));
+
+ return memsize + GIMP_OBJECT_CLASS (parent_class)->get_memsize (object,
+ gui_size);
+}
+
+static GeglNode *
+gimp_filter_real_get_node (GimpFilter *filter)
+{
+ GimpFilterPrivate *private = GET_PRIVATE (filter);
+
+ private->node = gegl_node_new ();
+
+ return private->node;
+}
+
+
+/* public functions */
+
+GimpFilter *
+gimp_filter_new (const gchar *name)
+{
+ g_return_val_if_fail (name != NULL, NULL);
+
+ return g_object_new (GIMP_TYPE_FILTER,
+ "name", name,
+ NULL);
+}
+
+GeglNode *
+gimp_filter_get_node (GimpFilter *filter)
+{
+ GimpFilterPrivate *private;
+
+ g_return_val_if_fail (GIMP_IS_FILTER (filter), NULL);
+
+ private = GET_PRIVATE (filter);
+
+ if (private->node)
+ return private->node;
+
+ return GIMP_FILTER_GET_CLASS (filter)->get_node (filter);
+}
+
+GeglNode *
+gimp_filter_peek_node (GimpFilter *filter)
+{
+ g_return_val_if_fail (GIMP_IS_FILTER (filter), NULL);
+
+ return GET_PRIVATE (filter)->node;
+}
+
+void
+gimp_filter_set_active (GimpFilter *filter,
+ gboolean active)
+{
+ g_return_if_fail (GIMP_IS_FILTER (filter));
+
+ active = active ? TRUE : FALSE;
+
+ if (active != gimp_filter_get_active (filter))
+ {
+ GET_PRIVATE (filter)->active = active;
+
+ g_signal_emit (filter, gimp_filter_signals[ACTIVE_CHANGED], 0);
+
+ g_object_notify (G_OBJECT (filter), "active");
+ }
+}
+
+gboolean
+gimp_filter_get_active (GimpFilter *filter)
+{
+ g_return_val_if_fail (GIMP_IS_FILTER (filter), FALSE);
+
+ return GET_PRIVATE (filter)->active;
+}
+
+void
+gimp_filter_set_is_last_node (GimpFilter *filter,
+ gboolean is_last_node)
+{
+ g_return_if_fail (GIMP_IS_FILTER (filter));
+
+ is_last_node = is_last_node ? TRUE : FALSE;
+
+ if (is_last_node != gimp_filter_get_is_last_node (filter))
+ {
+ GET_PRIVATE (filter)->is_last_node = is_last_node;
+
+ g_object_notify (G_OBJECT (filter), "is-last-node");
+ }
+}
+
+gboolean
+gimp_filter_get_is_last_node (GimpFilter *filter)
+{
+ g_return_val_if_fail (GIMP_IS_FILTER (filter), FALSE);
+
+ return GET_PRIVATE (filter)->is_last_node;
+}
+
+void
+gimp_filter_set_applicator (GimpFilter *filter,
+ GimpApplicator *applicator)
+{
+ GimpFilterPrivate *private;
+
+ g_return_if_fail (GIMP_IS_FILTER (filter));
+
+ private = GET_PRIVATE (filter);
+
+ private->applicator = applicator;
+}
+
+GimpApplicator *
+gimp_filter_get_applicator (GimpFilter *filter)
+{
+ g_return_val_if_fail (GIMP_IS_FILTER (filter), NULL);
+
+ return GET_PRIVATE (filter)->applicator;
+}
diff --git a/app/core/gimpfilter.h b/app/core/gimpfilter.h
new file mode 100644
index 0000000..b1dc922
--- /dev/null
+++ b/app/core/gimpfilter.h
@@ -0,0 +1,73 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpfilter.h
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_FILTER_H__
+#define __GIMP_FILTER_H__
+
+
+#include "gimpviewable.h"
+
+
+#define GIMP_TYPE_FILTER (gimp_filter_get_type ())
+#define GIMP_FILTER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_FILTER, GimpFilter))
+#define GIMP_FILTER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_FILTER, GimpFilterClass))
+#define GIMP_IS_FILTER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_FILTER))
+#define GIMP_IS_FILTER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_FILTER))
+#define GIMP_FILTER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_FILTER, GimpFilterClass))
+
+
+typedef struct _GimpFilterClass GimpFilterClass;
+
+struct _GimpFilter
+{
+ GimpViewable parent_instance;
+};
+
+struct _GimpFilterClass
+{
+ GimpViewableClass parent_class;
+
+ /* signals */
+ void (* active_changed) (GimpFilter *filter);
+
+ /* virtual functions */
+ GeglNode * (* get_node) (GimpFilter *filter);
+};
+
+
+GType gimp_filter_get_type (void) G_GNUC_CONST;
+GimpFilter * gimp_filter_new (const gchar *name);
+
+GeglNode * gimp_filter_get_node (GimpFilter *filter);
+GeglNode * gimp_filter_peek_node (GimpFilter *filter);
+
+void gimp_filter_set_active (GimpFilter *filter,
+ gboolean active);
+gboolean gimp_filter_get_active (GimpFilter *filter);
+
+void gimp_filter_set_is_last_node (GimpFilter *filter,
+ gboolean is_last_node);
+gboolean gimp_filter_get_is_last_node (GimpFilter *filter);
+
+void gimp_filter_set_applicator (GimpFilter *filter,
+ GimpApplicator *applicator);
+GimpApplicator * gimp_filter_get_applicator (GimpFilter *filter);
+
+
+#endif /* __GIMP_FILTER_H__ */
diff --git a/app/core/gimpfilteredcontainer.c b/app/core/gimpfilteredcontainer.c
new file mode 100644
index 0000000..ec56593
--- /dev/null
+++ b/app/core/gimpfilteredcontainer.c
@@ -0,0 +1,373 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-1997 Spencer Kimball and Peter Mattis
+ *
+ * gimpfilteredcontainer.c
+ * Copyright (C) 2008 Aurimas Juška <aurisj@svn.gnome.org>
+ * 2011 Michael Natterer <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <glib-object.h>
+
+#include "core-types.h"
+
+#include "gimpfilteredcontainer.h"
+
+
+enum
+{
+ PROP_0,
+ PROP_SRC_CONTAINER,
+ PROP_FILTER_FUNC,
+ PROP_FILTER_DATA
+};
+
+
+static void gimp_filtered_container_constructed (GObject *object);
+static void gimp_filtered_container_dispose (GObject *object);
+static void gimp_filtered_container_finalize (GObject *object);
+static void gimp_filtered_container_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_filtered_container_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static void gimp_filtered_container_real_src_add (GimpFilteredContainer *filtered_container,
+ GimpObject *object);
+static void gimp_filtered_container_real_src_remove (GimpFilteredContainer *filtered_container,
+ GimpObject *object);
+static void gimp_filtered_container_real_src_freeze (GimpFilteredContainer *filtered_container);
+static void gimp_filtered_container_real_src_thaw (GimpFilteredContainer *filtered_container);
+
+static gboolean gimp_filtered_container_object_matches (GimpFilteredContainer *filtered_container,
+ GimpObject *object);
+static void gimp_filtered_container_src_add (GimpContainer *src_container,
+ GimpObject *obj,
+ GimpFilteredContainer *filtered_container);
+static void gimp_filtered_container_src_remove (GimpContainer *src_container,
+ GimpObject *obj,
+ GimpFilteredContainer *filtered_container);
+static void gimp_filtered_container_src_freeze (GimpContainer *src_container,
+ GimpFilteredContainer *filtered_container);
+static void gimp_filtered_container_src_thaw (GimpContainer *src_container,
+ GimpFilteredContainer *filtered_container);
+
+
+G_DEFINE_TYPE (GimpFilteredContainer, gimp_filtered_container, GIMP_TYPE_LIST)
+
+#define parent_class gimp_filtered_container_parent_class
+
+
+static void
+gimp_filtered_container_class_init (GimpFilteredContainerClass *klass)
+{
+ GObjectClass *g_object_class = G_OBJECT_CLASS (klass);
+ GimpFilteredContainerClass *filtered_class = GIMP_FILTERED_CONTAINER_CLASS (klass);
+
+ g_object_class->constructed = gimp_filtered_container_constructed;
+ g_object_class->dispose = gimp_filtered_container_dispose;
+ g_object_class->finalize = gimp_filtered_container_finalize;
+ g_object_class->set_property = gimp_filtered_container_set_property;
+ g_object_class->get_property = gimp_filtered_container_get_property;
+
+ filtered_class->src_add = gimp_filtered_container_real_src_add;
+ filtered_class->src_remove = gimp_filtered_container_real_src_remove;
+ filtered_class->src_freeze = gimp_filtered_container_real_src_freeze;
+ filtered_class->src_thaw = gimp_filtered_container_real_src_thaw;
+
+ g_object_class_install_property (g_object_class, PROP_SRC_CONTAINER,
+ g_param_spec_object ("src-container",
+ NULL, NULL,
+ GIMP_TYPE_CONTAINER,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+
+ g_object_class_install_property (g_object_class, PROP_FILTER_FUNC,
+ g_param_spec_pointer ("filter-func",
+ NULL, NULL,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+
+ g_object_class_install_property (g_object_class, PROP_FILTER_DATA,
+ g_param_spec_pointer ("filter-data",
+ NULL, NULL,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+}
+
+static void
+gimp_filtered_container_init (GimpFilteredContainer *filtered_container)
+{
+}
+
+static void
+gimp_filtered_container_constructed (GObject *object)
+{
+ GimpFilteredContainer *filtered_container = GIMP_FILTERED_CONTAINER (object);
+
+ G_OBJECT_CLASS (parent_class)->constructed (object);
+
+ gimp_assert (GIMP_IS_CONTAINER (filtered_container->src_container));
+
+ if (! gimp_container_frozen (filtered_container->src_container))
+ {
+ /* a freeze/thaw can't hurt on a newly created container because
+ * we can't have any views yet. This way we get away without
+ * having a virtual function for initializing the container.
+ */
+ gimp_filtered_container_src_freeze (filtered_container->src_container,
+ filtered_container);
+ gimp_filtered_container_src_thaw (filtered_container->src_container,
+ filtered_container);
+ }
+}
+
+static void
+gimp_filtered_container_dispose (GObject *object)
+{
+ GimpFilteredContainer *filtered_container = GIMP_FILTERED_CONTAINER (object);
+
+ if (filtered_container->src_container)
+ {
+ g_signal_handlers_disconnect_by_func (filtered_container->src_container,
+ gimp_filtered_container_src_add,
+ filtered_container);
+ g_signal_handlers_disconnect_by_func (filtered_container->src_container,
+ gimp_filtered_container_src_remove,
+ filtered_container);
+ g_signal_handlers_disconnect_by_func (filtered_container->src_container,
+ gimp_filtered_container_src_freeze,
+ filtered_container);
+ g_signal_handlers_disconnect_by_func (filtered_container->src_container,
+ gimp_filtered_container_src_thaw,
+ filtered_container);
+ }
+
+ G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+gimp_filtered_container_finalize (GObject *object)
+{
+ GimpFilteredContainer *filtered_container = GIMP_FILTERED_CONTAINER (object);
+
+ g_clear_object (&filtered_container->src_container);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gimp_filtered_container_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpFilteredContainer *filtered_container = GIMP_FILTERED_CONTAINER (object);
+
+ switch (property_id)
+ {
+ case PROP_SRC_CONTAINER:
+ filtered_container->src_container = g_value_dup_object (value);
+
+ g_signal_connect (filtered_container->src_container, "add",
+ G_CALLBACK (gimp_filtered_container_src_add),
+ filtered_container);
+ g_signal_connect (filtered_container->src_container, "remove",
+ G_CALLBACK (gimp_filtered_container_src_remove),
+ filtered_container);
+ g_signal_connect (filtered_container->src_container, "freeze",
+ G_CALLBACK (gimp_filtered_container_src_freeze),
+ filtered_container);
+ g_signal_connect (filtered_container->src_container, "thaw",
+ G_CALLBACK (gimp_filtered_container_src_thaw),
+ filtered_container);
+ break;
+
+ case PROP_FILTER_FUNC:
+ filtered_container->filter_func = g_value_get_pointer (value);
+ break;
+
+ case PROP_FILTER_DATA:
+ filtered_container->filter_data = g_value_get_pointer (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_filtered_container_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpFilteredContainer *filtered_container = GIMP_FILTERED_CONTAINER (object);
+
+ switch (property_id)
+ {
+ case PROP_SRC_CONTAINER:
+ g_value_set_object (value, filtered_container->src_container);
+ break;
+
+ case PROP_FILTER_FUNC:
+ g_value_set_pointer (value, filtered_container->filter_func);
+ break;
+
+ case PROP_FILTER_DATA:
+ g_value_set_pointer (value, filtered_container->filter_data);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_filtered_container_real_src_add (GimpFilteredContainer *filtered_container,
+ GimpObject *object)
+{
+ if (gimp_filtered_container_object_matches (filtered_container, object))
+ {
+ gimp_container_add (GIMP_CONTAINER (filtered_container), object);
+ }
+}
+
+static void
+gimp_filtered_container_real_src_remove (GimpFilteredContainer *filtered_container,
+ GimpObject *object)
+{
+ if (gimp_filtered_container_object_matches (filtered_container, object))
+ {
+ gimp_container_remove (GIMP_CONTAINER (filtered_container), object);
+ }
+}
+
+static void
+gimp_filtered_container_real_src_freeze (GimpFilteredContainer *filtered_container)
+{
+ gimp_container_clear (GIMP_CONTAINER (filtered_container));
+}
+
+static void
+gimp_filtered_container_real_src_thaw (GimpFilteredContainer *filtered_container)
+{
+ GList *list;
+
+ for (list = GIMP_LIST (filtered_container->src_container)->queue->head;
+ list;
+ list = g_list_next (list))
+ {
+ GimpObject *object = list->data;
+
+ if (gimp_filtered_container_object_matches (filtered_container, object))
+ {
+ gimp_container_add (GIMP_CONTAINER (filtered_container), object);
+ }
+ }
+}
+
+/**
+ * gimp_filtered_container_new:
+ * @src_container: container to be filtered.
+ *
+ * Creates a new #GimpFilteredContainer object which creates filtered
+ * data view of #GimpTagged objects. It filters @src_container for objects
+ * containing all of the filtering tags. Synchronization with @src_container
+ * data is performed automatically.
+ *
+ * Return value: a new #GimpFilteredContainer object.
+ **/
+GimpContainer *
+gimp_filtered_container_new (GimpContainer *src_container,
+ GimpObjectFilterFunc filter_func,
+ gpointer filter_data)
+{
+ GType children_type;
+ GCompareFunc sort_func;
+
+ g_return_val_if_fail (GIMP_IS_LIST (src_container), NULL);
+
+ children_type = gimp_container_get_children_type (src_container);
+ sort_func = GIMP_LIST (src_container)->sort_func;
+
+ return g_object_new (GIMP_TYPE_FILTERED_CONTAINER,
+ "sort-func", sort_func,
+ "children-type", children_type,
+ "policy", GIMP_CONTAINER_POLICY_WEAK,
+ "unique-names", FALSE,
+ "src-container", src_container,
+ "filter-func", filter_func,
+ "filter-data", filter_data,
+ NULL);
+}
+
+static gboolean
+gimp_filtered_container_object_matches (GimpFilteredContainer *filtered_container,
+ GimpObject *object)
+{
+ return (! filtered_container->filter_func ||
+ filtered_container->filter_func (object,
+ filtered_container->filter_data));
+}
+
+static void
+gimp_filtered_container_src_add (GimpContainer *src_container,
+ GimpObject *object,
+ GimpFilteredContainer *filtered_container)
+{
+ if (! gimp_container_frozen (filtered_container->src_container))
+ {
+ GIMP_FILTERED_CONTAINER_GET_CLASS (filtered_container)->src_add (filtered_container,
+ object);
+ }
+}
+
+static void
+gimp_filtered_container_src_remove (GimpContainer *src_container,
+ GimpObject *object,
+ GimpFilteredContainer *filtered_container)
+{
+ if (! gimp_container_frozen (filtered_container->src_container))
+ {
+ GIMP_FILTERED_CONTAINER_GET_CLASS (filtered_container)->src_remove (filtered_container,
+ object);
+ }
+}
+
+static void
+gimp_filtered_container_src_freeze (GimpContainer *src_container,
+ GimpFilteredContainer *filtered_container)
+{
+ gimp_container_freeze (GIMP_CONTAINER (filtered_container));
+
+ GIMP_FILTERED_CONTAINER_GET_CLASS (filtered_container)->src_freeze (filtered_container);
+}
+
+static void
+gimp_filtered_container_src_thaw (GimpContainer *src_container,
+ GimpFilteredContainer *filtered_container)
+{
+ GIMP_FILTERED_CONTAINER_GET_CLASS (filtered_container)->src_thaw (filtered_container);
+
+ gimp_container_thaw (GIMP_CONTAINER (filtered_container));
+}
diff --git a/app/core/gimpfilteredcontainer.h b/app/core/gimpfilteredcontainer.h
new file mode 100644
index 0000000..28803c8
--- /dev/null
+++ b/app/core/gimpfilteredcontainer.h
@@ -0,0 +1,68 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-1997 Spencer Kimball and Peter Mattis
+ *
+ * gimpfilteredcontainer.h
+ * Copyright (C) 2008 Aurimas Juška <aurisj@svn.gnome.org>
+ * 2011 Michael Natterer <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_FILTERED_CONTAINER_H__
+#define __GIMP_FILTERED_CONTAINER_H__
+
+
+#include "gimplist.h"
+
+
+#define GIMP_TYPE_FILTERED_CONTAINER (gimp_filtered_container_get_type ())
+#define GIMP_FILTERED_CONTAINER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_FILTERED_CONTAINER, GimpFilteredContainer))
+#define GIMP_FILTERED_CONTAINER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_FILTERED_CONTAINER, GimpFilteredContainerClass))
+#define GIMP_IS_FILTERED_CONTAINER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_FILTERED_CONTAINER))
+#define GIMP_IS_FILTERED_CONTAINER_CLASS(class) (G_TYPE_CHECK_CLASS_TYPE ((class), GIMP_TYPE_FILTERED_CONTAINER))
+#define GIMP_FILTERED_CONTAINER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_FILTERED_CONTAINER, GimpFilteredContainerClass))
+
+
+typedef struct _GimpFilteredContainerClass GimpFilteredContainerClass;
+
+struct _GimpFilteredContainer
+{
+ GimpList parent_instance;
+
+ GimpContainer *src_container;
+ GimpObjectFilterFunc filter_func;
+ gpointer filter_data;
+};
+
+struct _GimpFilteredContainerClass
+{
+ GimpContainerClass parent_class;
+
+ void (* src_add) (GimpFilteredContainer *filtered_container,
+ GimpObject *object);
+ void (* src_remove) (GimpFilteredContainer *filtered_container,
+ GimpObject *object);
+ void (* src_freeze) (GimpFilteredContainer *filtered_container);
+ void (* src_thaw) (GimpFilteredContainer *filtered_container);
+};
+
+
+GType gimp_filtered_container_get_type (void) G_GNUC_CONST;
+
+GimpContainer * gimp_filtered_container_new (GimpContainer *src_container,
+ GimpObjectFilterFunc filter_func,
+ gpointer filter_data);
+
+
+#endif /* __GIMP_FILTERED_CONTAINER_H__ */
diff --git a/app/core/gimpfilterstack.c b/app/core/gimpfilterstack.c
new file mode 100644
index 0000000..09d9cfd
--- /dev/null
+++ b/app/core/gimpfilterstack.c
@@ -0,0 +1,350 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-1997 Spencer Kimball and Peter Mattis
+ *
+ * gimpfilterstack.c
+ * Copyright (C) 2008-2013 Michael Natterer <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "core-types.h"
+
+#include "gimpfilter.h"
+#include "gimpfilterstack.h"
+
+
+/* local function prototypes */
+
+static void gimp_filter_stack_constructed (GObject *object);
+static void gimp_filter_stack_finalize (GObject *object);
+
+static void gimp_filter_stack_add (GimpContainer *container,
+ GimpObject *object);
+static void gimp_filter_stack_remove (GimpContainer *container,
+ GimpObject *object);
+static void gimp_filter_stack_reorder (GimpContainer *container,
+ GimpObject *object,
+ gint new_index);
+
+static void gimp_filter_stack_add_node (GimpFilterStack *stack,
+ GimpFilter *filter);
+static void gimp_filter_stack_remove_node (GimpFilterStack *stack,
+ GimpFilter *filter);
+static void gimp_filter_stack_update_last_node (GimpFilterStack *stack);
+
+static void gimp_filter_stack_filter_active (GimpFilter *filter,
+ GimpFilterStack *stack);
+
+
+G_DEFINE_TYPE (GimpFilterStack, gimp_filter_stack, GIMP_TYPE_LIST);
+
+#define parent_class gimp_filter_stack_parent_class
+
+
+static void
+gimp_filter_stack_class_init (GimpFilterStackClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpContainerClass *container_class = GIMP_CONTAINER_CLASS (klass);
+
+ object_class->constructed = gimp_filter_stack_constructed;
+ object_class->finalize = gimp_filter_stack_finalize;
+
+ container_class->add = gimp_filter_stack_add;
+ container_class->remove = gimp_filter_stack_remove;
+ container_class->reorder = gimp_filter_stack_reorder;
+}
+
+static void
+gimp_filter_stack_init (GimpFilterStack *stack)
+{
+}
+
+static void
+gimp_filter_stack_constructed (GObject *object)
+{
+ GimpContainer *container = GIMP_CONTAINER (object);
+
+ G_OBJECT_CLASS (parent_class)->constructed (object);
+
+ gimp_assert (g_type_is_a (gimp_container_get_children_type (container),
+ GIMP_TYPE_FILTER));
+
+ gimp_container_add_handler (container, "active-changed",
+ G_CALLBACK (gimp_filter_stack_filter_active),
+ container);
+}
+
+static void
+gimp_filter_stack_finalize (GObject *object)
+{
+ GimpFilterStack *stack = GIMP_FILTER_STACK (object);
+
+ g_clear_object (&stack->graph);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gimp_filter_stack_add (GimpContainer *container,
+ GimpObject *object)
+{
+ GimpFilterStack *stack = GIMP_FILTER_STACK (container);
+ GimpFilter *filter = GIMP_FILTER (object);
+
+ GIMP_CONTAINER_CLASS (parent_class)->add (container, object);
+
+ if (gimp_filter_get_active (filter))
+ {
+ if (stack->graph)
+ {
+ gegl_node_add_child (stack->graph, gimp_filter_get_node (filter));
+ gimp_filter_stack_add_node (stack, filter);
+ }
+
+ gimp_filter_stack_update_last_node (stack);
+ }
+}
+
+static void
+gimp_filter_stack_remove (GimpContainer *container,
+ GimpObject *object)
+{
+ GimpFilterStack *stack = GIMP_FILTER_STACK (container);
+ GimpFilter *filter = GIMP_FILTER (object);
+
+ if (stack->graph && gimp_filter_get_active (filter))
+ {
+ gimp_filter_stack_remove_node (stack, filter);
+ gegl_node_remove_child (stack->graph, gimp_filter_get_node (filter));
+ }
+
+ GIMP_CONTAINER_CLASS (parent_class)->remove (container, object);
+
+ if (gimp_filter_get_active (filter))
+ {
+ gimp_filter_set_is_last_node (filter, FALSE);
+ gimp_filter_stack_update_last_node (stack);
+ }
+}
+
+static void
+gimp_filter_stack_reorder (GimpContainer *container,
+ GimpObject *object,
+ gint new_index)
+{
+ GimpFilterStack *stack = GIMP_FILTER_STACK (container);
+ GimpFilter *filter = GIMP_FILTER (object);
+
+ if (stack->graph && gimp_filter_get_active (filter))
+ gimp_filter_stack_remove_node (stack, filter);
+
+ GIMP_CONTAINER_CLASS (parent_class)->reorder (container, object, new_index);
+
+ if (gimp_filter_get_active (filter))
+ {
+ gimp_filter_stack_update_last_node (stack);
+
+ if (stack->graph)
+ gimp_filter_stack_add_node (stack, filter);
+ }
+}
+
+
+/* public functions */
+
+GimpContainer *
+gimp_filter_stack_new (GType filter_type)
+{
+ g_return_val_if_fail (g_type_is_a (filter_type, GIMP_TYPE_FILTER), NULL);
+
+ return g_object_new (GIMP_TYPE_FILTER_STACK,
+ "name", g_type_name (filter_type),
+ "children-type", filter_type,
+ "policy", GIMP_CONTAINER_POLICY_STRONG,
+ NULL);
+}
+
+GeglNode *
+gimp_filter_stack_get_graph (GimpFilterStack *stack)
+{
+ GList *list;
+ GeglNode *previous;
+ GeglNode *output;
+
+ g_return_val_if_fail (GIMP_IS_FILTER_STACK (stack), NULL);
+
+ if (stack->graph)
+ return stack->graph;
+
+ stack->graph = gegl_node_new ();
+
+ previous = gegl_node_get_input_proxy (stack->graph, "input");
+
+ for (list = GIMP_LIST (stack)->queue->tail;
+ list;
+ list = g_list_previous (list))
+ {
+ GimpFilter *filter = list->data;
+ GeglNode *node;
+
+ if (! gimp_filter_get_active (filter))
+ continue;
+
+ node = gimp_filter_get_node (filter);
+
+ gegl_node_add_child (stack->graph, node);
+
+ gegl_node_connect_to (previous, "output",
+ node, "input");
+
+ previous = node;
+ }
+
+ output = gegl_node_get_output_proxy (stack->graph, "output");
+
+ gegl_node_connect_to (previous, "output",
+ output, "input");
+
+ return stack->graph;
+}
+
+
+/* private functions */
+
+static void
+gimp_filter_stack_add_node (GimpFilterStack *stack,
+ GimpFilter *filter)
+{
+ GeglNode *node;
+ GeglNode *node_above = NULL;
+ GeglNode *node_below = NULL;
+ GList *iter;
+
+ node = gimp_filter_get_node (filter);
+
+
+ iter = g_list_find (GIMP_LIST (stack)->queue->head, filter);
+
+ while ((iter = g_list_previous (iter)))
+ {
+ GimpFilter *filter_above = iter->data;
+
+ if (gimp_filter_get_active (filter_above))
+ {
+ node_above = gimp_filter_get_node (filter_above);
+
+ break;
+ }
+ }
+
+ if (! node_above)
+ node_above = gegl_node_get_output_proxy (stack->graph, "output");
+
+ node_below = gegl_node_get_producer (node_above, "input", NULL);
+
+ gegl_node_connect_to (node_below, "output",
+ node, "input");
+ gegl_node_connect_to (node, "output",
+ node_above, "input");
+}
+
+static void
+gimp_filter_stack_remove_node (GimpFilterStack *stack,
+ GimpFilter *filter)
+{
+ GeglNode *node;
+ GeglNode *node_above = NULL;
+ GeglNode *node_below = NULL;
+ GList *iter;
+
+ node = gimp_filter_get_node (filter);
+
+ iter = g_list_find (GIMP_LIST (stack)->queue->head, filter);
+
+ while ((iter = g_list_previous (iter)))
+ {
+ GimpFilter *filter_above = iter->data;
+
+ if (gimp_filter_get_active (filter_above))
+ {
+ node_above = gimp_filter_get_node (filter_above);
+
+ break;
+ }
+ }
+
+ if (! node_above)
+ node_above = gegl_node_get_output_proxy (stack->graph, "output");
+
+ node_below = gegl_node_get_producer (node, "input", NULL);
+
+ gegl_node_disconnect (node, "input");
+
+ gegl_node_connect_to (node_below, "output",
+ node_above, "input");
+}
+
+static void
+gimp_filter_stack_update_last_node (GimpFilterStack *stack)
+{
+ GList *list;
+ gboolean found_last = FALSE;
+
+ for (list = GIMP_LIST (stack)->queue->tail;
+ list;
+ list = g_list_previous (list))
+ {
+ GimpFilter *filter = list->data;
+
+ if (! found_last && gimp_filter_get_active (filter))
+ {
+ gimp_filter_set_is_last_node (filter, TRUE);
+ found_last = TRUE;
+ }
+ else
+ {
+ gimp_filter_set_is_last_node (filter, FALSE);
+ }
+ }
+}
+
+static void
+gimp_filter_stack_filter_active (GimpFilter *filter,
+ GimpFilterStack *stack)
+{
+ if (stack->graph)
+ {
+ if (gimp_filter_get_active (filter))
+ {
+ gegl_node_add_child (stack->graph, gimp_filter_get_node (filter));
+ gimp_filter_stack_add_node (stack, filter);
+ }
+ else
+ {
+ gimp_filter_stack_remove_node (stack, filter);
+ gegl_node_remove_child (stack->graph, gimp_filter_get_node (filter));
+ }
+ }
+
+ gimp_filter_stack_update_last_node (stack);
+
+ if (! gimp_filter_get_active (filter))
+ gimp_filter_set_is_last_node (filter, FALSE);
+}
diff --git a/app/core/gimpfilterstack.h b/app/core/gimpfilterstack.h
new file mode 100644
index 0000000..3567f06
--- /dev/null
+++ b/app/core/gimpfilterstack.h
@@ -0,0 +1,55 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-1997 Spencer Kimball and Peter Mattis
+ *
+ * gimpfilterstack.h
+ * Copyright (C) 2008-2013 Michael Natterer <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_FILTER_STACK_H__
+#define __GIMP_FILTER_STACK_H__
+
+#include "gimplist.h"
+
+
+#define GIMP_TYPE_FILTER_STACK (gimp_filter_stack_get_type ())
+#define GIMP_FILTER_STACK(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_FILTER_STACK, GimpFilterStack))
+#define GIMP_FILTER_STACK_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_FILTER_STACK, GimpFilterStackClass))
+#define GIMP_IS_FILTER_STACK(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_FILTER_STACK))
+#define GIMP_IS_FILTER_STACK_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_FILTER_STACK))
+
+
+typedef struct _GimpFilterStackClass GimpFilterStackClass;
+
+struct _GimpFilterStack
+{
+ GimpList parent_instance;
+
+ GeglNode *graph;
+};
+
+struct _GimpFilterStackClass
+{
+ GimpListClass parent_class;
+};
+
+
+GType gimp_filter_stack_get_type (void) G_GNUC_CONST;
+GimpContainer * gimp_filter_stack_new (GType filter_type);
+
+GeglNode * gimp_filter_stack_get_graph (GimpFilterStack *stack);
+
+
+#endif /* __GIMP_FILTER_STACK_H__ */
diff --git a/app/core/gimpfloatingselectionundo.c b/app/core/gimpfloatingselectionundo.c
new file mode 100644
index 0000000..6ca28ab
--- /dev/null
+++ b/app/core/gimpfloatingselectionundo.c
@@ -0,0 +1,135 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "core-types.h"
+
+#include "gimpdrawable-floating-selection.h"
+#include "gimpfloatingselectionundo.h"
+#include "gimpimage.h"
+#include "gimplayer.h"
+#include "gimplayer-floating-selection.h"
+
+
+static void gimp_floating_selection_undo_constructed (GObject *object);
+
+static void gimp_floating_selection_undo_pop (GimpUndo *undo,
+ GimpUndoMode undo_mode,
+ GimpUndoAccumulator *accum);
+
+
+G_DEFINE_TYPE (GimpFloatingSelectionUndo, gimp_floating_selection_undo,
+ GIMP_TYPE_ITEM_UNDO)
+
+#define parent_class gimp_floating_selection_undo_parent_class
+
+
+static void
+gimp_floating_selection_undo_class_init (GimpFloatingSelectionUndoClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpUndoClass *undo_class = GIMP_UNDO_CLASS (klass);
+
+ object_class->constructed = gimp_floating_selection_undo_constructed;
+
+ undo_class->pop = gimp_floating_selection_undo_pop;
+}
+
+static void
+gimp_floating_selection_undo_init (GimpFloatingSelectionUndo *undo)
+{
+}
+
+static void
+gimp_floating_selection_undo_constructed (GObject *object)
+{
+ GimpFloatingSelectionUndo *floating_sel_undo;
+ GimpLayer *layer;
+
+ floating_sel_undo = GIMP_FLOATING_SELECTION_UNDO (object);
+
+ G_OBJECT_CLASS (parent_class)->constructed (object);
+
+ gimp_assert (GIMP_IS_LAYER (GIMP_ITEM_UNDO (object)->item));
+
+ layer = GIMP_LAYER (GIMP_ITEM_UNDO (object)->item);
+
+ switch (GIMP_UNDO (object)->undo_type)
+ {
+ case GIMP_UNDO_FS_TO_LAYER:
+ floating_sel_undo->drawable = gimp_layer_get_floating_sel_drawable (layer);
+ break;
+
+ default:
+ g_return_if_reached ();
+ }
+}
+
+static void
+gimp_floating_selection_undo_pop (GimpUndo *undo,
+ GimpUndoMode undo_mode,
+ GimpUndoAccumulator *accum)
+{
+ GimpFloatingSelectionUndo *floating_sel_undo;
+ GimpLayer *floating_layer;
+
+ floating_sel_undo = GIMP_FLOATING_SELECTION_UNDO (undo);
+ floating_layer = GIMP_LAYER (GIMP_ITEM_UNDO (undo)->item);
+
+ GIMP_UNDO_CLASS (parent_class)->pop (undo, undo_mode, accum);
+
+ switch (undo->undo_type)
+ {
+ case GIMP_UNDO_FS_TO_LAYER:
+ if (undo_mode == GIMP_UNDO_MODE_UNDO)
+ {
+ /* Update the preview for the floating selection */
+ gimp_viewable_invalidate_preview (GIMP_VIEWABLE (floating_layer));
+
+ gimp_layer_set_floating_sel_drawable (floating_layer,
+ floating_sel_undo->drawable);
+ gimp_image_set_active_layer (undo->image, floating_layer);
+
+ gimp_drawable_attach_floating_sel (gimp_layer_get_floating_sel_drawable (floating_layer),
+ floating_layer);
+ }
+ else
+ {
+ gimp_drawable_detach_floating_sel (gimp_layer_get_floating_sel_drawable (floating_layer));
+ gimp_layer_set_floating_sel_drawable (floating_layer, NULL);
+ }
+
+ /* When the floating selection is converted to/from a normal
+ * layer it does something resembling a name change, so emit the
+ * "name-changed" signal
+ */
+ gimp_object_name_changed (GIMP_OBJECT (floating_layer));
+
+ gimp_drawable_update (GIMP_DRAWABLE (floating_layer),
+ 0, 0,
+ gimp_item_get_width (GIMP_ITEM (floating_layer)),
+ gimp_item_get_height (GIMP_ITEM (floating_layer)));
+ break;
+
+ default:
+ g_return_if_reached ();
+ }
+}
diff --git a/app/core/gimpfloatingselectionundo.h b/app/core/gimpfloatingselectionundo.h
new file mode 100644
index 0000000..4d9a626
--- /dev/null
+++ b/app/core/gimpfloatingselectionundo.h
@@ -0,0 +1,52 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_FLOATING_SELECTION_UNDO_H__
+#define __GIMP_FLOATING_SELECTION_UNDO_H__
+
+
+#include "gimpitemundo.h"
+
+
+#define GIMP_TYPE_FLOATING_SELECTION_UNDO (gimp_floating_selection_undo_get_type ())
+#define GIMP_FLOATING_SELECTION_UNDO(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_FLOATING_SELECTION_UNDO, GimpFloatingSelectionUndo))
+#define GIMP_FLOATING_SELECTION_UNDO_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_FLOATING_SELECTION_UNDO, GimpFloatingSelectionUndoClass))
+#define GIMP_IS_FLOATING_SELECTION_UNDO(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_FLOATING_SELECTION_UNDO))
+#define GIMP_IS_FLOATING_SELECTION_UNDO_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_FLOATING_SELECTION_UNDO))
+#define GIMP_FLOATING_SELECTION_UNDO_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_FLOATING_SELECTION_UNDO, GimpFloatingSelectionUndoClass))
+
+
+typedef struct _GimpFloatingSelectionUndo GimpFloatingSelectionUndo;
+typedef struct _GimpFloatingSelectionUndoClass GimpFloatingSelectionUndoClass;
+
+struct _GimpFloatingSelectionUndo
+{
+ GimpItemUndo parent_instance;
+
+ GimpDrawable *drawable;
+};
+
+struct _GimpFloatingSelectionUndoClass
+{
+ GimpItemUndoClass parent_class;
+};
+
+
+GType gimp_floating_selection_undo_get_type (void) G_GNUC_CONST;
+
+
+#endif /* __GIMP_FLOATING_SELECTION_UNDO_H__ */
diff --git a/app/core/gimpgradient-load.c b/app/core/gimpgradient-load.c
new file mode 100644
index 0000000..1abe494
--- /dev/null
+++ b/app/core/gimpgradient-load.c
@@ -0,0 +1,575 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include <cairo.h>
+#include <gegl.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+#include "libgimpbase/gimpbase.h"
+#include "libgimpcolor/gimpcolor.h"
+
+#include "core-types.h"
+
+#include "config/gimpxmlparser.h"
+
+#include "gimp-utils.h"
+#include "gimpgradient.h"
+#include "gimpgradient-load.h"
+
+#include "gimp-intl.h"
+
+
+GList *
+gimp_gradient_load (GimpContext *context,
+ GFile *file,
+ GInputStream *input,
+ GError **error)
+{
+ GimpGradient *gradient = NULL;
+ GimpGradientSegment *prev;
+ gint num_segments;
+ gint i;
+ GDataInputStream *data_input;
+ gchar *line;
+ gsize line_len;
+ gint linenum;
+
+ g_return_val_if_fail (G_IS_FILE (file), NULL);
+ g_return_val_if_fail (G_IS_INPUT_STREAM (input), NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ data_input = g_data_input_stream_new (input);
+
+ linenum = 1;
+ line_len = 1024;
+ line = gimp_data_input_stream_read_line_always (data_input, &line_len,
+ NULL, error);
+ if (! line)
+ goto failed;
+
+ if (! g_str_has_prefix (line, "GIMP Gradient"))
+ {
+ g_set_error (error, GIMP_DATA_ERROR, GIMP_DATA_ERROR_READ,
+ _("Not a GIMP gradient file."));
+ g_free (line);
+ goto failed;
+ }
+
+ g_free (line);
+
+ gradient = g_object_new (GIMP_TYPE_GRADIENT,
+ "mime-type", "application/x-gimp-gradient",
+ NULL);
+
+ linenum++;
+ line_len = 1024;
+ line = gimp_data_input_stream_read_line_always (data_input, &line_len,
+ NULL, error);
+ if (! line)
+ goto failed;
+
+ if (g_str_has_prefix (line, "Name: "))
+ {
+ gchar *utf8;
+
+ utf8 = gimp_any_to_utf8 (g_strstrip (line + strlen ("Name: ")), -1,
+ _("Invalid UTF-8 string in gradient file '%s'."),
+ gimp_file_get_utf8_name (file));
+ gimp_object_take_name (GIMP_OBJECT (gradient), utf8);
+
+ g_free (line);
+
+ linenum++;
+ line_len = 1024;
+ line = gimp_data_input_stream_read_line_always (data_input, &line_len,
+ NULL, error);
+ if (! line)
+ goto failed;
+ }
+ else /* old gradient format */
+ {
+ gimp_object_take_name (GIMP_OBJECT (gradient),
+ g_path_get_basename (gimp_file_get_utf8_name (file)));
+ }
+
+ num_segments = atoi (line);
+
+ g_free (line);
+
+ if (num_segments < 1)
+ {
+ g_set_error (error, GIMP_DATA_ERROR, GIMP_DATA_ERROR_READ,
+ _("File is corrupt."));
+ goto failed;
+ }
+
+ prev = NULL;
+
+ for (i = 0; i < num_segments; i++)
+ {
+ GimpGradientSegment *seg;
+ gchar *end;
+ gint color;
+ gint type;
+ gint left_color_type;
+ gint right_color_type;
+
+ seg = gimp_gradient_segment_new ();
+
+ seg->prev = prev;
+
+ if (prev)
+ prev->next = seg;
+ else
+ gradient->segments = seg;
+
+ linenum++;
+ line_len = 1024;
+ line = gimp_data_input_stream_read_line_always (data_input, &line_len,
+ NULL, error);
+ if (! line)
+ goto failed;
+
+ if (! gimp_ascii_strtod (line, &end, &seg->left) ||
+ ! gimp_ascii_strtod (end, &end, &seg->middle) ||
+ ! gimp_ascii_strtod (end, &end, &seg->right) ||
+
+ ! gimp_ascii_strtod (end, &end, &seg->left_color.r) ||
+ ! gimp_ascii_strtod (end, &end, &seg->left_color.g) ||
+ ! gimp_ascii_strtod (end, &end, &seg->left_color.b) ||
+ ! gimp_ascii_strtod (end, &end, &seg->left_color.a) ||
+
+ ! gimp_ascii_strtod (end, &end, &seg->right_color.r) ||
+ ! gimp_ascii_strtod (end, &end, &seg->right_color.g) ||
+ ! gimp_ascii_strtod (end, &end, &seg->right_color.b) ||
+ ! gimp_ascii_strtod (end, &end, &seg->right_color.a))
+ {
+ g_set_error (error, GIMP_DATA_ERROR, GIMP_DATA_ERROR_READ,
+ _("Corrupt segment %d."), i);
+ g_free (line);
+ goto failed;
+ }
+
+ switch (sscanf (end, "%d %d %d %d",
+ &type, &color,
+ &left_color_type, &right_color_type))
+ {
+ case 4:
+ seg->left_color_type = (GimpGradientColor) left_color_type;
+ if (seg->left_color_type < GIMP_GRADIENT_COLOR_FIXED ||
+ seg->left_color_type > GIMP_GRADIENT_COLOR_BACKGROUND_TRANSPARENT)
+ {
+ g_set_error (error, GIMP_DATA_ERROR, GIMP_DATA_ERROR_READ,
+ _("Corrupt segment %d."), i);
+ g_free (line);
+ goto failed;
+ }
+
+ seg->right_color_type = (GimpGradientColor) right_color_type;
+ if (seg->right_color_type < GIMP_GRADIENT_COLOR_FIXED ||
+ seg->right_color_type > GIMP_GRADIENT_COLOR_BACKGROUND_TRANSPARENT)
+ {
+ g_set_error (error, GIMP_DATA_ERROR, GIMP_DATA_ERROR_READ,
+ _("Corrupt segment %d."), i);
+ g_free (line);
+ goto failed;
+ }
+ /* fall thru */
+
+ case 2:
+ seg->type = (GimpGradientSegmentType) type;
+ if (seg->type < GIMP_GRADIENT_SEGMENT_LINEAR ||
+ seg->type > GIMP_GRADIENT_SEGMENT_STEP)
+ {
+ g_set_error (error, GIMP_DATA_ERROR, GIMP_DATA_ERROR_READ,
+ _("Corrupt segment %d."), i);
+ g_free (line);
+ goto failed;
+ }
+
+ seg->color = (GimpGradientSegmentColor) color;
+ if (seg->color < GIMP_GRADIENT_SEGMENT_RGB ||
+ seg->color > GIMP_GRADIENT_SEGMENT_HSV_CW)
+ {
+ g_set_error (error, GIMP_DATA_ERROR, GIMP_DATA_ERROR_READ,
+ _("Corrupt segment %d."), i);
+ g_free (line);
+ goto failed;
+ }
+ break;
+
+ default:
+ g_set_error (error, GIMP_DATA_ERROR, GIMP_DATA_ERROR_READ,
+ _("Corrupt segment %d."), i);
+ g_free (line);
+ goto failed;
+ }
+
+ g_free (line);
+
+ if (seg->left > seg->middle ||
+ seg->middle > seg->right ||
+ ( prev && (prev->right != seg->left)) ||
+ (! prev && (0.0 != seg->left)))
+ {
+ g_set_error (error, GIMP_DATA_ERROR, GIMP_DATA_ERROR_READ,
+ _("Segments do not span the range 0-1."));
+ goto failed;
+ }
+
+ prev = seg;
+ }
+
+ if (prev->right != 1.0)
+ {
+ g_set_error (error, GIMP_DATA_ERROR, GIMP_DATA_ERROR_READ,
+ _("Segments do not span the range 0-1."));
+ goto failed;
+ }
+
+ g_object_unref (data_input);
+
+ return g_list_prepend (NULL, gradient);
+
+ failed:
+
+ g_object_unref (data_input);
+
+ if (gradient)
+ g_object_unref (gradient);
+
+ g_prefix_error (error, _("In line %d of gradient file: "), linenum);
+
+ return NULL;
+}
+
+
+/* SVG gradient parser */
+
+typedef struct
+{
+ GimpGradient *gradient; /* current gradient */
+ GList *gradients; /* finished gradients */
+ GList *stops;
+} SvgParser;
+
+typedef struct
+{
+ gdouble offset;
+ GimpRGB color;
+} SvgStop;
+
+
+static void svg_parser_start_element (GMarkupParseContext *context,
+ const gchar *element_name,
+ const gchar **attribute_names,
+ const gchar **attribute_values,
+ gpointer user_data,
+ GError **error);
+static void svg_parser_end_element (GMarkupParseContext *context,
+ const gchar *element_name,
+ gpointer user_data,
+ GError **error);
+
+static GimpGradientSegment *
+ svg_parser_gradient_segments (GList *stops);
+
+static SvgStop * svg_parse_gradient_stop (const gchar **names,
+ const gchar **values);
+
+
+static const GMarkupParser markup_parser =
+{
+ svg_parser_start_element,
+ svg_parser_end_element,
+ NULL, /* characters */
+ NULL, /* passthrough */
+ NULL /* error */
+};
+
+
+GList *
+gimp_gradient_load_svg (GimpContext *context,
+ GFile *file,
+ GInputStream *input,
+ GError **error)
+{
+ GimpXmlParser *xml_parser;
+ SvgParser parser = { NULL, };
+ gboolean success;
+
+ g_return_val_if_fail (G_IS_FILE (file), NULL);
+ g_return_val_if_fail (G_IS_INPUT_STREAM (input), NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ /* FIXME input */
+ g_input_stream_close (input, NULL, NULL);
+
+ xml_parser = gimp_xml_parser_new (&markup_parser, &parser);
+
+ success = gimp_xml_parser_parse_gfile (xml_parser, file, error);
+
+ gimp_xml_parser_free (xml_parser);
+
+ if (success && ! parser.gradients)
+ {
+ g_set_error (error, GIMP_DATA_ERROR, GIMP_DATA_ERROR_READ,
+ _("No linear gradients found."));
+ }
+
+ if (parser.gradient)
+ g_object_unref (parser.gradient);
+
+ if (parser.stops)
+ {
+ GList *list;
+
+ for (list = parser.stops; list; list = list->next)
+ g_slice_free (SvgStop, list->data);
+
+ g_list_free (parser.stops);
+ }
+
+ return g_list_reverse (parser.gradients);
+}
+
+static void
+svg_parser_start_element (GMarkupParseContext *context,
+ const gchar *element_name,
+ const gchar **attribute_names,
+ const gchar **attribute_values,
+ gpointer user_data,
+ GError **error)
+{
+ SvgParser *parser = user_data;
+
+ if (! parser->gradient && strcmp (element_name, "linearGradient") == 0)
+ {
+ const gchar *name = NULL;
+
+ while (*attribute_names && *attribute_values)
+ {
+ if (strcmp (*attribute_names, "id") == 0 && *attribute_values)
+ name = *attribute_values;
+
+ attribute_names++;
+ attribute_values++;
+ }
+
+ parser->gradient = g_object_new (GIMP_TYPE_GRADIENT,
+ "name", name,
+ "mime-type", "image/svg+xml",
+ NULL);
+ }
+ else if (parser->gradient && strcmp (element_name, "stop") == 0)
+ {
+ SvgStop *stop = svg_parse_gradient_stop (attribute_names,
+ attribute_values);
+
+ /* The spec clearly states that each gradient stop's offset
+ * value is required to be equal to or greater than the
+ * previous gradient stop's offset value.
+ */
+ if (parser->stops)
+ stop->offset = MAX (stop->offset,
+ ((SvgStop *) parser->stops->data)->offset);
+
+ parser->stops = g_list_prepend (parser->stops, stop);
+ }
+}
+
+static void
+svg_parser_end_element (GMarkupParseContext *context,
+ const gchar *element_name,
+ gpointer user_data,
+ GError **error)
+{
+ SvgParser *parser = user_data;
+
+ if (parser->gradient &&
+ strcmp (element_name, "linearGradient") == 0)
+ {
+ GList *list;
+
+ parser->gradient->segments = svg_parser_gradient_segments (parser->stops);
+
+ for (list = parser->stops; list; list = list->next)
+ g_slice_free (SvgStop, list->data);
+
+ g_list_free (parser->stops);
+ parser->stops = NULL;
+
+ if (parser->gradient->segments)
+ parser->gradients = g_list_prepend (parser->gradients,
+ parser->gradient);
+ else
+ g_object_unref (parser->gradient);
+
+ parser->gradient = NULL;
+ }
+}
+
+static GimpGradientSegment *
+svg_parser_gradient_segments (GList *stops)
+{
+ GimpGradientSegment *segment;
+ SvgStop *stop;
+ GList *list;
+
+ if (! stops)
+ return NULL;
+
+ stop = stops->data;
+
+ segment = gimp_gradient_segment_new ();
+
+ segment->left_color = stop->color;
+ segment->right_color = stop->color;
+
+ /* the list of offsets is sorted from largest to smallest */
+ for (list = g_list_next (stops); list; list = g_list_next (list))
+ {
+ GimpGradientSegment *next = segment;
+
+ segment->left = stop->offset;
+ segment->middle = (segment->left + segment->right) / 2.0;
+
+ segment = gimp_gradient_segment_new ();
+
+ segment->next = next;
+ next->prev = segment;
+
+ segment->right = stop->offset;
+ segment->right_color = stop->color;
+
+ stop = list->data;
+
+ segment->left_color = stop->color;
+ }
+
+ segment->middle = (segment->left + segment->right) / 2.0;
+
+ if (stop->offset > 0.0)
+ segment->right_color = stop->color;
+
+ /* FIXME: remove empty segments here or add a GimpGradient API to do that
+ */
+
+ return segment;
+}
+
+static void
+svg_parse_gradient_stop_style_prop (SvgStop *stop,
+ const gchar *name,
+ const gchar *value)
+{
+ if (strcmp (name, "stop-color") == 0)
+ {
+ gimp_rgb_parse_css (&stop->color, value, -1);
+ }
+ else if (strcmp (name, "stop-opacity") == 0)
+ {
+ gdouble opacity = g_ascii_strtod (value, NULL);
+
+ if (errno != ERANGE)
+ gimp_rgb_set_alpha (&stop->color, CLAMP (opacity, 0.0, 1.0));
+ }
+}
+
+/* very simplistic CSS style parser */
+static void
+svg_parse_gradient_stop_style (SvgStop *stop,
+ const gchar *style)
+{
+ const gchar *end;
+ const gchar *sep;
+
+ while (*style)
+ {
+ while (g_ascii_isspace (*style))
+ style++;
+
+ for (end = style; *end && *end != ';'; end++)
+ /* do nothing */;
+
+ for (sep = style; sep < end && *sep != ':'; sep++)
+ /* do nothing */;
+
+ if (end > sep && sep > style)
+ {
+ gchar *name;
+ gchar *value;
+
+ name = g_strndup (style, sep - style);
+ sep++;
+ value = g_strndup (sep, end - sep - (*end == ';' ? 1 : 0));
+
+ svg_parse_gradient_stop_style_prop (stop, name, value);
+
+ g_free (value);
+ g_free (name);
+ }
+
+ style = end;
+
+ if (*style == ';')
+ style++;
+ }
+}
+
+static SvgStop *
+svg_parse_gradient_stop (const gchar **names,
+ const gchar **values)
+{
+ SvgStop *stop = g_slice_new0 (SvgStop);
+
+ gimp_rgb_set_alpha (&stop->color, 1.0);
+
+ while (*names && *values)
+ {
+ if (strcmp (*names, "offset") == 0)
+ {
+ gchar *end;
+
+ stop->offset = g_ascii_strtod (*values, &end);
+
+ if (end && *end == '%')
+ stop->offset /= 100.0;
+
+ stop->offset = CLAMP (stop->offset, 0.0, 1.0);
+ }
+ else if (strcmp (*names, "style") == 0)
+ {
+ svg_parse_gradient_stop_style (stop, *values);
+ }
+ else
+ {
+ svg_parse_gradient_stop_style_prop (stop, *names, *values);
+ }
+
+ names++;
+ values++;
+ }
+
+ return stop;
+}
diff --git a/app/core/gimpgradient-load.h b/app/core/gimpgradient-load.h
new file mode 100644
index 0000000..fcc648c
--- /dev/null
+++ b/app/core/gimpgradient-load.h
@@ -0,0 +1,36 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_GRADIENT_LOAD_H__
+#define __GIMP_GRADIENT_LOAD_H__
+
+
+#define GIMP_GRADIENT_FILE_EXTENSION ".ggr"
+#define GIMP_GRADIENT_SVG_FILE_EXTENSION ".svg"
+
+
+GList * gimp_gradient_load (GimpContext *context,
+ GFile *file,
+ GInputStream *input,
+ GError **error);
+GList * gimp_gradient_load_svg (GimpContext *context,
+ GFile *file,
+ GInputStream *input,
+ GError **error);
+
+
+#endif /* __GIMP_GRADIENT_LOAD_H__ */
diff --git a/app/core/gimpgradient-save.c b/app/core/gimpgradient-save.c
new file mode 100644
index 0000000..deca2d2
--- /dev/null
+++ b/app/core/gimpgradient-save.c
@@ -0,0 +1,221 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpbase/gimpbase.h"
+
+#include "core-types.h"
+
+#include "gimpgradient.h"
+#include "gimpgradient-save.h"
+
+#include "gimp-intl.h"
+
+
+gboolean
+gimp_gradient_save (GimpData *data,
+ GOutputStream *output,
+ GError **error)
+{
+ GimpGradient *gradient = GIMP_GRADIENT (data);
+ GString *string;
+ GimpGradientSegment *seg;
+ gint num_segments;
+
+ /* File format is:
+ *
+ * GIMP Gradient
+ * Name: name
+ * number_of_segments
+ * left middle right r0 g0 b0 a0 r1 g1 b1 a1 type coloring left_color_type
+ * left middle right r0 g0 b0 a0 r1 g1 b1 a1 type coloring right_color_type
+ * ...
+ */
+
+ string = g_string_new ("GIMP Gradient\n");
+
+ g_string_append_printf (string, "Name: %s\n",
+ gimp_object_get_name (gradient));
+
+ /* Count number of segments */
+ num_segments = 0;
+ seg = gradient->segments;
+
+ while (seg)
+ {
+ num_segments++;
+ seg = seg->next;
+ }
+
+ /* Write rest of file */
+ g_string_append_printf (string, "%d\n", num_segments);
+
+ for (seg = gradient->segments; seg; seg = seg->next)
+ {
+ gchar buf[11][G_ASCII_DTOSTR_BUF_SIZE];
+
+ g_ascii_dtostr (buf[0], G_ASCII_DTOSTR_BUF_SIZE, seg->left);
+ g_ascii_dtostr (buf[1], G_ASCII_DTOSTR_BUF_SIZE, seg->middle);
+ g_ascii_dtostr (buf[2], G_ASCII_DTOSTR_BUF_SIZE, seg->right);
+ g_ascii_dtostr (buf[3], G_ASCII_DTOSTR_BUF_SIZE, seg->left_color.r);
+ g_ascii_dtostr (buf[4], G_ASCII_DTOSTR_BUF_SIZE, seg->left_color.g);
+ g_ascii_dtostr (buf[5], G_ASCII_DTOSTR_BUF_SIZE, seg->left_color.b);
+ g_ascii_dtostr (buf[6], G_ASCII_DTOSTR_BUF_SIZE, seg->left_color.a);
+ g_ascii_dtostr (buf[7], G_ASCII_DTOSTR_BUF_SIZE, seg->right_color.r);
+ g_ascii_dtostr (buf[8], G_ASCII_DTOSTR_BUF_SIZE, seg->right_color.g);
+ g_ascii_dtostr (buf[9], G_ASCII_DTOSTR_BUF_SIZE, seg->right_color.b);
+ g_ascii_dtostr (buf[10], G_ASCII_DTOSTR_BUF_SIZE, seg->right_color.a);
+
+ g_string_append_printf (string,
+ "%s %s %s %s %s %s %s %s %s %s %s %d %d %d %d\n",
+ buf[0], buf[1], buf[2], /* left, middle, right */
+ buf[3], buf[4], buf[5], buf[6], /* left color */
+ buf[7], buf[8], buf[9], buf[10], /* right color */
+ (gint) seg->type,
+ (gint) seg->color,
+ (gint) seg->left_color_type,
+ (gint) seg->right_color_type);
+ }
+
+ if (! g_output_stream_write_all (output, string->str, string->len,
+ NULL, NULL, error))
+ {
+ g_string_free (string, TRUE);
+
+ return FALSE;
+ }
+
+ g_string_free (string, TRUE);
+
+ return TRUE;
+}
+
+gboolean
+gimp_gradient_save_pov (GimpGradient *gradient,
+ GFile *file,
+ GError **error)
+{
+ GOutputStream *output;
+ GString *string;
+ GimpGradientSegment *seg;
+ gchar buf[G_ASCII_DTOSTR_BUF_SIZE];
+ gchar color_buf[4][G_ASCII_DTOSTR_BUF_SIZE];
+ GError *my_error = NULL;
+
+ g_return_val_if_fail (GIMP_IS_GRADIENT (gradient), FALSE);
+ g_return_val_if_fail (G_IS_FILE (file), FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ output = G_OUTPUT_STREAM (g_file_replace (file,
+ NULL, FALSE, G_FILE_CREATE_NONE,
+ NULL, error));
+ if (! output)
+ return FALSE;
+
+ string = g_string_new ("/* color_map file created by GIMP */\n"
+ "/* https://www.gimp.org/ */\n"
+ "color_map {\n");
+
+ for (seg = gradient->segments; seg; seg = seg->next)
+ {
+ /* Left */
+ g_ascii_dtostr (buf, G_ASCII_DTOSTR_BUF_SIZE,
+ seg->left);
+ g_ascii_dtostr (color_buf[0], G_ASCII_DTOSTR_BUF_SIZE,
+ seg->left_color.r);
+ g_ascii_dtostr (color_buf[1], G_ASCII_DTOSTR_BUF_SIZE,
+ seg->left_color.g);
+ g_ascii_dtostr (color_buf[2], G_ASCII_DTOSTR_BUF_SIZE,
+ seg->left_color.b);
+ g_ascii_dtostr (color_buf[3], G_ASCII_DTOSTR_BUF_SIZE,
+ 1.0 - seg->left_color.a);
+
+ g_string_append_printf (string,
+ "\t[%s color rgbt <%s, %s, %s, %s>]\n",
+ buf,
+ color_buf[0], color_buf[1],
+ color_buf[2], color_buf[3]);
+
+ /* Middle */
+ g_ascii_dtostr (buf, G_ASCII_DTOSTR_BUF_SIZE,
+ seg->middle);
+ g_ascii_dtostr (color_buf[0], G_ASCII_DTOSTR_BUF_SIZE,
+ (seg->left_color.r + seg->right_color.r) / 2.0);
+ g_ascii_dtostr (color_buf[1], G_ASCII_DTOSTR_BUF_SIZE,
+ (seg->left_color.g + seg->right_color.g) / 2.0);
+ g_ascii_dtostr (color_buf[2], G_ASCII_DTOSTR_BUF_SIZE,
+ (seg->left_color.b + seg->right_color.b) / 2.0);
+ g_ascii_dtostr (color_buf[3], G_ASCII_DTOSTR_BUF_SIZE,
+ 1.0 - (seg->left_color.a + seg->right_color.a) / 2.0);
+
+ g_string_append_printf (string,
+ "\t[%s color rgbt <%s, %s, %s, %s>]\n",
+ buf,
+ color_buf[0], color_buf[1],
+ color_buf[2], color_buf[3]);
+
+ /* Right */
+ g_ascii_dtostr (buf, G_ASCII_DTOSTR_BUF_SIZE,
+ seg->right);
+ g_ascii_dtostr (color_buf[0], G_ASCII_DTOSTR_BUF_SIZE,
+ seg->right_color.r);
+ g_ascii_dtostr (color_buf[1], G_ASCII_DTOSTR_BUF_SIZE,
+ seg->right_color.g);
+ g_ascii_dtostr (color_buf[2], G_ASCII_DTOSTR_BUF_SIZE,
+ seg->right_color.b);
+ g_ascii_dtostr (color_buf[3], G_ASCII_DTOSTR_BUF_SIZE,
+ 1.0 - seg->right_color.a);
+
+ g_string_append_printf (string,
+ "\t[%s color rgbt <%s, %s, %s, %s>]\n",
+ buf,
+ color_buf[0], color_buf[1],
+ color_buf[2], color_buf[3]);
+ }
+
+ g_string_append_printf (string, "} /* color_map */\n");
+
+ if (! g_output_stream_write_all (output, string->str, string->len,
+ NULL, NULL, &my_error))
+ {
+ GCancellable *cancellable = g_cancellable_new ();
+
+ g_set_error (error, GIMP_DATA_ERROR, GIMP_DATA_ERROR_WRITE,
+ _("Writing POV file '%s' failed: %s"),
+ gimp_file_get_utf8_name (file),
+ my_error->message);
+ g_clear_error (&my_error);
+ g_string_free (string, TRUE);
+
+ /* Cancel the overwrite initiated by g_file_replace(). */
+ g_cancellable_cancel (cancellable);
+ g_output_stream_close (output, cancellable, NULL);
+ g_object_unref (cancellable);
+ g_object_unref (output);
+
+ return FALSE;
+ }
+
+ g_string_free (string, TRUE);
+ g_object_unref (output);
+
+ return TRUE;
+}
diff --git a/app/core/gimpgradient-save.h b/app/core/gimpgradient-save.h
new file mode 100644
index 0000000..073bdd8
--- /dev/null
+++ b/app/core/gimpgradient-save.h
@@ -0,0 +1,32 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_GRADIENT_SAVE_H__
+#define __GIMP_GRADIENT_SAVE_H__
+
+
+/* don't call this function directly, use gimp_data_save() instead */
+gboolean gimp_gradient_save (GimpData *data,
+ GOutputStream *output,
+ GError **error);
+
+gboolean gimp_gradient_save_pov (GimpGradient *gradient,
+ GFile *file,
+ GError **error);
+
+
+#endif /* __GIMP_GRADIENT_SAVE_H__ */
diff --git a/app/core/gimpgradient.c b/app/core/gimpgradient.c
new file mode 100644
index 0000000..8265225
--- /dev/null
+++ b/app/core/gimpgradient.c
@@ -0,0 +1,2297 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <cairo.h>
+#include <gegl.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+#include "libgimpcolor/gimpcolor.h"
+#include "libgimpmath/gimpmath.h"
+
+#include "core-types.h"
+
+#include "gimpcontext.h"
+#include "gimpgradient.h"
+#include "gimpgradient-load.h"
+#include "gimpgradient-save.h"
+#include "gimptagged.h"
+#include "gimptempbuf.h"
+
+
+#define EPSILON 1e-10
+
+
+static void gimp_gradient_tagged_iface_init (GimpTaggedInterface *iface);
+static void gimp_gradient_finalize (GObject *object);
+
+static gint64 gimp_gradient_get_memsize (GimpObject *object,
+ gint64 *gui_size);
+
+static void gimp_gradient_get_preview_size (GimpViewable *viewable,
+ gint size,
+ gboolean popup,
+ gboolean dot_for_dot,
+ gint *width,
+ gint *height);
+static gboolean gimp_gradient_get_popup_size (GimpViewable *viewable,
+ gint width,
+ gint height,
+ gboolean dot_for_dot,
+ gint *popup_width,
+ gint *popup_height);
+static GimpTempBuf * gimp_gradient_get_new_preview (GimpViewable *viewable,
+ GimpContext *context,
+ gint width,
+ gint height);
+
+static const gchar * gimp_gradient_get_extension (GimpData *data);
+static void gimp_gradient_copy (GimpData *data,
+ GimpData *src_data);
+static gint gimp_gradient_compare (GimpData *data1,
+ GimpData *data2);
+
+static gchar * gimp_gradient_get_checksum (GimpTagged *tagged);
+
+static inline GimpGradientSegment *
+ gimp_gradient_get_segment_at_internal (GimpGradient *gradient,
+ GimpGradientSegment *seg,
+ gdouble pos);
+static void gimp_gradient_get_flat_color (GimpContext *context,
+ const GimpRGB *color,
+ GimpGradientColor color_type,
+ GimpRGB *flat_color);
+
+
+static inline gdouble gimp_gradient_calc_linear_factor (gdouble middle,
+ gdouble pos);
+static inline gdouble gimp_gradient_calc_curved_factor (gdouble middle,
+ gdouble pos);
+static inline gdouble gimp_gradient_calc_sine_factor (gdouble middle,
+ gdouble pos);
+static inline gdouble gimp_gradient_calc_sphere_increasing_factor (gdouble middle,
+ gdouble pos);
+static inline gdouble gimp_gradient_calc_sphere_decreasing_factor (gdouble middle,
+ gdouble pos);
+static inline gdouble gimp_gradient_calc_step_factor (gdouble middle,
+ gdouble pos);
+
+
+G_DEFINE_TYPE_WITH_CODE (GimpGradient, gimp_gradient, GIMP_TYPE_DATA,
+ G_IMPLEMENT_INTERFACE (GIMP_TYPE_TAGGED,
+ gimp_gradient_tagged_iface_init))
+
+#define parent_class gimp_gradient_parent_class
+
+static const Babl *fish_srgb_to_linear_rgb = NULL;
+static const Babl *fish_linear_rgb_to_srgb = NULL;
+static const Babl *fish_srgb_to_cie_lab = NULL;
+static const Babl *fish_cie_lab_to_srgb = NULL;
+
+
+static void
+gimp_gradient_class_init (GimpGradientClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpObjectClass *gimp_object_class = GIMP_OBJECT_CLASS (klass);
+ GimpViewableClass *viewable_class = GIMP_VIEWABLE_CLASS (klass);
+ GimpDataClass *data_class = GIMP_DATA_CLASS (klass);
+
+ object_class->finalize = gimp_gradient_finalize;
+
+ gimp_object_class->get_memsize = gimp_gradient_get_memsize;
+
+ viewable_class->default_icon_name = "gimp-tool-gradient";
+ viewable_class->get_preview_size = gimp_gradient_get_preview_size;
+ viewable_class->get_popup_size = gimp_gradient_get_popup_size;
+ viewable_class->get_new_preview = gimp_gradient_get_new_preview;
+
+ data_class->save = gimp_gradient_save;
+ data_class->get_extension = gimp_gradient_get_extension;
+ data_class->copy = gimp_gradient_copy;
+ data_class->compare = gimp_gradient_compare;
+
+ fish_srgb_to_linear_rgb = babl_fish (babl_format ("R'G'B' double"),
+ babl_format ("RGB double"));
+ fish_linear_rgb_to_srgb = babl_fish (babl_format ("RGB double"),
+ babl_format ("R'G'B' double"));
+ fish_srgb_to_cie_lab = babl_fish (babl_format ("R'G'B' double"),
+ babl_format ("CIE Lab double"));
+ fish_cie_lab_to_srgb = babl_fish (babl_format ("CIE Lab double"),
+ babl_format ("R'G'B' double"));
+}
+
+static void
+gimp_gradient_tagged_iface_init (GimpTaggedInterface *iface)
+{
+ iface->get_checksum = gimp_gradient_get_checksum;
+}
+
+static void
+gimp_gradient_init (GimpGradient *gradient)
+{
+ gradient->segments = NULL;
+}
+
+static void
+gimp_gradient_finalize (GObject *object)
+{
+ GimpGradient *gradient = GIMP_GRADIENT (object);
+
+ if (gradient->segments)
+ {
+ gimp_gradient_segments_free (gradient->segments);
+ gradient->segments = NULL;
+ }
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static gint64
+gimp_gradient_get_memsize (GimpObject *object,
+ gint64 *gui_size)
+{
+ GimpGradient *gradient = GIMP_GRADIENT (object);
+ GimpGradientSegment *segment;
+ gint64 memsize = 0;
+
+ for (segment = gradient->segments; segment; segment = segment->next)
+ memsize += sizeof (GimpGradientSegment);
+
+ return memsize + GIMP_OBJECT_CLASS (parent_class)->get_memsize (object,
+ gui_size);
+}
+
+static void
+gimp_gradient_get_preview_size (GimpViewable *viewable,
+ gint size,
+ gboolean popup,
+ gboolean dot_for_dot,
+ gint *width,
+ gint *height)
+{
+ *width = size;
+ *height = 1 + size / 2;
+}
+
+static gboolean
+gimp_gradient_get_popup_size (GimpViewable *viewable,
+ gint width,
+ gint height,
+ gboolean dot_for_dot,
+ gint *popup_width,
+ gint *popup_height)
+{
+ if (width < 128 || height < 32)
+ {
+ *popup_width = 128;
+ *popup_height = 32;
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static GimpTempBuf *
+gimp_gradient_get_new_preview (GimpViewable *viewable,
+ GimpContext *context,
+ gint width,
+ gint height)
+{
+ GimpGradient *gradient = GIMP_GRADIENT (viewable);
+ GimpGradientSegment *seg = NULL;
+ GimpTempBuf *temp_buf;
+ guchar *buf;
+ guchar *p;
+ guchar *row;
+ gint x, y;
+ gdouble dx, cur_x;
+ GimpRGB color;
+
+ dx = 1.0 / (width - 1);
+ cur_x = 0.0;
+ p = row = g_malloc (width * 4);
+
+ /* Create lines to fill the image */
+
+ for (x = 0; x < width; x++)
+ {
+ seg = gimp_gradient_get_color_at (gradient, context, seg, cur_x,
+ FALSE,
+ GIMP_GRADIENT_BLEND_RGB_PERCEPTUAL,
+ &color);
+
+ *p++ = ROUND (color.r * 255.0);
+ *p++ = ROUND (color.g * 255.0);
+ *p++ = ROUND (color.b * 255.0);
+ *p++ = ROUND (color.a * 255.0);
+
+ cur_x += dx;
+ }
+
+ temp_buf = gimp_temp_buf_new (width, height, babl_format ("R'G'B'A u8"));
+
+ buf = gimp_temp_buf_get_data (temp_buf);
+
+ for (y = 0; y < height; y++)
+ memcpy (buf + (width * y * 4), row, width * 4);
+
+ g_free (row);
+
+ return temp_buf;
+}
+
+static void
+gimp_gradient_copy (GimpData *data,
+ GimpData *src_data)
+{
+ GimpGradient *gradient = GIMP_GRADIENT (data);
+ GimpGradient *src_gradient = GIMP_GRADIENT (src_data);
+ GimpGradientSegment *head, *prev, *cur, *orig;
+
+ if (gradient->segments)
+ {
+ gimp_gradient_segments_free (gradient->segments);
+ gradient->segments = NULL;
+ }
+
+ prev = NULL;
+ orig = src_gradient->segments;
+ head = NULL;
+
+ while (orig)
+ {
+ cur = gimp_gradient_segment_new ();
+
+ *cur = *orig; /* Copy everything */
+
+ cur->prev = prev;
+ cur->next = NULL;
+
+ if (prev)
+ prev->next = cur;
+ else
+ head = cur; /* Remember head */
+
+ prev = cur;
+ orig = orig->next;
+ }
+
+ gradient->segments = head;
+
+ gimp_data_dirty (GIMP_DATA (gradient));
+}
+
+static gint
+gimp_gradient_compare (GimpData *data1,
+ GimpData *data2)
+{
+ gboolean is_custom1;
+ gboolean is_custom2;
+
+ /* check whether data1 and data2 are the custom gradient, which is the only
+ * writable internal gradient.
+ */
+ is_custom1 = gimp_data_is_internal (data1) && gimp_data_is_writable (data1);
+ is_custom2 = gimp_data_is_internal (data2) && gimp_data_is_writable (data2);
+
+ /* order the custom gradient before all the other gradients; use the default
+ * ordering for the rest.
+ */
+ if (is_custom1)
+ {
+ if (is_custom2)
+ return 0;
+ else
+ return -1;
+ }
+ else if (is_custom2)
+ {
+ return +1;
+ }
+ else
+ return GIMP_DATA_CLASS (parent_class)->compare (data1, data2);
+}
+
+static gchar *
+gimp_gradient_get_checksum (GimpTagged *tagged)
+{
+ GimpGradient *gradient = GIMP_GRADIENT (tagged);
+ gchar *checksum_string = NULL;
+
+ if (gradient->segments)
+ {
+ GChecksum *checksum = g_checksum_new (G_CHECKSUM_MD5);
+ GimpGradientSegment *segment = gradient->segments;
+
+ while (segment)
+ {
+ g_checksum_update (checksum,
+ (const guchar *) &segment->left,
+ sizeof (segment->left));
+ g_checksum_update (checksum,
+ (const guchar *) &segment->middle,
+ sizeof (segment->middle));
+ g_checksum_update (checksum,
+ (const guchar *) &segment->right,
+ sizeof (segment->right));
+ g_checksum_update (checksum,
+ (const guchar *) &segment->left_color_type,
+ sizeof (segment->left_color_type));
+ g_checksum_update (checksum,
+ (const guchar *) &segment->left_color,
+ sizeof (segment->left_color));
+ g_checksum_update (checksum,
+ (const guchar *) &segment->right_color_type,
+ sizeof (segment->right_color_type));
+ g_checksum_update (checksum,
+ (const guchar *) &segment->right_color,
+ sizeof (segment->right_color));
+ g_checksum_update (checksum,
+ (const guchar *) &segment->type,
+ sizeof (segment->type));
+ g_checksum_update (checksum,
+ (const guchar *) &segment->color,
+ sizeof (segment->color));
+
+ segment = segment->next;
+ }
+
+ checksum_string = g_strdup (g_checksum_get_string (checksum));
+
+ g_checksum_free (checksum);
+ }
+
+ return checksum_string;
+}
+
+
+/* public functions */
+
+GimpData *
+gimp_gradient_new (GimpContext *context,
+ const gchar *name)
+{
+ GimpGradient *gradient;
+
+ g_return_val_if_fail (name != NULL, NULL);
+ g_return_val_if_fail (*name != '\0', NULL);
+
+ gradient = g_object_new (GIMP_TYPE_GRADIENT,
+ "name", name,
+ NULL);
+
+ gradient->segments = gimp_gradient_segment_new ();
+
+ return GIMP_DATA (gradient);
+}
+
+GimpData *
+gimp_gradient_get_standard (GimpContext *context)
+{
+ static GimpData *standard_gradient = NULL;
+
+ if (! standard_gradient)
+ {
+ standard_gradient = gimp_gradient_new (context, "Standard");
+
+ gimp_data_clean (standard_gradient);
+ gimp_data_make_internal (standard_gradient, "gimp-gradient-standard");
+
+ g_object_add_weak_pointer (G_OBJECT (standard_gradient),
+ (gpointer *) &standard_gradient);
+ }
+
+ return standard_gradient;
+}
+
+static const gchar *
+gimp_gradient_get_extension (GimpData *data)
+{
+ return GIMP_GRADIENT_FILE_EXTENSION;
+}
+
+/**
+ * gimp_gradient_get_color_at:
+ * @gradient: a gradient
+ * @context: a context
+ * @seg: a segment to seed the search with (or %NULL)
+ * @pos: position in the gradient (between 0.0 and 1.0)
+ * @reverse: when %TRUE, use the reversed gradient
+ * @blend_color_space: color space to use for blending RGB segments
+ * @color: returns the color
+ *
+ * If you are iterating over an gradient, you should pass the the
+ * return value from the last call for @seg.
+ *
+ * Return value: the gradient segment the color is from
+ **/
+GimpGradientSegment *
+gimp_gradient_get_color_at (GimpGradient *gradient,
+ GimpContext *context,
+ GimpGradientSegment *seg,
+ gdouble pos,
+ gboolean reverse,
+ GimpGradientBlendColorSpace blend_color_space,
+ GimpRGB *color)
+{
+ gdouble factor = 0.0;
+ gdouble seg_len;
+ gdouble middle;
+ GimpRGB left_color;
+ GimpRGB right_color;
+ GimpRGB rgb;
+
+ /* type-check disabled to improve speed */
+ /* g_return_val_if_fail (GIMP_IS_GRADIENT (gradient), NULL); */
+ g_return_val_if_fail (color != NULL, NULL);
+
+ pos = CLAMP (pos, 0.0, 1.0);
+
+ if (reverse)
+ pos = 1.0 - pos;
+
+ seg = gimp_gradient_get_segment_at_internal (gradient, seg, pos);
+
+ seg_len = seg->right - seg->left;
+
+ if (seg_len < EPSILON)
+ {
+ middle = 0.5;
+ pos = 0.5;
+ }
+ else
+ {
+ middle = (seg->middle - seg->left) / seg_len;
+ pos = (pos - seg->left) / seg_len;
+ }
+
+ switch (seg->type)
+ {
+ case GIMP_GRADIENT_SEGMENT_LINEAR:
+ factor = gimp_gradient_calc_linear_factor (middle, pos);
+ break;
+
+ case GIMP_GRADIENT_SEGMENT_CURVED:
+ factor = gimp_gradient_calc_curved_factor (middle, pos);
+ break;
+
+ case GIMP_GRADIENT_SEGMENT_SINE:
+ factor = gimp_gradient_calc_sine_factor (middle, pos);
+ break;
+
+ case GIMP_GRADIENT_SEGMENT_SPHERE_INCREASING:
+ factor = gimp_gradient_calc_sphere_increasing_factor (middle, pos);
+ break;
+
+ case GIMP_GRADIENT_SEGMENT_SPHERE_DECREASING:
+ factor = gimp_gradient_calc_sphere_decreasing_factor (middle, pos);
+ break;
+
+ case GIMP_GRADIENT_SEGMENT_STEP:
+ factor = gimp_gradient_calc_step_factor (middle, pos);
+ break;
+
+ default:
+ g_warning ("%s: Unknown gradient type %d.", G_STRFUNC, seg->type);
+ break;
+ }
+
+ /* Get left/right colors */
+
+ if (context)
+ {
+ gimp_gradient_segment_get_left_flat_color (gradient,
+ context, seg, &left_color);
+
+ gimp_gradient_segment_get_right_flat_color (gradient,
+ context, seg, &right_color);
+ }
+ else
+ {
+ left_color = seg->left_color;
+ right_color = seg->right_color;
+ }
+
+ /* Calculate color components */
+
+ if (seg->color == GIMP_GRADIENT_SEGMENT_RGB)
+ {
+ switch (blend_color_space)
+ {
+ case GIMP_GRADIENT_BLEND_CIE_LAB:
+ babl_process (fish_srgb_to_cie_lab,
+ &left_color, &left_color, 1);
+ babl_process (fish_srgb_to_cie_lab,
+ &right_color, &right_color, 1);
+ break;
+
+ case GIMP_GRADIENT_BLEND_RGB_LINEAR:
+ babl_process (fish_srgb_to_linear_rgb,
+ &left_color, &left_color, 1);
+ babl_process (fish_srgb_to_linear_rgb,
+ &right_color, &right_color, 1);
+
+ case GIMP_GRADIENT_BLEND_RGB_PERCEPTUAL:
+ break;
+ }
+
+ rgb.r = left_color.r + (right_color.r - left_color.r) * factor;
+ rgb.g = left_color.g + (right_color.g - left_color.g) * factor;
+ rgb.b = left_color.b + (right_color.b - left_color.b) * factor;
+
+ switch (blend_color_space)
+ {
+ case GIMP_GRADIENT_BLEND_CIE_LAB:
+ babl_process (fish_cie_lab_to_srgb,
+ &rgb, &rgb, 1);
+ break;
+
+ case GIMP_GRADIENT_BLEND_RGB_LINEAR:
+ babl_process (fish_linear_rgb_to_srgb,
+ &rgb, &rgb, 1);
+
+ case GIMP_GRADIENT_BLEND_RGB_PERCEPTUAL:
+ break;
+ }
+ }
+ else
+ {
+ GimpHSV left_hsv;
+ GimpHSV right_hsv;
+
+ gimp_rgb_to_hsv (&left_color, &left_hsv);
+ gimp_rgb_to_hsv (&right_color, &right_hsv);
+
+ left_hsv.s = left_hsv.s + (right_hsv.s - left_hsv.s) * factor;
+ left_hsv.v = left_hsv.v + (right_hsv.v - left_hsv.v) * factor;
+
+ switch (seg->color)
+ {
+ case GIMP_GRADIENT_SEGMENT_HSV_CCW:
+ if (left_hsv.h < right_hsv.h)
+ {
+ left_hsv.h += (right_hsv.h - left_hsv.h) * factor;
+ }
+ else
+ {
+ left_hsv.h += (1.0 - (left_hsv.h - right_hsv.h)) * factor;
+
+ if (left_hsv.h > 1.0)
+ left_hsv.h -= 1.0;
+ }
+ break;
+
+ case GIMP_GRADIENT_SEGMENT_HSV_CW:
+ if (right_hsv.h < left_hsv.h)
+ {
+ left_hsv.h -= (left_hsv.h - right_hsv.h) * factor;
+ }
+ else
+ {
+ left_hsv.h -= (1.0 - (right_hsv.h - left_hsv.h)) * factor;
+
+ if (left_hsv.h < 0.0)
+ left_hsv.h += 1.0;
+ }
+ break;
+
+ default:
+ g_warning ("%s: Unknown coloring mode %d",
+ G_STRFUNC, (gint) seg->color);
+ break;
+ }
+
+ gimp_hsv_to_rgb (&left_hsv, &rgb);
+ }
+
+ /* Calculate alpha */
+
+ rgb.a = left_color.a + (right_color.a - left_color.a) * factor;
+
+ *color = rgb;
+
+ return seg;
+}
+
+GimpGradientSegment *
+gimp_gradient_get_segment_at (GimpGradient *gradient,
+ gdouble pos)
+{
+ g_return_val_if_fail (GIMP_IS_GRADIENT (gradient), NULL);
+
+ return gimp_gradient_get_segment_at_internal (gradient, NULL, pos);
+}
+
+gboolean
+gimp_gradient_has_fg_bg_segments (GimpGradient *gradient)
+{
+ GimpGradientSegment *segment;
+
+ g_return_val_if_fail (GIMP_IS_GRADIENT (gradient), FALSE);
+
+ for (segment = gradient->segments; segment; segment = segment->next)
+ if (segment->left_color_type != GIMP_GRADIENT_COLOR_FIXED ||
+ segment->right_color_type != GIMP_GRADIENT_COLOR_FIXED)
+ return TRUE;
+
+ return FALSE;
+}
+
+void
+gimp_gradient_split_at (GimpGradient *gradient,
+ GimpContext *context,
+ GimpGradientSegment *seg,
+ gdouble pos,
+ GimpGradientBlendColorSpace blend_color_space,
+ GimpGradientSegment **newl,
+ GimpGradientSegment **newr)
+{
+ GimpRGB color;
+ GimpGradientSegment *newseg;
+
+ g_return_if_fail (GIMP_IS_GRADIENT (gradient));
+ g_return_if_fail (GIMP_IS_CONTEXT (context));
+
+ gimp_data_freeze (GIMP_DATA (gradient));
+
+ pos = CLAMP (pos, 0.0, 1.0);
+ seg = gimp_gradient_get_segment_at_internal (gradient, seg, pos);
+
+ /* Get color at pos */
+ gimp_gradient_get_color_at (gradient, context, seg, pos,
+ FALSE, blend_color_space, &color);
+
+ /* Create a new segment and insert it in the list */
+
+ newseg = gimp_gradient_segment_new ();
+
+ newseg->prev = seg;
+ newseg->next = seg->next;
+
+ seg->next = newseg;
+
+ if (newseg->next)
+ newseg->next->prev = newseg;
+
+ /* Set coordinates of new segment */
+
+ newseg->left = pos;
+ newseg->right = seg->right;
+ newseg->middle = (newseg->left + newseg->right) / 2.0;
+
+ /* Set coordinates of original segment */
+
+ seg->right = newseg->left;
+ seg->middle = (seg->left + seg->right) / 2.0;
+
+ /* Set colors of both segments */
+
+ newseg->right_color_type = seg->right_color_type;
+ newseg->right_color = seg->right_color;
+
+ seg->right_color_type = newseg->left_color_type = GIMP_GRADIENT_COLOR_FIXED;
+ seg->right_color = newseg->left_color = color;
+
+ /* Set parameters of new segment */
+
+ newseg->type = seg->type;
+ newseg->color = seg->color;
+
+ /* Done */
+
+ if (newl) *newl = seg;
+ if (newr) *newr = newseg;
+
+ gimp_data_thaw (GIMP_DATA (gradient));
+}
+
+GimpGradient *
+gimp_gradient_flatten (GimpGradient *gradient,
+ GimpContext *context)
+{
+ GimpGradient *flat;
+ GimpGradientSegment *seg;
+
+ g_return_val_if_fail (GIMP_IS_GRADIENT (gradient), NULL);
+ g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL);
+
+ flat = GIMP_GRADIENT (gimp_data_duplicate (GIMP_DATA (gradient)));
+
+ for (seg = flat->segments; seg; seg = seg->next)
+ {
+ gimp_gradient_segment_get_left_flat_color (gradient,
+ context, seg,
+ &seg->left_color);
+
+ seg->left_color_type = GIMP_GRADIENT_COLOR_FIXED;
+
+ gimp_gradient_segment_get_right_flat_color (gradient,
+ context, seg,
+ &seg->right_color);
+
+ seg->right_color_type = GIMP_GRADIENT_COLOR_FIXED;
+ }
+
+ return flat;
+}
+
+
+/* gradient segment functions */
+
+GimpGradientSegment *
+gimp_gradient_segment_new (void)
+{
+ GimpGradientSegment *seg;
+
+ seg = g_slice_new0 (GimpGradientSegment);
+
+ seg->left = 0.0;
+ seg->middle = 0.5;
+ seg->right = 1.0;
+
+ seg->left_color_type = GIMP_GRADIENT_COLOR_FIXED;
+ gimp_rgba_set (&seg->left_color, 0.0, 0.0, 0.0, 1.0);
+
+ seg->right_color_type = GIMP_GRADIENT_COLOR_FIXED;
+ gimp_rgba_set (&seg->right_color, 1.0, 1.0, 1.0, 1.0);
+
+ seg->type = GIMP_GRADIENT_SEGMENT_LINEAR;
+ seg->color = GIMP_GRADIENT_SEGMENT_RGB;
+
+ seg->prev = seg->next = NULL;
+
+ return seg;
+}
+
+
+void
+gimp_gradient_segment_free (GimpGradientSegment *seg)
+{
+ g_return_if_fail (seg != NULL);
+
+ g_slice_free (GimpGradientSegment, seg);
+}
+
+void
+gimp_gradient_segments_free (GimpGradientSegment *seg)
+{
+ g_return_if_fail (seg != NULL);
+
+ g_slice_free_chain (GimpGradientSegment, seg, next);
+}
+
+GimpGradientSegment *
+gimp_gradient_segment_get_last (GimpGradientSegment *seg)
+{
+ if (! seg)
+ return NULL;
+
+ while (seg->next)
+ seg = seg->next;
+
+ return seg;
+}
+
+GimpGradientSegment *
+gimp_gradient_segment_get_first (GimpGradientSegment *seg)
+{
+ if (! seg)
+ return NULL;
+
+ while (seg->prev)
+ seg = seg->prev;
+
+ return seg;
+}
+
+GimpGradientSegment *
+gimp_gradient_segment_get_nth (GimpGradientSegment *seg,
+ gint index)
+{
+ gint i = 0;
+
+ g_return_val_if_fail (index >= 0, NULL);
+
+ if (! seg)
+ return NULL;
+
+ while (seg && (i < index))
+ {
+ seg = seg->next;
+ i++;
+ }
+
+ if (i == index)
+ return seg;
+
+ return NULL;
+}
+
+void
+gimp_gradient_segment_split_midpoint (GimpGradient *gradient,
+ GimpContext *context,
+ GimpGradientSegment *lseg,
+ GimpGradientBlendColorSpace blend_color_space,
+ GimpGradientSegment **newl,
+ GimpGradientSegment **newr)
+{
+ g_return_if_fail (GIMP_IS_GRADIENT (gradient));
+ g_return_if_fail (GIMP_IS_CONTEXT (context));
+ g_return_if_fail (lseg != NULL);
+ g_return_if_fail (newl != NULL);
+ g_return_if_fail (newr != NULL);
+
+ gimp_gradient_split_at (gradient, context, lseg, lseg->middle,
+ blend_color_space, newl, newr);
+}
+
+void
+gimp_gradient_segment_split_uniform (GimpGradient *gradient,
+ GimpContext *context,
+ GimpGradientSegment *lseg,
+ gint parts,
+ GimpGradientBlendColorSpace blend_color_space,
+ GimpGradientSegment **newl,
+ GimpGradientSegment **newr)
+{
+ GimpGradientSegment *seg, *prev, *tmp;
+ gdouble seg_len;
+ gint i;
+
+ g_return_if_fail (GIMP_IS_GRADIENT (gradient));
+ g_return_if_fail (GIMP_IS_CONTEXT (context));
+ g_return_if_fail (lseg != NULL);
+ g_return_if_fail (newl != NULL);
+ g_return_if_fail (newr != NULL);
+
+ gimp_data_freeze (GIMP_DATA (gradient));
+
+ seg_len = (lseg->right - lseg->left) / parts; /* Length of divisions */
+
+ seg = NULL;
+ prev = NULL;
+ tmp = NULL;
+
+ for (i = 0; i < parts; i++)
+ {
+ seg = gimp_gradient_segment_new ();
+
+ if (i == 0)
+ tmp = seg; /* Remember first segment */
+
+ seg->left = lseg->left + i * seg_len;
+ seg->right = lseg->left + (i + 1) * seg_len;
+ seg->middle = (seg->left + seg->right) / 2.0;
+
+ seg->left_color_type = GIMP_GRADIENT_COLOR_FIXED;
+ seg->right_color_type = GIMP_GRADIENT_COLOR_FIXED;
+
+ gimp_gradient_get_color_at (gradient, context, lseg,
+ seg->left, FALSE, blend_color_space,
+ &seg->left_color);
+ gimp_gradient_get_color_at (gradient, context, lseg,
+ seg->right, FALSE, blend_color_space,
+ &seg->right_color);
+
+ seg->type = lseg->type;
+ seg->color = lseg->color;
+
+ seg->prev = prev;
+ seg->next = NULL;
+
+ if (prev)
+ prev->next = seg;
+
+ prev = seg;
+ }
+
+ /* Fix edges */
+
+ tmp->left_color_type = lseg->left_color_type;
+ tmp->left_color = lseg->left_color;
+
+ seg->right_color_type = lseg->right_color_type;
+ seg->right_color = lseg->right_color;
+
+ tmp->left = lseg->left;
+ seg->right = lseg->right; /* To squish accumulative error */
+
+ /* Link in list */
+
+ tmp->prev = lseg->prev;
+ seg->next = lseg->next;
+
+ if (lseg->prev)
+ lseg->prev->next = tmp;
+ else
+ gradient->segments = tmp; /* We are on leftmost segment */
+
+ if (lseg->next)
+ lseg->next->prev = seg;
+
+ /* Done */
+ *newl = tmp;
+ *newr = seg;
+
+ /* Delete old segment */
+ gimp_gradient_segment_free (lseg);
+
+ gimp_data_thaw (GIMP_DATA (gradient));
+}
+
+void
+gimp_gradient_segment_get_left_color (GimpGradient *gradient,
+ GimpGradientSegment *seg,
+ GimpRGB *color)
+{
+ g_return_if_fail (GIMP_IS_GRADIENT (gradient));
+ g_return_if_fail (seg != NULL);
+ g_return_if_fail (color != NULL);
+
+ *color = seg->left_color;
+}
+
+void
+gimp_gradient_segment_set_left_color (GimpGradient *gradient,
+ GimpGradientSegment *seg,
+ const GimpRGB *color)
+{
+ g_return_if_fail (GIMP_IS_GRADIENT (gradient));
+ g_return_if_fail (seg != NULL);
+ g_return_if_fail (color != NULL);
+
+ gimp_data_freeze (GIMP_DATA (gradient));
+
+ gimp_gradient_segment_range_blend (gradient, seg, seg,
+ color, &seg->right_color,
+ TRUE, TRUE);
+
+ gimp_data_thaw (GIMP_DATA (gradient));
+}
+
+void
+gimp_gradient_segment_get_right_color (GimpGradient *gradient,
+ GimpGradientSegment *seg,
+ GimpRGB *color)
+{
+ g_return_if_fail (GIMP_IS_GRADIENT (gradient));
+ g_return_if_fail (seg != NULL);
+ g_return_if_fail (color != NULL);
+
+ *color = seg->right_color;
+}
+
+void
+gimp_gradient_segment_set_right_color (GimpGradient *gradient,
+ GimpGradientSegment *seg,
+ const GimpRGB *color)
+{
+ g_return_if_fail (GIMP_IS_GRADIENT (gradient));
+ g_return_if_fail (seg != NULL);
+ g_return_if_fail (color != NULL);
+
+ gimp_data_freeze (GIMP_DATA (gradient));
+
+ gimp_gradient_segment_range_blend (gradient, seg, seg,
+ &seg->left_color, color,
+ TRUE, TRUE);
+
+ gimp_data_thaw (GIMP_DATA (gradient));
+}
+
+GimpGradientColor
+gimp_gradient_segment_get_left_color_type (GimpGradient *gradient,
+ GimpGradientSegment *seg)
+{
+ g_return_val_if_fail (GIMP_IS_GRADIENT (gradient), 0);
+ g_return_val_if_fail (seg != NULL, 0);
+
+ return seg->left_color_type;
+}
+
+void
+gimp_gradient_segment_set_left_color_type (GimpGradient *gradient,
+ GimpGradientSegment *seg,
+ GimpGradientColor color_type)
+{
+ g_return_if_fail (GIMP_IS_GRADIENT (gradient));
+ g_return_if_fail (seg != NULL);
+
+ gimp_data_freeze (GIMP_DATA (gradient));
+
+ seg->left_color_type = color_type;
+
+ gimp_data_thaw (GIMP_DATA (gradient));
+}
+
+GimpGradientColor
+gimp_gradient_segment_get_right_color_type (GimpGradient *gradient,
+ GimpGradientSegment *seg)
+{
+ g_return_val_if_fail (GIMP_IS_GRADIENT (gradient), 0);
+ g_return_val_if_fail (seg != NULL, 0);
+
+ return seg->right_color_type;
+}
+
+void
+gimp_gradient_segment_set_right_color_type (GimpGradient *gradient,
+ GimpGradientSegment *seg,
+ GimpGradientColor color_type)
+{
+ g_return_if_fail (GIMP_IS_GRADIENT (gradient));
+ g_return_if_fail (seg != NULL);
+
+ gimp_data_freeze (GIMP_DATA (gradient));
+
+ seg->right_color_type = color_type;
+
+ gimp_data_thaw (GIMP_DATA (gradient));
+}
+
+void
+gimp_gradient_segment_get_left_flat_color (GimpGradient *gradient,
+ GimpContext *context,
+ GimpGradientSegment *seg,
+ GimpRGB *color)
+{
+ g_return_if_fail (GIMP_IS_GRADIENT (gradient));
+ g_return_if_fail (seg != NULL);
+ g_return_if_fail (color != NULL);
+
+ gimp_gradient_get_flat_color (context,
+ &seg->left_color, seg->left_color_type,
+ color);
+}
+
+void
+gimp_gradient_segment_get_right_flat_color (GimpGradient *gradient,
+ GimpContext *context,
+ GimpGradientSegment *seg,
+ GimpRGB *color)
+{
+ g_return_if_fail (GIMP_IS_GRADIENT (gradient));
+ g_return_if_fail (seg != NULL);
+ g_return_if_fail (color != NULL);
+
+ gimp_gradient_get_flat_color (context,
+ &seg->right_color, seg->right_color_type,
+ color);
+}
+
+gdouble
+gimp_gradient_segment_get_left_pos (GimpGradient *gradient,
+ GimpGradientSegment *seg)
+{
+ g_return_val_if_fail (GIMP_IS_GRADIENT (gradient), 0.0);
+ g_return_val_if_fail (seg != NULL, 0.0);
+
+ return seg->left;
+}
+
+gdouble
+gimp_gradient_segment_set_left_pos (GimpGradient *gradient,
+ GimpGradientSegment *seg,
+ gdouble pos)
+{
+ gdouble final_pos;
+
+ g_return_val_if_fail (GIMP_IS_GRADIENT (gradient), 0.0);
+ g_return_val_if_fail (seg != NULL, 0.0);
+
+ if (seg->prev == NULL)
+ {
+ final_pos = 0;
+ }
+ else
+ {
+ gimp_data_freeze (GIMP_DATA (gradient));
+
+ final_pos = seg->prev->right = seg->left =
+ CLAMP (pos,
+ seg->prev->middle + EPSILON,
+ seg->middle - EPSILON);
+
+ gimp_data_thaw (GIMP_DATA (gradient));
+ }
+
+ return final_pos;
+}
+
+gdouble
+gimp_gradient_segment_get_right_pos (GimpGradient *gradient,
+ GimpGradientSegment *seg)
+{
+ g_return_val_if_fail (GIMP_IS_GRADIENT (gradient), 0.0);
+ g_return_val_if_fail (seg != NULL, 0.0);
+
+ return seg->right;
+}
+
+gdouble
+gimp_gradient_segment_set_right_pos (GimpGradient *gradient,
+ GimpGradientSegment *seg,
+ gdouble pos)
+{
+ gdouble final_pos;
+
+ g_return_val_if_fail (GIMP_IS_GRADIENT (gradient), 0.0);
+ g_return_val_if_fail (seg != NULL, 0.0);
+
+ if (seg->next == NULL)
+ {
+ final_pos = 1.0;
+ }
+ else
+ {
+ gimp_data_freeze (GIMP_DATA (gradient));
+
+ final_pos = seg->next->left = seg->right =
+ CLAMP (pos,
+ seg->middle + EPSILON,
+ seg->next->middle - EPSILON);
+
+ gimp_data_thaw (GIMP_DATA (gradient));
+ }
+
+ return final_pos;
+}
+
+gdouble
+gimp_gradient_segment_get_middle_pos (GimpGradient *gradient,
+ GimpGradientSegment *seg)
+{
+ g_return_val_if_fail (GIMP_IS_GRADIENT (gradient), 0.0);
+ g_return_val_if_fail (seg != NULL, 0.0);
+
+ return seg->middle;
+}
+
+gdouble
+gimp_gradient_segment_set_middle_pos (GimpGradient *gradient,
+ GimpGradientSegment *seg,
+ gdouble pos)
+{
+ gdouble final_pos;
+
+ g_return_val_if_fail (GIMP_IS_GRADIENT (gradient), 0.0);
+ g_return_val_if_fail (seg != NULL, 0.0);
+
+ gimp_data_freeze (GIMP_DATA (gradient));
+
+ final_pos = seg->middle =
+ CLAMP (pos,
+ seg->left + EPSILON,
+ seg->right - EPSILON);
+
+ gimp_data_thaw (GIMP_DATA (gradient));
+
+ return final_pos;
+}
+
+GimpGradientSegmentType
+gimp_gradient_segment_get_blending_function (GimpGradient *gradient,
+ GimpGradientSegment *seg)
+{
+ g_return_val_if_fail (GIMP_IS_GRADIENT (gradient), 0);
+
+ return seg->type;
+}
+
+GimpGradientSegmentColor
+gimp_gradient_segment_get_coloring_type (GimpGradient *gradient,
+ GimpGradientSegment *seg)
+{
+ g_return_val_if_fail (GIMP_IS_GRADIENT (gradient), 0);
+
+ return seg->color;
+}
+
+gint
+gimp_gradient_segment_range_get_n_segments (GimpGradient *gradient,
+ GimpGradientSegment *range_l,
+ GimpGradientSegment *range_r)
+{
+ gint n_segments = 0;
+
+ g_return_val_if_fail (GIMP_IS_GRADIENT (gradient), 0);
+ g_return_val_if_fail (range_l != NULL, 0);
+
+ for (; range_l != range_r; range_l = range_l->next)
+ n_segments++;
+
+ if (range_r != NULL)
+ n_segments++;
+
+ return n_segments;
+}
+
+void
+gimp_gradient_segment_range_compress (GimpGradient *gradient,
+ GimpGradientSegment *range_l,
+ GimpGradientSegment *range_r,
+ gdouble new_l,
+ gdouble new_r)
+{
+ gdouble orig_l, orig_r;
+ GimpGradientSegment *seg, *aseg;
+
+ g_return_if_fail (GIMP_IS_GRADIENT (gradient));
+ g_return_if_fail (range_l != NULL);
+
+ gimp_data_freeze (GIMP_DATA (gradient));
+
+ if (! range_r)
+ range_r = gimp_gradient_segment_get_last (range_l);
+
+ orig_l = range_l->left;
+ orig_r = range_r->right;
+
+ if (orig_r - orig_l > EPSILON)
+ {
+ gdouble scale;
+
+ scale = (new_r - new_l) / (orig_r - orig_l);
+
+ seg = range_l;
+
+ do
+ {
+ if (seg->prev)
+ seg->left = new_l + (seg->left - orig_l) * scale;
+ seg->middle = new_l + (seg->middle - orig_l) * scale;
+ if (seg->next)
+ seg->right = new_l + (seg->right - orig_l) * scale;
+
+ /* Next */
+
+ aseg = seg;
+ seg = seg->next;
+ }
+ while (aseg != range_r);
+ }
+ else
+ {
+ gint n;
+ gint i;
+
+ n = gimp_gradient_segment_range_get_n_segments (gradient,
+ range_l, range_r);
+
+ for (i = 0, seg = range_l; i < n; i++, seg = seg->next)
+ {
+ if (seg->prev)
+ seg->left = new_l + (new_r - new_l) * (i + 0.0) / n;
+ seg->middle = new_l + (new_r - new_l) * (i + 0.5) / n;;
+ if (seg->next)
+ seg->right = new_l + (new_r - new_l) * (i + 1.0) / n;
+ }
+ }
+
+ /* Make sure that the left and right endpoints of the range are *exactly*
+ * equal to new_l and new_r; the above computations can introduce
+ * numerical inaccuracies.
+ */
+
+ range_l->left = new_l;
+ range_l->right = new_r;
+
+ gimp_data_thaw (GIMP_DATA (gradient));
+}
+
+void
+gimp_gradient_segment_range_blend (GimpGradient *gradient,
+ GimpGradientSegment *lseg,
+ GimpGradientSegment *rseg,
+ const GimpRGB *rgb1,
+ const GimpRGB *rgb2,
+ gboolean blend_colors,
+ gboolean blend_opacity)
+{
+ GimpRGB d;
+ gdouble left, len;
+ GimpGradientSegment *seg;
+ GimpGradientSegment *aseg;
+
+ g_return_if_fail (GIMP_IS_GRADIENT (gradient));
+ g_return_if_fail (lseg != NULL);
+
+ gimp_data_freeze (GIMP_DATA (gradient));
+
+ if (! rseg)
+ rseg = gimp_gradient_segment_get_last (lseg);
+
+ d.r = rgb2->r - rgb1->r;
+ d.g = rgb2->g - rgb1->g;
+ d.b = rgb2->b - rgb1->b;
+ d.a = rgb2->a - rgb1->a;
+
+ left = lseg->left;
+ len = rseg->right - left;
+
+ seg = lseg;
+
+ do
+ {
+ if (blend_colors)
+ {
+ seg->left_color.r = rgb1->r + (seg->left - left) / len * d.r;
+ seg->left_color.g = rgb1->g + (seg->left - left) / len * d.g;
+ seg->left_color.b = rgb1->b + (seg->left - left) / len * d.b;
+
+ seg->right_color.r = rgb1->r + (seg->right - left) / len * d.r;
+ seg->right_color.g = rgb1->g + (seg->right - left) / len * d.g;
+ seg->right_color.b = rgb1->b + (seg->right - left) / len * d.b;
+ }
+
+ if (blend_opacity)
+ {
+ seg->left_color.a = rgb1->a + (seg->left - left) / len * d.a;
+ seg->right_color.a = rgb1->a + (seg->right - left) / len * d.a;
+ }
+
+ aseg = seg;
+ seg = seg->next;
+ }
+ while (aseg != rseg);
+ gimp_data_thaw (GIMP_DATA (gradient));
+
+}
+
+void
+gimp_gradient_segment_range_set_blending_function (GimpGradient *gradient,
+ GimpGradientSegment *start_seg,
+ GimpGradientSegment *end_seg,
+ GimpGradientSegmentType new_type)
+{
+ GimpGradientSegment *seg;
+ gboolean reached_last_segment = FALSE;
+
+ g_return_if_fail (GIMP_IS_GRADIENT (gradient));
+
+ gimp_data_freeze (GIMP_DATA (gradient));
+
+ seg = start_seg;
+ while (seg && ! reached_last_segment)
+ {
+ if (seg == end_seg)
+ reached_last_segment = TRUE;
+
+ seg->type = new_type;
+ seg = seg->next;
+ }
+
+ gimp_data_thaw (GIMP_DATA (gradient));
+}
+
+void
+gimp_gradient_segment_range_set_coloring_type (GimpGradient *gradient,
+ GimpGradientSegment *start_seg,
+ GimpGradientSegment *end_seg,
+ GimpGradientSegmentColor new_color)
+{
+ GimpGradientSegment *seg;
+ gboolean reached_last_segment = FALSE;
+
+ g_return_if_fail (GIMP_IS_GRADIENT (gradient));
+
+ gimp_data_freeze (GIMP_DATA (gradient));
+
+ seg = start_seg;
+ while (seg && ! reached_last_segment)
+ {
+ if (seg == end_seg)
+ reached_last_segment = TRUE;
+
+ seg->color = new_color;
+ seg = seg->next;
+ }
+
+ gimp_data_thaw (GIMP_DATA (gradient));
+}
+
+void
+gimp_gradient_segment_range_flip (GimpGradient *gradient,
+ GimpGradientSegment *start_seg,
+ GimpGradientSegment *end_seg,
+ GimpGradientSegment **final_start_seg,
+ GimpGradientSegment **final_end_seg)
+{
+ GimpGradientSegment *oseg, *oaseg;
+ GimpGradientSegment *seg, *prev, *tmp;
+ GimpGradientSegment *lseg, *rseg;
+ gdouble left, right;
+
+ g_return_if_fail (GIMP_IS_GRADIENT (gradient));
+
+ gimp_data_freeze (GIMP_DATA (gradient));
+
+ if (! end_seg)
+ end_seg = gimp_gradient_segment_get_last (start_seg);
+
+ left = start_seg->left;
+ right = end_seg->right;
+
+ /* Build flipped segments */
+
+ prev = NULL;
+ oseg = end_seg;
+ tmp = NULL;
+
+ do
+ {
+ seg = gimp_gradient_segment_new ();
+
+ if (prev == NULL)
+ {
+ seg->left = left;
+ tmp = seg; /* Remember first segment */
+ }
+ else
+ seg->left = left + right - oseg->right;
+
+ seg->middle = left + right - oseg->middle;
+ seg->right = left + right - oseg->left;
+
+ seg->left_color_type = oseg->right_color_type;
+ seg->left_color = oseg->right_color;
+
+ seg->right_color_type = oseg->left_color_type;
+ seg->right_color = oseg->left_color;
+
+ switch (oseg->type)
+ {
+ case GIMP_GRADIENT_SEGMENT_SPHERE_INCREASING:
+ seg->type = GIMP_GRADIENT_SEGMENT_SPHERE_DECREASING;
+ break;
+
+ case GIMP_GRADIENT_SEGMENT_SPHERE_DECREASING:
+ seg->type = GIMP_GRADIENT_SEGMENT_SPHERE_INCREASING;
+ break;
+
+ default:
+ seg->type = oseg->type;
+ }
+
+ switch (oseg->color)
+ {
+ case GIMP_GRADIENT_SEGMENT_HSV_CCW:
+ seg->color = GIMP_GRADIENT_SEGMENT_HSV_CW;
+ break;
+
+ case GIMP_GRADIENT_SEGMENT_HSV_CW:
+ seg->color = GIMP_GRADIENT_SEGMENT_HSV_CCW;
+ break;
+
+ default:
+ seg->color = oseg->color;
+ }
+
+ seg->prev = prev;
+ seg->next = NULL;
+
+ if (prev)
+ prev->next = seg;
+
+ prev = seg;
+
+ oaseg = oseg;
+ oseg = oseg->prev; /* Move backwards! */
+ }
+ while (oaseg != start_seg);
+
+ seg->right = right; /* Squish accumulative error */
+
+ /* Free old segments */
+
+ lseg = start_seg->prev;
+ rseg = end_seg->next;
+
+ oseg = start_seg;
+
+ do
+ {
+ oaseg = oseg->next;
+ gimp_gradient_segment_free (oseg);
+ oseg = oaseg;
+ }
+ while (oaseg != rseg);
+
+ /* Link in new segments */
+
+ if (lseg)
+ lseg->next = tmp;
+ else
+ gradient->segments = tmp;
+
+ tmp->prev = lseg;
+
+ seg->next = rseg;
+
+ if (rseg)
+ rseg->prev = seg;
+
+ /* Reset selection */
+
+ if (final_start_seg)
+ *final_start_seg = tmp;
+
+ if (final_end_seg)
+ *final_end_seg = seg;
+
+ /* Done */
+
+ gimp_data_thaw (GIMP_DATA (gradient));
+}
+
+void
+gimp_gradient_segment_range_replicate (GimpGradient *gradient,
+ GimpGradientSegment *start_seg,
+ GimpGradientSegment *end_seg,
+ gint replicate_times,
+ GimpGradientSegment **final_start_seg,
+ GimpGradientSegment **final_end_seg)
+{
+ gdouble sel_left, sel_right, sel_len;
+ gdouble new_left;
+ gdouble factor;
+ GimpGradientSegment *prev, *seg, *tmp;
+ GimpGradientSegment *oseg, *oaseg;
+ GimpGradientSegment *lseg, *rseg;
+ gint i;
+
+ g_return_if_fail (GIMP_IS_GRADIENT (gradient));
+
+ if (! end_seg)
+ end_seg = gimp_gradient_segment_get_last (start_seg);
+
+ if (replicate_times < 2)
+ {
+ *final_start_seg = start_seg;
+ *final_end_seg = end_seg;
+ return;
+ }
+
+ gimp_data_freeze (GIMP_DATA (gradient));
+
+ /* Remember original parameters */
+ sel_left = start_seg->left;
+ sel_right = end_seg->right;
+ sel_len = sel_right - sel_left;
+
+ factor = 1.0 / replicate_times;
+
+ /* Build replicated segments */
+
+ prev = NULL;
+ seg = NULL;
+ tmp = NULL;
+
+ for (i = 0; i < replicate_times; i++)
+ {
+ /* Build one cycle */
+
+ new_left = sel_left + i * factor * sel_len;
+
+ oseg = start_seg;
+
+ do
+ {
+ seg = gimp_gradient_segment_new ();
+
+ if (prev == NULL)
+ {
+ seg->left = sel_left;
+ tmp = seg; /* Remember first segment */
+ }
+ else
+ {
+ seg->left = new_left + factor * (oseg->left - sel_left);
+ }
+
+ seg->middle = new_left + factor * (oseg->middle - sel_left);
+ seg->right = new_left + factor * (oseg->right - sel_left);
+
+ seg->left_color_type = oseg->left_color_type;
+ seg->left_color = oseg->left_color;
+
+ seg->right_color_type = oseg->right_color_type;
+ seg->right_color = oseg->right_color;
+
+ seg->type = oseg->type;
+ seg->color = oseg->color;
+
+ seg->prev = prev;
+ seg->next = NULL;
+
+ if (prev)
+ prev->next = seg;
+
+ prev = seg;
+
+ oaseg = oseg;
+ oseg = oseg->next;
+ }
+ while (oaseg != end_seg);
+ }
+
+ seg->right = sel_right; /* Squish accumulative error */
+
+ /* Free old segments */
+
+ lseg = start_seg->prev;
+ rseg = end_seg->next;
+
+ oseg = start_seg;
+
+ do
+ {
+ oaseg = oseg->next;
+ gimp_gradient_segment_free (oseg);
+ oseg = oaseg;
+ }
+ while (oaseg != rseg);
+
+ /* Link in new segments */
+
+ if (lseg)
+ lseg->next = tmp;
+ else
+ gradient->segments = tmp;
+
+ tmp->prev = lseg;
+
+ seg->next = rseg;
+
+ if (rseg)
+ rseg->prev = seg;
+
+ /* Reset selection */
+
+ if (final_start_seg)
+ *final_start_seg = tmp;
+
+ if (final_end_seg)
+ *final_end_seg = seg;
+
+ /* Done */
+
+ gimp_data_thaw (GIMP_DATA (gradient));
+}
+
+void
+gimp_gradient_segment_range_split_midpoint (GimpGradient *gradient,
+ GimpContext *context,
+ GimpGradientSegment *start_seg,
+ GimpGradientSegment *end_seg,
+ GimpGradientBlendColorSpace blend_color_space,
+ GimpGradientSegment **final_start_seg,
+ GimpGradientSegment **final_end_seg)
+{
+ GimpGradientSegment *seg, *lseg, *rseg;
+
+ g_return_if_fail (GIMP_IS_GRADIENT (gradient));
+ g_return_if_fail (GIMP_IS_CONTEXT (context));
+
+ gimp_data_freeze (GIMP_DATA (gradient));
+
+ if (! end_seg)
+ end_seg = gimp_gradient_segment_get_last (start_seg);
+
+ seg = start_seg;
+
+ do
+ {
+ gimp_gradient_segment_split_midpoint (gradient, context,
+ seg, blend_color_space,
+ &lseg, &rseg);
+ seg = rseg->next;
+ }
+ while (lseg != end_seg);
+
+ if (final_start_seg)
+ *final_start_seg = start_seg;
+
+ if (final_end_seg)
+ *final_end_seg = rseg;
+
+ gimp_data_thaw (GIMP_DATA (gradient));
+}
+
+void
+gimp_gradient_segment_range_split_uniform (GimpGradient *gradient,
+ GimpContext *context,
+ GimpGradientSegment *start_seg,
+ GimpGradientSegment *end_seg,
+ gint parts,
+ GimpGradientBlendColorSpace blend_color_space,
+ GimpGradientSegment **final_start_seg,
+ GimpGradientSegment **final_end_seg)
+{
+ GimpGradientSegment *seg, *aseg, *lseg, *rseg, *lsel;
+
+ g_return_if_fail (GIMP_IS_GRADIENT (gradient));
+ g_return_if_fail (GIMP_IS_CONTEXT (context));
+
+ if (! end_seg)
+ end_seg = gimp_gradient_segment_get_last (start_seg);
+
+ if (parts < 2)
+ {
+ *final_start_seg = start_seg;
+ *final_end_seg = end_seg;
+ return;
+ }
+
+ gimp_data_freeze (GIMP_DATA (gradient));
+
+ seg = start_seg;
+ lsel = NULL;
+
+ do
+ {
+ aseg = seg;
+
+ gimp_gradient_segment_split_uniform (gradient, context, seg,
+ parts, blend_color_space,
+ &lseg, &rseg);
+
+ if (seg == start_seg)
+ lsel = lseg;
+
+ seg = rseg->next;
+ }
+ while (aseg != end_seg);
+
+ if (final_start_seg)
+ *final_start_seg = lsel;
+
+ if (final_end_seg)
+ *final_end_seg = rseg;
+
+ gimp_data_thaw (GIMP_DATA (gradient));
+}
+
+void
+gimp_gradient_segment_range_delete (GimpGradient *gradient,
+ GimpGradientSegment *start_seg,
+ GimpGradientSegment *end_seg,
+ GimpGradientSegment **final_start_seg,
+ GimpGradientSegment **final_end_seg)
+{
+ GimpGradientSegment *lseg, *rseg, *seg, *aseg, *next;
+ gdouble join;
+
+ g_return_if_fail (GIMP_IS_GRADIENT (gradient));
+
+ if (! end_seg)
+ end_seg = gimp_gradient_segment_get_last (start_seg);
+
+ /* Remember segments to the left and to the right of the selection */
+
+ lseg = start_seg->prev;
+ rseg = end_seg->next;
+
+ /* Cannot delete all the segments in the gradient */
+
+ if ((lseg == NULL) && (rseg == NULL))
+ goto premature_return;
+
+ gimp_data_freeze (GIMP_DATA (gradient));
+
+ /* Calculate join point */
+
+ join = (start_seg->left +
+ end_seg->right) / 2.0;
+
+ if (lseg == NULL)
+ join = 0.0;
+ else if (rseg == NULL)
+ join = 1.0;
+
+ /* Move segments */
+
+ if (lseg != NULL)
+ gimp_gradient_segment_range_compress (gradient, lseg, lseg,
+ lseg->left, join);
+
+ if (rseg != NULL)
+ gimp_gradient_segment_range_compress (gradient, rseg, rseg,
+ join, rseg->right);
+
+ /* Link */
+
+ if (lseg)
+ lseg->next = rseg;
+
+ if (rseg)
+ rseg->prev = lseg;
+
+ /* Delete old segments */
+
+ seg = start_seg;
+
+ do
+ {
+ next = seg->next;
+ aseg = seg;
+
+ gimp_gradient_segment_free (seg);
+
+ seg = next;
+ }
+ while (aseg != end_seg);
+
+ /* Change selection */
+
+ if (rseg)
+ {
+ if (final_start_seg)
+ *final_start_seg = rseg;
+
+ if (final_end_seg)
+ *final_end_seg = rseg;
+ }
+ else
+ {
+ if (final_start_seg)
+ *final_start_seg = lseg;
+
+ if (final_end_seg)
+ *final_end_seg = lseg;
+ }
+
+ if (lseg == NULL)
+ gradient->segments = rseg;
+
+ gimp_data_thaw (GIMP_DATA (gradient));
+
+ return;
+
+ premature_return:
+ if (final_start_seg)
+ *final_start_seg = start_seg;
+ if (final_end_seg)
+ *final_end_seg = end_seg;
+}
+
+void
+gimp_gradient_segment_range_merge (GimpGradient *gradient,
+ GimpGradientSegment *start_seg,
+ GimpGradientSegment *end_seg,
+ GimpGradientSegment **final_start_seg,
+ GimpGradientSegment **final_end_seg)
+{
+ GimpGradientSegment *seg;
+
+ g_return_if_fail (GIMP_IS_GRADIENT (gradient));
+
+ if (! end_seg)
+ end_seg = gimp_gradient_segment_get_last (start_seg);
+
+ gimp_data_freeze (GIMP_DATA (gradient));
+
+ /* Copy the end segment's right position and color to the start segment */
+
+ start_seg->right = end_seg->right;
+ start_seg->right_color_type = end_seg->right_color_type;
+ start_seg->right_color = end_seg->right_color;
+
+ /* Center the start segment's midpoint */
+
+ start_seg->middle = (start_seg->left + start_seg->right) / 2.0;
+
+ /* Remove range segments past the start segment from the segment list */
+
+ start_seg->next = end_seg->next;
+
+ if (start_seg->next)
+ start_seg->next->prev = start_seg;
+
+ /* Merge the range's blend function and coloring type, and free the rest of
+ * the segments.
+ */
+
+ seg = end_seg;
+
+ while (seg != start_seg)
+ {
+ GimpGradientSegment *prev = seg->prev;
+
+ /* If the blend function and/or coloring type aren't uniform, reset them. */
+
+ if (seg->type != start_seg->type)
+ start_seg->type = GIMP_GRADIENT_SEGMENT_LINEAR;
+
+ if (seg->color != start_seg->color)
+ start_seg->color = GIMP_GRADIENT_SEGMENT_RGB;
+
+ gimp_gradient_segment_free (seg);
+
+ seg = prev;
+ }
+
+ if (final_start_seg)
+ *final_start_seg = start_seg;
+ if (final_end_seg)
+ *final_end_seg = start_seg;
+
+ gimp_data_thaw (GIMP_DATA (gradient));
+}
+
+void
+gimp_gradient_segment_range_recenter_handles (GimpGradient *gradient,
+ GimpGradientSegment *start_seg,
+ GimpGradientSegment *end_seg)
+{
+ GimpGradientSegment *seg, *aseg;
+
+ g_return_if_fail (GIMP_IS_GRADIENT (gradient));
+
+ gimp_data_freeze (GIMP_DATA (gradient));
+
+ if (! end_seg)
+ end_seg = gimp_gradient_segment_get_last (start_seg);
+
+ seg = start_seg;
+
+ do
+ {
+ seg->middle = (seg->left + seg->right) / 2.0;
+
+ aseg = seg;
+ seg = seg->next;
+ }
+ while (aseg != end_seg);
+
+ gimp_data_thaw (GIMP_DATA (gradient));
+}
+
+void
+gimp_gradient_segment_range_redistribute_handles (GimpGradient *gradient,
+ GimpGradientSegment *start_seg,
+ GimpGradientSegment *end_seg)
+{
+ GimpGradientSegment *seg, *aseg;
+ gdouble left, right, seg_len;
+ gint num_segs;
+ gint i;
+
+ g_return_if_fail (GIMP_IS_GRADIENT (gradient));
+
+ gimp_data_freeze (GIMP_DATA (gradient));
+
+ if (! end_seg)
+ end_seg = gimp_gradient_segment_get_last (start_seg);
+
+ /* Count number of segments in selection */
+
+ num_segs = 0;
+ seg = start_seg;
+
+ do
+ {
+ num_segs++;
+ aseg = seg;
+ seg = seg->next;
+ }
+ while (aseg != end_seg);
+
+ /* Calculate new segment length */
+
+ left = start_seg->left;
+ right = end_seg->right;
+ seg_len = (right - left) / num_segs;
+
+ /* Redistribute */
+
+ seg = start_seg;
+
+ for (i = 0; i < num_segs; i++)
+ {
+ seg->left = left + i * seg_len;
+ seg->right = left + (i + 1) * seg_len;
+ seg->middle = (seg->left + seg->right) / 2.0;
+
+ seg = seg->next;
+ }
+
+ /* Fix endpoints to squish accumulative error */
+
+ start_seg->left = left;
+ end_seg->right = right;
+
+ gimp_data_thaw (GIMP_DATA (gradient));
+}
+
+gdouble
+gimp_gradient_segment_range_move (GimpGradient *gradient,
+ GimpGradientSegment *range_l,
+ GimpGradientSegment *range_r,
+ gdouble delta,
+ gboolean control_compress)
+{
+ gdouble lbound, rbound;
+ gint is_first, is_last;
+ GimpGradientSegment *seg, *aseg;
+
+ g_return_val_if_fail (GIMP_IS_GRADIENT (gradient), 0.0);
+
+ gimp_data_freeze (GIMP_DATA (gradient));
+
+ if (! range_r)
+ range_r = gimp_gradient_segment_get_last (range_l);
+
+ /* First or last segments in gradient? */
+
+ is_first = (range_l->prev == NULL);
+ is_last = (range_r->next == NULL);
+
+ /* Calculate drag bounds */
+
+ if (! control_compress)
+ {
+ if (!is_first)
+ lbound = range_l->prev->middle + EPSILON;
+ else
+ lbound = range_l->left + EPSILON;
+
+ if (!is_last)
+ rbound = range_r->next->middle - EPSILON;
+ else
+ rbound = range_r->right - EPSILON;
+ }
+ else
+ {
+ if (!is_first)
+ lbound = range_l->prev->left + 2.0 * EPSILON;
+ else
+ lbound = range_l->left + EPSILON;
+
+ if (!is_last)
+ rbound = range_r->next->right - 2.0 * EPSILON;
+ else
+ rbound = range_r->right - EPSILON;
+ }
+
+ /* Fix the delta if necessary */
+
+ if (delta < 0.0)
+ {
+ if (!is_first)
+ {
+ if (range_l->left + delta < lbound)
+ delta = lbound - range_l->left;
+ }
+ else
+ if (range_l->middle + delta < lbound)
+ delta = lbound - range_l->middle;
+ }
+ else
+ {
+ if (!is_last)
+ {
+ if (range_r->right + delta > rbound)
+ delta = rbound - range_r->right;
+ }
+ else
+ if (range_r->middle + delta > rbound)
+ delta = rbound - range_r->middle;
+ }
+
+ /* Move all the segments inside the range */
+
+ seg = range_l;
+
+ do
+ {
+ if (!((seg == range_l) && is_first))
+ seg->left += delta;
+
+ seg->middle += delta;
+
+ if (!((seg == range_r) && is_last))
+ seg->right += delta;
+
+ /* Next */
+
+ aseg = seg;
+ seg = seg->next;
+ }
+ while (aseg != range_r);
+
+ /* Fix the segments that surround the range */
+
+ if (!is_first)
+ {
+ if (! control_compress)
+ range_l->prev->right = range_l->left;
+ else
+ gimp_gradient_segment_range_compress (gradient,
+ range_l->prev, range_l->prev,
+ range_l->prev->left, range_l->left);
+ }
+
+ if (!is_last)
+ {
+ if (! control_compress)
+ range_r->next->left = range_r->right;
+ else
+ gimp_gradient_segment_range_compress (gradient,
+ range_r->next, range_r->next,
+ range_r->right, range_r->next->right);
+ }
+
+ gimp_data_thaw (GIMP_DATA (gradient));
+
+ return delta;
+}
+
+
+/* private functions */
+
+static inline GimpGradientSegment *
+gimp_gradient_get_segment_at_internal (GimpGradient *gradient,
+ GimpGradientSegment *seg,
+ gdouble pos)
+{
+ /* handle FP imprecision at the edges of the gradient */
+ pos = CLAMP (pos, 0.0, 1.0);
+
+ if (! seg)
+ seg = gradient->segments;
+
+ if (pos >= seg->left)
+ {
+ while (seg->next && pos >= seg->right)
+ seg = seg->next;
+ }
+ else
+ {
+ do
+ seg = seg->prev;
+ while (pos < seg->left);
+ }
+
+ return seg;
+}
+
+static void
+gimp_gradient_get_flat_color (GimpContext *context,
+ const GimpRGB *color,
+ GimpGradientColor color_type,
+ GimpRGB *flat_color)
+{
+ switch (color_type)
+ {
+ case GIMP_GRADIENT_COLOR_FIXED:
+ *flat_color = *color;
+ break;
+
+ case GIMP_GRADIENT_COLOR_FOREGROUND:
+ case GIMP_GRADIENT_COLOR_FOREGROUND_TRANSPARENT:
+ gimp_context_get_foreground (context, flat_color);
+
+ if (color_type == GIMP_GRADIENT_COLOR_FOREGROUND_TRANSPARENT)
+ gimp_rgb_set_alpha (flat_color, 0.0);
+ break;
+
+ case GIMP_GRADIENT_COLOR_BACKGROUND:
+ case GIMP_GRADIENT_COLOR_BACKGROUND_TRANSPARENT:
+ gimp_context_get_background (context, flat_color);
+
+ if (color_type == GIMP_GRADIENT_COLOR_BACKGROUND_TRANSPARENT)
+ gimp_rgb_set_alpha (flat_color, 0.0);
+ break;
+ }
+}
+
+static inline gdouble
+gimp_gradient_calc_linear_factor (gdouble middle,
+ gdouble pos)
+{
+ if (pos <= middle)
+ {
+ if (middle < EPSILON)
+ return 0.0;
+ else
+ return 0.5 * pos / middle;
+ }
+ else
+ {
+ pos -= middle;
+ middle = 1.0 - middle;
+
+ if (middle < EPSILON)
+ return 1.0;
+ else
+ return 0.5 + 0.5 * pos / middle;
+ }
+}
+
+static inline gdouble
+gimp_gradient_calc_curved_factor (gdouble middle,
+ gdouble pos)
+{
+ if (middle < EPSILON)
+ return 1.0;
+ else if (1.0 - middle < EPSILON)
+ return 0.0;
+
+ return exp (-G_LN2 * log (pos) / log (middle));
+}
+
+static inline gdouble
+gimp_gradient_calc_sine_factor (gdouble middle,
+ gdouble pos)
+{
+ pos = gimp_gradient_calc_linear_factor (middle, pos);
+
+ return (sin ((-G_PI / 2.0) + G_PI * pos) + 1.0) / 2.0;
+}
+
+static inline gdouble
+gimp_gradient_calc_sphere_increasing_factor (gdouble middle,
+ gdouble pos)
+{
+ pos = gimp_gradient_calc_linear_factor (middle, pos) - 1.0;
+
+ /* Works for convex increasing and concave decreasing */
+ return sqrt (1.0 - pos * pos);
+}
+
+static inline gdouble
+gimp_gradient_calc_sphere_decreasing_factor (gdouble middle,
+ gdouble pos)
+{
+ pos = gimp_gradient_calc_linear_factor (middle, pos);
+
+ /* Works for convex decreasing and concave increasing */
+ return 1.0 - sqrt(1.0 - pos * pos);
+}
+
+static inline gdouble
+gimp_gradient_calc_step_factor (gdouble middle,
+ gdouble pos)
+{
+ return pos >= middle;
+}
diff --git a/app/core/gimpgradient.h b/app/core/gimpgradient.h
new file mode 100644
index 0000000..8aa817d
--- /dev/null
+++ b/app/core/gimpgradient.h
@@ -0,0 +1,296 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_GRADIENT_H__
+#define __GIMP_GRADIENT_H__
+
+
+#include "gimpdata.h"
+
+
+#define GIMP_GRADIENT_DEFAULT_SAMPLE_SIZE 40
+
+
+struct _GimpGradientSegment
+{
+ gdouble left, middle, right;
+
+ GimpGradientColor left_color_type;
+ GimpRGB left_color;
+ GimpGradientColor right_color_type;
+ GimpRGB right_color;
+
+ GimpGradientSegmentType type; /* Segment's blending function */
+ GimpGradientSegmentColor color; /* Segment's coloring type */
+
+ GimpGradientSegment *prev;
+ GimpGradientSegment *next;
+};
+
+
+#define GIMP_TYPE_GRADIENT (gimp_gradient_get_type ())
+#define GIMP_GRADIENT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_GRADIENT, GimpGradient))
+#define GIMP_GRADIENT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_GRADIENT, GimpGradientClass))
+#define GIMP_IS_GRADIENT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_GRADIENT))
+#define GIMP_IS_GRADIENT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_GRADIENT))
+#define GIMP_GRADIENT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_GRADIENT, GimpGradientClass))
+
+
+typedef struct _GimpGradientClass GimpGradientClass;
+
+struct _GimpGradient
+{
+ GimpData parent_instance;
+
+ GimpGradientSegment *segments;
+};
+
+struct _GimpGradientClass
+{
+ GimpDataClass parent_class;
+};
+
+
+GType gimp_gradient_get_type (void) G_GNUC_CONST;
+
+GimpData * gimp_gradient_new (GimpContext *context,
+ const gchar *name);
+GimpData * gimp_gradient_get_standard (GimpContext *context);
+
+GimpGradientSegment * gimp_gradient_get_color_at (GimpGradient *gradient,
+ GimpContext *context,
+ GimpGradientSegment *seg,
+ gdouble pos,
+ gboolean reverse,
+ GimpGradientBlendColorSpace blend_color_space,
+ GimpRGB *color);
+GimpGradientSegment * gimp_gradient_get_segment_at (GimpGradient *grad,
+ gdouble pos);
+void gimp_gradient_split_at (GimpGradient *gradient,
+ GimpContext *context,
+ GimpGradientSegment *seg,
+ gdouble pos,
+ GimpGradientBlendColorSpace blend_color_space,
+ GimpGradientSegment **newl,
+ GimpGradientSegment **newr);
+
+gboolean gimp_gradient_has_fg_bg_segments (GimpGradient *gradient);
+GimpGradient * gimp_gradient_flatten (GimpGradient *gradient,
+ GimpContext *context);
+
+
+/* gradient segment functions */
+
+GimpGradientSegment * gimp_gradient_segment_new (void);
+GimpGradientSegment * gimp_gradient_segment_get_last (GimpGradientSegment *seg);
+GimpGradientSegment * gimp_gradient_segment_get_first (GimpGradientSegment *seg);
+GimpGradientSegment * gimp_gradient_segment_get_nth (GimpGradientSegment *seg,
+ gint index);
+
+void gimp_gradient_segment_free (GimpGradientSegment *seg);
+void gimp_gradient_segments_free (GimpGradientSegment *seg);
+
+void gimp_gradient_segment_split_midpoint (GimpGradient *gradient,
+ GimpContext *context,
+ GimpGradientSegment *lseg,
+ GimpGradientBlendColorSpace blend_color_space,
+ GimpGradientSegment **newl,
+ GimpGradientSegment **newr);
+void gimp_gradient_segment_split_uniform (GimpGradient *gradient,
+ GimpContext *context,
+ GimpGradientSegment *lseg,
+ gint parts,
+ GimpGradientBlendColorSpace blend_color_space,
+ GimpGradientSegment **newl,
+ GimpGradientSegment **newr);
+
+/* Colors Setting/Getting Routines */
+void gimp_gradient_segment_get_left_color (GimpGradient *gradient,
+ GimpGradientSegment *seg,
+ GimpRGB *color);
+
+void gimp_gradient_segment_set_left_color (GimpGradient *gradient,
+ GimpGradientSegment *seg,
+ const GimpRGB *color);
+
+
+void gimp_gradient_segment_get_right_color (GimpGradient *gradient,
+ GimpGradientSegment *seg,
+ GimpRGB *color);
+
+void gimp_gradient_segment_set_right_color (GimpGradient *gradient,
+ GimpGradientSegment *seg,
+ const GimpRGB *color);
+
+
+GimpGradientColor
+gimp_gradient_segment_get_left_color_type (GimpGradient *gradient,
+ GimpGradientSegment *seg);
+
+void
+gimp_gradient_segment_set_left_color_type (GimpGradient *gradient,
+ GimpGradientSegment *seg,
+ GimpGradientColor color_type);
+
+
+GimpGradientColor
+gimp_gradient_segment_get_right_color_type (GimpGradient *gradient,
+ GimpGradientSegment *seg);
+
+void
+gimp_gradient_segment_set_right_color_type (GimpGradient *gradient,
+ GimpGradientSegment *seg,
+ GimpGradientColor color_type);
+
+
+void
+gimp_gradient_segment_get_left_flat_color (GimpGradient *gradient,
+ GimpContext *context,
+ GimpGradientSegment *seg,
+ GimpRGB *color);
+
+
+void
+gimp_gradient_segment_get_right_flat_color (GimpGradient *gradient,
+ GimpContext *context,
+ GimpGradientSegment *seg,
+ GimpRGB *color);
+
+
+/* Position Setting/Getting Routines */
+/* (Setters return the position after it was set) */
+gdouble gimp_gradient_segment_get_left_pos (GimpGradient *gradient,
+ GimpGradientSegment *seg);
+gdouble gimp_gradient_segment_set_left_pos (GimpGradient *gradient,
+ GimpGradientSegment *seg,
+ gdouble pos);
+
+gdouble gimp_gradient_segment_get_right_pos (GimpGradient *gradient,
+ GimpGradientSegment *seg);
+gdouble gimp_gradient_segment_set_right_pos (GimpGradient *gradient,
+ GimpGradientSegment *seg,
+ gdouble pos);
+
+gdouble gimp_gradient_segment_get_middle_pos (GimpGradient *gradient,
+ GimpGradientSegment *seg);
+gdouble gimp_gradient_segment_set_middle_pos (GimpGradient *gradient,
+ GimpGradientSegment *seg,
+ gdouble pos);
+
+/* Getting/Setting the Blending Function/Coloring Type */
+GimpGradientSegmentType
+gimp_gradient_segment_get_blending_function (GimpGradient *gradient,
+ GimpGradientSegment *seg);
+GimpGradientSegmentColor
+gimp_gradient_segment_get_coloring_type (GimpGradient *gradient,
+ GimpGradientSegment *seg);
+
+/*
+ * If the second segment is NULL, these functions will process
+ * until the end of the string.
+ * */
+gint gimp_gradient_segment_range_get_n_segments
+ (GimpGradient *gradient,
+ GimpGradientSegment *range_l,
+ GimpGradientSegment *range_r);
+
+void gimp_gradient_segment_range_compress (GimpGradient *gradient,
+ GimpGradientSegment *range_l,
+ GimpGradientSegment *range_r,
+ gdouble new_l,
+ gdouble new_r);
+void gimp_gradient_segment_range_blend (GimpGradient *gradient,
+ GimpGradientSegment *lseg,
+ GimpGradientSegment *rseg,
+ const GimpRGB *rgb1,
+ const GimpRGB *rgb2,
+ gboolean blend_colors,
+ gboolean blend_opacity);
+
+void gimp_gradient_segment_range_set_blending_function
+ (GimpGradient *gradient,
+ GimpGradientSegment *start_seg,
+ GimpGradientSegment *end_seg,
+ GimpGradientSegmentType new_type);
+
+void gimp_gradient_segment_range_set_coloring_type
+ (GimpGradient *gradient,
+ GimpGradientSegment *start_seg,
+ GimpGradientSegment *end_seg,
+ GimpGradientSegmentColor new_color);
+
+void gimp_gradient_segment_range_flip (GimpGradient *gradient,
+ GimpGradientSegment *start_seg,
+ GimpGradientSegment *end_seg,
+ GimpGradientSegment **final_start_seg,
+ GimpGradientSegment **final_end_seg);
+
+void gimp_gradient_segment_range_replicate (GimpGradient *gradient,
+ GimpGradientSegment *start_seg,
+ GimpGradientSegment *end_seg,
+ gint replicate_times,
+ GimpGradientSegment **final_start_seg,
+ GimpGradientSegment **final_end_seg);
+
+void gimp_gradient_segment_range_split_midpoint
+ (GimpGradient *gradient,
+ GimpContext *context,
+ GimpGradientSegment *start_seg,
+ GimpGradientSegment *end_seg,
+ GimpGradientBlendColorSpace blend_color_space,
+ GimpGradientSegment **final_start_seg,
+ GimpGradientSegment **final_end_seg);
+
+void gimp_gradient_segment_range_split_uniform
+ (GimpGradient *gradient,
+ GimpContext *context,
+ GimpGradientSegment *start_seg,
+ GimpGradientSegment *end_seg,
+ gint parts,
+ GimpGradientBlendColorSpace blend_color_space,
+ GimpGradientSegment **final_start_seg,
+ GimpGradientSegment **final_end_seg);
+
+void gimp_gradient_segment_range_delete (GimpGradient *gradient,
+ GimpGradientSegment *start_seg,
+ GimpGradientSegment *end_seg,
+ GimpGradientSegment **final_start_seg,
+ GimpGradientSegment **final_end_seg);
+
+void gimp_gradient_segment_range_merge (GimpGradient *gradient,
+ GimpGradientSegment *start_seg,
+ GimpGradientSegment *end_seg,
+ GimpGradientSegment **final_start_seg,
+ GimpGradientSegment **final_end_seg);
+
+void gimp_gradient_segment_range_recenter_handles
+ (GimpGradient *gradient,
+ GimpGradientSegment *start_seg,
+ GimpGradientSegment *end_seg);
+void gimp_gradient_segment_range_redistribute_handles
+ (GimpGradient *gradient,
+ GimpGradientSegment *start_seg,
+ GimpGradientSegment *end_seg);
+
+gdouble gimp_gradient_segment_range_move (GimpGradient *gradient,
+ GimpGradientSegment *range_l,
+ GimpGradientSegment *range_r,
+ gdouble delta,
+ gboolean control_compress);
+
+
+#endif /* __GIMP_GRADIENT_H__ */
diff --git a/app/core/gimpgrid.c b/app/core/gimpgrid.c
new file mode 100644
index 0000000..d500113
--- /dev/null
+++ b/app/core/gimpgrid.c
@@ -0,0 +1,359 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * GimpGrid
+ * Copyright (C) 2003 Henrik Brix Andersen <brix@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <cairo.h>
+#include <gegl.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+#include "libgimpmath/gimpmath.h"
+#include "libgimpbase/gimpbase.h"
+#include "libgimpconfig/gimpconfig.h"
+
+#include "libgimpcolor/gimpcolor.h"
+
+#include "core-types.h"
+
+#include "gimpgrid.h"
+
+#include "gimp-intl.h"
+
+
+enum
+{
+ PROP_0,
+ PROP_STYLE,
+ PROP_FGCOLOR,
+ PROP_BGCOLOR,
+ PROP_XSPACING,
+ PROP_YSPACING,
+ PROP_SPACING_UNIT,
+ PROP_XOFFSET,
+ PROP_YOFFSET,
+ PROP_OFFSET_UNIT
+};
+
+
+static void gimp_grid_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+static void gimp_grid_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+
+
+G_DEFINE_TYPE_WITH_CODE (GimpGrid, gimp_grid, GIMP_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (GIMP_TYPE_CONFIG, NULL))
+
+
+static void
+gimp_grid_class_init (GimpGridClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpRGB black;
+ GimpRGB white;
+
+ object_class->get_property = gimp_grid_get_property;
+ object_class->set_property = gimp_grid_set_property;
+
+ gimp_rgba_set (&black, 0.0, 0.0, 0.0, GIMP_OPACITY_OPAQUE);
+ gimp_rgba_set (&white, 1.0, 1.0, 1.0, GIMP_OPACITY_OPAQUE);
+
+ GIMP_CONFIG_PROP_ENUM (object_class, PROP_STYLE,
+ "style",
+ _("Line style"),
+ _("Line style used for the grid."),
+ GIMP_TYPE_GRID_STYLE,
+ GIMP_GRID_SOLID,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_RGB (object_class, PROP_FGCOLOR,
+ "fgcolor",
+ _("Foreground color"),
+ _("The foreground color of the grid."),
+ TRUE, &black,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_RGB (object_class, PROP_BGCOLOR,
+ "bgcolor",
+ _("Background color"),
+ _("The background color of the grid; "
+ "only used in double dashed line style."),
+ TRUE, &white,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_DOUBLE (object_class, PROP_XSPACING,
+ "xspacing",
+ _("Spacing X"),
+ _("Horizontal spacing of grid lines."),
+ 0.0, GIMP_MAX_IMAGE_SIZE, 10.0,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_DOUBLE (object_class, PROP_YSPACING,
+ "yspacing",
+ _("Spacing Y"),
+ _("Vertical spacing of grid lines."),
+ 0.0, GIMP_MAX_IMAGE_SIZE, 10.0,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_UNIT (object_class, PROP_SPACING_UNIT,
+ "spacing-unit",
+ _("Spacing unit"),
+ NULL,
+ FALSE, FALSE, GIMP_UNIT_INCH,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_DOUBLE (object_class, PROP_XOFFSET,
+ "xoffset",
+ _("Offset X"),
+ _("Horizontal offset of the first grid "
+ "line; this may be a negative number."),
+ - GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE, 0.0,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_DOUBLE (object_class, PROP_YOFFSET,
+ "yoffset",
+ _("Offset Y"),
+ _("Vertical offset of the first grid "
+ "line; this may be a negative number."),
+ - GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE, 0.0,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_UNIT (object_class, PROP_OFFSET_UNIT,
+ "offset-unit",
+ _("Offset unit"),
+ NULL,
+ FALSE, FALSE, GIMP_UNIT_INCH,
+ GIMP_PARAM_STATIC_STRINGS);
+}
+
+static void
+gimp_grid_init (GimpGrid *grid)
+{
+}
+
+static void
+gimp_grid_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpGrid *grid = GIMP_GRID (object);
+
+ switch (property_id)
+ {
+ case PROP_STYLE:
+ g_value_set_enum (value, grid->style);
+ break;
+ case PROP_FGCOLOR:
+ g_value_set_boxed (value, &grid->fgcolor);
+ break;
+ case PROP_BGCOLOR:
+ g_value_set_boxed (value, &grid->bgcolor);
+ break;
+ case PROP_XSPACING:
+ g_value_set_double (value, grid->xspacing);
+ break;
+ case PROP_YSPACING:
+ g_value_set_double (value, grid->yspacing);
+ break;
+ case PROP_SPACING_UNIT:
+ g_value_set_int (value, grid->spacing_unit);
+ break;
+ case PROP_XOFFSET:
+ g_value_set_double (value, grid->xoffset);
+ break;
+ case PROP_YOFFSET:
+ g_value_set_double (value, grid->yoffset);
+ break;
+ case PROP_OFFSET_UNIT:
+ g_value_set_int (value, grid->offset_unit);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_grid_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpGrid *grid = GIMP_GRID (object);
+ GimpRGB *color;
+
+ switch (property_id)
+ {
+ case PROP_STYLE:
+ grid->style = g_value_get_enum (value);
+ break;
+ case PROP_FGCOLOR:
+ color = g_value_get_boxed (value);
+ grid->fgcolor = *color;
+ break;
+ case PROP_BGCOLOR:
+ color = g_value_get_boxed (value);
+ grid->bgcolor = *color;
+ break;
+ case PROP_XSPACING:
+ grid->xspacing = g_value_get_double (value);
+ break;
+ case PROP_YSPACING:
+ grid->yspacing = g_value_get_double (value);
+ break;
+ case PROP_SPACING_UNIT:
+ grid->spacing_unit = g_value_get_int (value);
+ break;
+ case PROP_XOFFSET:
+ grid->xoffset = g_value_get_double (value);
+ break;
+ case PROP_YOFFSET:
+ grid->yoffset = g_value_get_double (value);
+ break;
+ case PROP_OFFSET_UNIT:
+ grid->offset_unit = g_value_get_int (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+
+/* public functions */
+
+GimpGridStyle
+gimp_grid_get_style (GimpGrid *grid)
+{
+ g_return_val_if_fail (GIMP_IS_GRID (grid), GIMP_GRID_SOLID);
+
+ return grid->style;
+}
+
+void
+gimp_grid_get_fgcolor (GimpGrid *grid,
+ GimpRGB *fgcolor)
+{
+ g_return_if_fail (GIMP_IS_GRID (grid));
+ g_return_if_fail (fgcolor != NULL);
+
+ *fgcolor = grid->fgcolor;
+}
+
+void
+gimp_grid_get_bgcolor (GimpGrid *grid,
+ GimpRGB *bgcolor)
+{
+ g_return_if_fail (GIMP_IS_GRID (grid));
+ g_return_if_fail (bgcolor != NULL);
+
+ *bgcolor = grid->bgcolor;
+}
+
+void
+gimp_grid_get_spacing (GimpGrid *grid,
+ gdouble *xspacing,
+ gdouble *yspacing)
+{
+ g_return_if_fail (GIMP_IS_GRID (grid));
+
+ if (xspacing) *xspacing = grid->xspacing;
+ if (yspacing) *yspacing = grid->yspacing;
+}
+
+void
+gimp_grid_get_offset (GimpGrid *grid,
+ gdouble *xoffset,
+ gdouble *yoffset)
+{
+ g_return_if_fail (GIMP_IS_GRID (grid));
+
+ if (xoffset) *xoffset = grid->xoffset;
+ if (yoffset) *yoffset = grid->yoffset;
+}
+
+const gchar *
+gimp_grid_parasite_name (void)
+{
+ return "gimp-image-grid";
+}
+
+GimpParasite *
+gimp_grid_to_parasite (GimpGrid *grid)
+{
+ GimpParasite *parasite;
+ gchar *str;
+
+ g_return_val_if_fail (GIMP_IS_GRID (grid), NULL);
+
+ str = gimp_config_serialize_to_string (GIMP_CONFIG (grid), NULL);
+ g_return_val_if_fail (str != NULL, NULL);
+
+ parasite = gimp_parasite_new (gimp_grid_parasite_name (),
+ GIMP_PARASITE_PERSISTENT,
+ strlen (str) + 1, str);
+ g_free (str);
+
+ return parasite;
+}
+
+GimpGrid *
+gimp_grid_from_parasite (const GimpParasite *parasite)
+{
+ GimpGrid *grid;
+ const gchar *str;
+ GError *error = NULL;
+
+ g_return_val_if_fail (parasite != NULL, NULL);
+ g_return_val_if_fail (strcmp (gimp_parasite_name (parasite),
+ gimp_grid_parasite_name ()) == 0, NULL);
+
+ str = gimp_parasite_data (parasite);
+
+ if (! str)
+ {
+ g_warning ("Empty grid parasite");
+
+ return NULL;
+ }
+
+ grid = g_object_new (GIMP_TYPE_GRID, NULL);
+
+ if (! gimp_config_deserialize_string (GIMP_CONFIG (grid),
+ str,
+ gimp_parasite_data_size (parasite),
+ NULL,
+ &error))
+ {
+ g_warning ("Failed to deserialize grid parasite: %s", error->message);
+ g_error_free (error);
+ }
+
+ return grid;
+}
diff --git a/app/core/gimpgrid.h b/app/core/gimpgrid.h
new file mode 100644
index 0000000..b7d45b2
--- /dev/null
+++ b/app/core/gimpgrid.h
@@ -0,0 +1,81 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * GimpGrid
+ * Copyright (C) 2003 Henrik Brix Andersen <brix@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_GRID_H__
+#define __GIMP_GRID_H__
+
+
+#include "gimpobject.h"
+
+
+#define GIMP_TYPE_GRID (gimp_grid_get_type ())
+#define GIMP_GRID(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_GRID, GimpGrid))
+#define GIMP_GRID_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_GRID, GimpGridClass))
+#define GIMP_IS_GRID(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_GRID))
+#define GIMP_IS_GRID_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_GRID))
+#define GIMP_GRID_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_GRID, GimpGridClass))
+
+
+typedef struct _GimpGridClass GimpGridClass;
+
+struct _GimpGrid
+{
+ GimpObject parent_instance;
+
+ GimpGridStyle style;
+ GimpRGB fgcolor;
+ GimpRGB bgcolor;
+ gdouble xspacing;
+ gdouble yspacing;
+ GimpUnit spacing_unit;
+ gdouble xoffset;
+ gdouble yoffset;
+ GimpUnit offset_unit;
+};
+
+
+struct _GimpGridClass
+{
+ GimpObjectClass parent_class;
+};
+
+
+GType gimp_grid_get_type (void) G_GNUC_CONST;
+
+GimpGridStyle gimp_grid_get_style (GimpGrid *grid);
+
+void gimp_grid_get_fgcolor (GimpGrid *grid,
+ GimpRGB *fgcolor);
+void gimp_grid_get_bgcolor (GimpGrid *grid,
+ GimpRGB *bgcolor);
+
+void gimp_grid_get_spacing (GimpGrid *grid,
+ gdouble *xspacing,
+ gdouble *yspacing);
+void gimp_grid_get_offset (GimpGrid *grid,
+ gdouble *xoffset,
+ gdouble *yoffset);
+
+const gchar * gimp_grid_parasite_name (void) G_GNUC_CONST;
+GimpParasite * gimp_grid_to_parasite (GimpGrid *grid);
+GimpGrid * gimp_grid_from_parasite (const GimpParasite *parasite);
+
+
+#endif /* __GIMP_GRID_H__ */
diff --git a/app/core/gimpgrouplayer.c b/app/core/gimpgrouplayer.c
new file mode 100644
index 0000000..b0de646
--- /dev/null
+++ b/app/core/gimpgrouplayer.c
@@ -0,0 +1,2297 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * GimpGroupLayer
+ * Copyright (C) 2009 Michael Natterer <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpbase/gimpbase.h"
+#include "libgimpmath/gimpmath.h"
+
+#include "core-types.h"
+
+#include "gegl/gimp-babl.h"
+#include "gegl/gimp-gegl-loops.h"
+
+#include "gimpdrawable-filters.h"
+#include "gimpgrouplayer.h"
+#include "gimpgrouplayerundo.h"
+#include "gimpimage.h"
+#include "gimpimage-undo.h"
+#include "gimpimage-undo-push.h"
+#include "gimplayerstack.h"
+#include "gimpobjectqueue.h"
+#include "gimppickable.h"
+#include "gimpprogress.h"
+#include "gimpprojectable.h"
+#include "gimpprojection.h"
+
+#include "gimp-intl.h"
+
+
+typedef struct _GimpGroupLayerPrivate GimpGroupLayerPrivate;
+
+struct _GimpGroupLayerPrivate
+{
+ GimpContainer *children;
+ GimpProjection *projection;
+ GeglNode *source_node;
+ GeglNode *parent_source_node;
+ GeglNode *graph;
+ GeglNode *offset_node;
+ GeglRectangle bounding_box;
+ gint suspend_resize;
+ gint suspend_mask;
+ GeglBuffer *suspended_mask_buffer;
+ GeglRectangle suspended_mask_bounds;
+ gint direct_update;
+ gint transforming;
+ gboolean expanded;
+ gboolean pass_through;
+
+ /* hackish temp states to make the projection/tiles stuff work */
+ const Babl *convert_format;
+ gboolean reallocate_projection;
+};
+
+#define GET_PRIVATE(item) ((GimpGroupLayerPrivate *) gimp_group_layer_get_instance_private ((GimpGroupLayer *) (item)))
+
+
+static void gimp_projectable_iface_init (GimpProjectableInterface *iface);
+static void gimp_pickable_iface_init (GimpPickableInterface *iface);
+
+static void gimp_group_layer_finalize (GObject *object);
+static void gimp_group_layer_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_group_layer_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static gint64 gimp_group_layer_get_memsize (GimpObject *object,
+ gint64 *gui_size);
+
+static void gimp_group_layer_ancestry_changed (GimpViewable *viewable);
+static gboolean gimp_group_layer_get_size (GimpViewable *viewable,
+ gint *width,
+ gint *height);
+static GimpContainer * gimp_group_layer_get_children (GimpViewable *viewable);
+static gboolean gimp_group_layer_get_expanded (GimpViewable *viewable);
+static void gimp_group_layer_set_expanded (GimpViewable *viewable,
+ gboolean expanded);
+
+static gboolean gimp_group_layer_is_position_locked (GimpItem *item);
+static GimpItem * gimp_group_layer_duplicate (GimpItem *item,
+ GType new_type);
+static void gimp_group_layer_convert (GimpItem *item,
+ GimpImage *dest_image,
+ GType old_type);
+static void gimp_group_layer_start_transform (GimpItem *item,
+ gboolean push_undo);
+static void gimp_group_layer_end_transform (GimpItem *item,
+ gboolean push_undo);
+static void gimp_group_layer_resize (GimpItem *item,
+ GimpContext *context,
+ GimpFillType fill_type,
+ gint new_width,
+ gint new_height,
+ gint offset_x,
+ gint offset_y);
+static GimpTransformResize
+ gimp_group_layer_get_clip (GimpItem *item,
+ GimpTransformResize clip_result);
+
+static gint64 gimp_group_layer_estimate_memsize (GimpDrawable *drawable,
+ GimpComponentType component_type,
+ gint width,
+ gint height);
+static void gimp_group_layer_update_all (GimpDrawable *drawable);
+
+static void gimp_group_layer_translate (GimpLayer *layer,
+ gint offset_x,
+ gint offset_y);
+static void gimp_group_layer_scale (GimpLayer *layer,
+ gint new_width,
+ gint new_height,
+ gint new_offset_x,
+ gint new_offset_y,
+ GimpInterpolationType interp_type,
+ GimpProgress *progress);
+static void gimp_group_layer_flip (GimpLayer *layer,
+ GimpContext *context,
+ GimpOrientationType flip_type,
+ gdouble axis,
+ gboolean clip_result);
+static void gimp_group_layer_rotate (GimpLayer *layer,
+ GimpContext *context,
+ GimpRotationType rotate_type,
+ gdouble center_x,
+ gdouble center_y,
+ gboolean clip_result);
+static void gimp_group_layer_transform (GimpLayer *layer,
+ GimpContext *context,
+ const GimpMatrix3 *matrix,
+ GimpTransformDirection direction,
+ GimpInterpolationType interpolation_type,
+ GimpTransformResize clip_result,
+ GimpProgress *progress);
+static void gimp_group_layer_convert_type (GimpLayer *layer,
+ GimpImage *dest_image,
+ const Babl *new_format,
+ GimpColorProfile *dest_profile,
+ GeglDitherMethod layer_dither_type,
+ GeglDitherMethod mask_dither_type,
+ gboolean push_undo,
+ GimpProgress *progress);
+static GeglNode * gimp_group_layer_get_source_node (GimpDrawable *drawable);
+
+static void gimp_group_layer_opacity_changed (GimpLayer *layer);
+static void gimp_group_layer_effective_mode_changed (GimpLayer *layer);
+static void
+ gimp_group_layer_excludes_backdrop_changed (GimpLayer *layer);
+static GeglRectangle
+ gimp_group_layer_get_bounding_box (GimpLayer *layer);
+static void gimp_group_layer_get_effective_mode (GimpLayer *layer,
+ GimpLayerMode *mode,
+ GimpLayerColorSpace *blend_space,
+ GimpLayerColorSpace *composite_space,
+ GimpLayerCompositeMode *composite_mode);
+static gboolean
+ gimp_group_layer_get_excludes_backdrop (GimpLayer *layer);
+
+static const Babl * gimp_group_layer_get_format (GimpProjectable *projectable);
+static GeglRectangle
+ gimp_group_layer_projectable_get_bounding_box (GimpProjectable *projectable);
+static GeglNode * gimp_group_layer_get_graph (GimpProjectable *projectable);
+static void gimp_group_layer_begin_render (GimpProjectable *projectable);
+static void gimp_group_layer_end_render (GimpProjectable *projectable);
+
+static void gimp_group_layer_pickable_flush (GimpPickable *pickable);
+static gdouble gimp_group_layer_get_opacity_at (GimpPickable *pickable,
+ gint x,
+ gint y);
+
+
+static void gimp_group_layer_child_add (GimpContainer *container,
+ GimpLayer *child,
+ GimpGroupLayer *group);
+static void gimp_group_layer_child_remove (GimpContainer *container,
+ GimpLayer *child,
+ GimpGroupLayer *group);
+static void gimp_group_layer_child_move (GimpLayer *child,
+ GParamSpec *pspec,
+ GimpGroupLayer *group);
+static void gimp_group_layer_child_resize (GimpLayer *child,
+ GimpGroupLayer *group);
+static void gimp_group_layer_child_active_changed (GimpLayer *child,
+ GimpGroupLayer *group);
+static void
+ gimp_group_layer_child_effective_mode_changed (GimpLayer *child,
+ GimpGroupLayer *group);
+static void
+ gimp_group_layer_child_excludes_backdrop_changed (GimpLayer *child,
+ GimpGroupLayer *group);
+
+static void gimp_group_layer_flush (GimpGroupLayer *group);
+static void gimp_group_layer_update (GimpGroupLayer *group);
+static void gimp_group_layer_update_size (GimpGroupLayer *group);
+static void gimp_group_layer_update_mask_size (GimpGroupLayer *group);
+static void gimp_group_layer_update_source_node (GimpGroupLayer *group);
+static void gimp_group_layer_update_mode_node (GimpGroupLayer *group);
+
+static void gimp_group_layer_stack_update (GimpDrawableStack *stack,
+ gint x,
+ gint y,
+ gint width,
+ gint height,
+ GimpGroupLayer *group);
+static void gimp_group_layer_proj_update (GimpProjection *proj,
+ gboolean now,
+ gint x,
+ gint y,
+ gint width,
+ gint height,
+ GimpGroupLayer *group);
+
+
+G_DEFINE_TYPE_WITH_CODE (GimpGroupLayer, gimp_group_layer, GIMP_TYPE_LAYER,
+ G_ADD_PRIVATE (GimpGroupLayer)
+ G_IMPLEMENT_INTERFACE (GIMP_TYPE_PROJECTABLE,
+ gimp_projectable_iface_init)
+ G_IMPLEMENT_INTERFACE (GIMP_TYPE_PICKABLE,
+ gimp_pickable_iface_init))
+
+
+#define parent_class gimp_group_layer_parent_class
+
+
+/* disable pass-through groups strength-reduction to normal groups.
+ * see gimp_group_layer_get_effective_mode().
+ */
+static gboolean no_pass_through_strength_reduction = FALSE;
+
+
+static void
+gimp_group_layer_class_init (GimpGroupLayerClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpObjectClass *gimp_object_class = GIMP_OBJECT_CLASS (klass);
+ GimpViewableClass *viewable_class = GIMP_VIEWABLE_CLASS (klass);
+ GimpItemClass *item_class = GIMP_ITEM_CLASS (klass);
+ GimpDrawableClass *drawable_class = GIMP_DRAWABLE_CLASS (klass);
+ GimpLayerClass *layer_class = GIMP_LAYER_CLASS (klass);
+
+ object_class->set_property = gimp_group_layer_set_property;
+ object_class->get_property = gimp_group_layer_get_property;
+ object_class->finalize = gimp_group_layer_finalize;
+
+ gimp_object_class->get_memsize = gimp_group_layer_get_memsize;
+
+ viewable_class->default_icon_name = "gimp-group-layer";
+ viewable_class->ancestry_changed = gimp_group_layer_ancestry_changed;
+ viewable_class->get_size = gimp_group_layer_get_size;
+ viewable_class->get_children = gimp_group_layer_get_children;
+ viewable_class->set_expanded = gimp_group_layer_set_expanded;
+ viewable_class->get_expanded = gimp_group_layer_get_expanded;
+
+ item_class->is_position_locked = gimp_group_layer_is_position_locked;
+ item_class->duplicate = gimp_group_layer_duplicate;
+ item_class->convert = gimp_group_layer_convert;
+ item_class->start_transform = gimp_group_layer_start_transform;
+ item_class->end_transform = gimp_group_layer_end_transform;
+ item_class->resize = gimp_group_layer_resize;
+ item_class->get_clip = gimp_group_layer_get_clip;
+
+ item_class->default_name = _("Layer Group");
+ item_class->rename_desc = C_("undo-type", "Rename Layer Group");
+ item_class->translate_desc = C_("undo-type", "Move Layer Group");
+ item_class->scale_desc = C_("undo-type", "Scale Layer Group");
+ item_class->resize_desc = C_("undo-type", "Resize Layer Group");
+ item_class->flip_desc = C_("undo-type", "Flip Layer Group");
+ item_class->rotate_desc = C_("undo-type", "Rotate Layer Group");
+ item_class->transform_desc = C_("undo-type", "Transform Layer Group");
+
+ drawable_class->estimate_memsize = gimp_group_layer_estimate_memsize;
+ drawable_class->update_all = gimp_group_layer_update_all;
+ drawable_class->get_source_node = gimp_group_layer_get_source_node;
+
+ layer_class->opacity_changed = gimp_group_layer_opacity_changed;
+ layer_class->effective_mode_changed = gimp_group_layer_effective_mode_changed;
+ layer_class->excludes_backdrop_changed = gimp_group_layer_excludes_backdrop_changed;
+ layer_class->translate = gimp_group_layer_translate;
+ layer_class->scale = gimp_group_layer_scale;
+ layer_class->flip = gimp_group_layer_flip;
+ layer_class->rotate = gimp_group_layer_rotate;
+ layer_class->transform = gimp_group_layer_transform;
+ layer_class->convert_type = gimp_group_layer_convert_type;
+ layer_class->get_bounding_box = gimp_group_layer_get_bounding_box;
+ layer_class->get_effective_mode = gimp_group_layer_get_effective_mode;
+ layer_class->get_excludes_backdrop = gimp_group_layer_get_excludes_backdrop;
+
+ if (g_getenv ("GIMP_NO_PASS_THROUGH_STRENGTH_REDUCTION"))
+ no_pass_through_strength_reduction = TRUE;
+}
+
+static void
+gimp_projectable_iface_init (GimpProjectableInterface *iface)
+{
+ iface->get_image = (GimpImage * (*) (GimpProjectable *)) gimp_item_get_image;
+ iface->get_format = gimp_group_layer_get_format;
+ iface->get_offset = (void (*) (GimpProjectable*, gint*, gint*)) gimp_item_get_offset;
+ iface->get_bounding_box = gimp_group_layer_projectable_get_bounding_box;
+ iface->get_graph = gimp_group_layer_get_graph;
+ iface->begin_render = gimp_group_layer_begin_render;
+ iface->end_render = gimp_group_layer_end_render;
+ iface->invalidate_preview = (void (*) (GimpProjectable*)) gimp_viewable_invalidate_preview;
+}
+
+static void
+gimp_pickable_iface_init (GimpPickableInterface *iface)
+{
+ iface->flush = gimp_group_layer_pickable_flush;
+ iface->get_opacity_at = gimp_group_layer_get_opacity_at;
+}
+
+static void
+gimp_group_layer_init (GimpGroupLayer *group)
+{
+ GimpGroupLayerPrivate *private = GET_PRIVATE (group);
+
+ private->children = gimp_layer_stack_new (GIMP_TYPE_LAYER);
+ private->expanded = TRUE;
+
+ g_signal_connect (private->children, "add",
+ G_CALLBACK (gimp_group_layer_child_add),
+ group);
+ g_signal_connect (private->children, "remove",
+ G_CALLBACK (gimp_group_layer_child_remove),
+ group);
+
+ gimp_container_add_handler (private->children, "notify::offset-x",
+ G_CALLBACK (gimp_group_layer_child_move),
+ group);
+ gimp_container_add_handler (private->children, "notify::offset-y",
+ G_CALLBACK (gimp_group_layer_child_move),
+ group);
+ gimp_container_add_handler (private->children, "size-changed",
+ G_CALLBACK (gimp_group_layer_child_resize),
+ group);
+ gimp_container_add_handler (private->children, "bounding-box-changed",
+ G_CALLBACK (gimp_group_layer_child_resize),
+ group);
+ gimp_container_add_handler (private->children, "active-changed",
+ G_CALLBACK (gimp_group_layer_child_active_changed),
+ group);
+ gimp_container_add_handler (private->children, "effective-mode-changed",
+ G_CALLBACK (gimp_group_layer_child_effective_mode_changed),
+ group);
+ gimp_container_add_handler (private->children, "excludes-backdrop-changed",
+ G_CALLBACK (gimp_group_layer_child_excludes_backdrop_changed),
+ group);
+
+ g_signal_connect (private->children, "update",
+ G_CALLBACK (gimp_group_layer_stack_update),
+ group);
+
+ private->projection = gimp_projection_new (GIMP_PROJECTABLE (group));
+ gimp_projection_set_priority (private->projection, 1);
+
+ g_signal_connect (private->projection, "update",
+ G_CALLBACK (gimp_group_layer_proj_update),
+ group);
+}
+
+static void
+gimp_group_layer_finalize (GObject *object)
+{
+ GimpGroupLayerPrivate *private = GET_PRIVATE (object);
+
+ if (private->children)
+ {
+ g_signal_handlers_disconnect_by_func (private->children,
+ gimp_group_layer_child_add,
+ object);
+ g_signal_handlers_disconnect_by_func (private->children,
+ gimp_group_layer_child_remove,
+ object);
+ g_signal_handlers_disconnect_by_func (private->children,
+ gimp_group_layer_stack_update,
+ object);
+
+ /* this is particularly important to avoid reallocating the projection
+ * in response to a "bounding-box-changed" signal, which can be emitted
+ * during layer destruction. see issue #4584.
+ */
+ gimp_container_remove_handlers_by_data (private->children, object);
+
+ g_clear_object (&private->children);
+ }
+
+ g_clear_object (&private->projection);
+ g_clear_object (&private->source_node);
+ g_clear_object (&private->graph);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gimp_group_layer_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id)
+ {
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_group_layer_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id)
+ {
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static gint64
+gimp_group_layer_get_memsize (GimpObject *object,
+ gint64 *gui_size)
+{
+ GimpGroupLayerPrivate *private = GET_PRIVATE (object);
+ gint64 memsize = 0;
+
+ memsize += gimp_object_get_memsize (GIMP_OBJECT (private->children), gui_size);
+ memsize += gimp_object_get_memsize (GIMP_OBJECT (private->projection), gui_size);
+
+ return memsize + GIMP_OBJECT_CLASS (parent_class)->get_memsize (object,
+ gui_size);
+}
+
+static void
+gimp_group_layer_ancestry_changed (GimpViewable *viewable)
+{
+ GimpGroupLayerPrivate *private = GET_PRIVATE (viewable);
+
+ gimp_projection_set_priority (private->projection,
+ gimp_viewable_get_depth (viewable) + 1);
+
+ GIMP_VIEWABLE_CLASS (parent_class)->ancestry_changed (viewable);
+}
+
+static gboolean
+gimp_group_layer_get_size (GimpViewable *viewable,
+ gint *width,
+ gint *height)
+{
+ GimpGroupLayerPrivate *private = GET_PRIVATE (viewable);
+
+ /* return the size only if there are children ... */
+ if (! gimp_container_is_empty (private->children))
+ {
+ return GIMP_VIEWABLE_CLASS (parent_class)->get_size (viewable,
+ width, height);
+ }
+
+ /* ... otherwise, return "no content" */
+ return FALSE;
+}
+
+static GimpContainer *
+gimp_group_layer_get_children (GimpViewable *viewable)
+{
+ return GET_PRIVATE (viewable)->children;
+}
+
+static gboolean
+gimp_group_layer_get_expanded (GimpViewable *viewable)
+{
+ GimpGroupLayer *group = GIMP_GROUP_LAYER (viewable);
+
+ return GET_PRIVATE (group)->expanded;
+}
+
+static void
+gimp_group_layer_set_expanded (GimpViewable *viewable,
+ gboolean expanded)
+{
+ GimpGroupLayerPrivate *private = GET_PRIVATE (viewable);
+
+ if (private->expanded != expanded)
+ {
+ private->expanded = expanded;
+
+ gimp_viewable_expanded_changed (viewable);
+ }
+}
+
+static gboolean
+gimp_group_layer_is_position_locked (GimpItem *item)
+{
+ GimpGroupLayerPrivate *private = GET_PRIVATE (item);
+ GList *list;
+
+ for (list = gimp_item_stack_get_item_iter (GIMP_ITEM_STACK (private->children));
+ list;
+ list = g_list_next (list))
+ {
+ GimpItem *child = list->data;
+
+ if (gimp_item_is_position_locked (child))
+ return TRUE;
+ }
+
+ return GIMP_ITEM_CLASS (parent_class)->is_position_locked (item);
+}
+
+static GimpItem *
+gimp_group_layer_duplicate (GimpItem *item,
+ GType new_type)
+{
+ GimpItem *new_item;
+
+ g_return_val_if_fail (g_type_is_a (new_type, GIMP_TYPE_DRAWABLE), NULL);
+
+ new_item = GIMP_ITEM_CLASS (parent_class)->duplicate (item, new_type);
+
+ if (GIMP_IS_GROUP_LAYER (new_item))
+ {
+ GimpGroupLayerPrivate *private = GET_PRIVATE (item);
+ GimpGroupLayer *new_group = GIMP_GROUP_LAYER (new_item);
+ GimpGroupLayerPrivate *new_private = GET_PRIVATE (new_item);
+ gint position = 0;
+ GList *list;
+
+ gimp_group_layer_suspend_resize (new_group, FALSE);
+
+ for (list = gimp_item_stack_get_item_iter (GIMP_ITEM_STACK (private->children));
+ list;
+ list = g_list_next (list))
+ {
+ GimpItem *child = list->data;
+ GimpItem *new_child;
+ GimpLayerMask *mask;
+
+ new_child = gimp_item_duplicate (child, G_TYPE_FROM_INSTANCE (child));
+
+ gimp_object_set_name (GIMP_OBJECT (new_child),
+ gimp_object_get_name (child));
+
+ mask = gimp_layer_get_mask (GIMP_LAYER (child));
+
+ if (mask)
+ {
+ GimpLayerMask *new_mask;
+
+ new_mask = gimp_layer_get_mask (GIMP_LAYER (new_child));
+
+ gimp_object_set_name (GIMP_OBJECT (new_mask),
+ gimp_object_get_name (mask));
+ }
+
+ gimp_viewable_set_parent (GIMP_VIEWABLE (new_child),
+ GIMP_VIEWABLE (new_group));
+
+ gimp_container_insert (new_private->children,
+ GIMP_OBJECT (new_child),
+ position++);
+ }
+
+ /* force the projection to reallocate itself */
+ GET_PRIVATE (new_group)->reallocate_projection = TRUE;
+
+ gimp_group_layer_resume_resize (new_group, FALSE);
+ }
+
+ return new_item;
+}
+
+static void
+gimp_group_layer_convert (GimpItem *item,
+ GimpImage *dest_image,
+ GType old_type)
+{
+ GimpGroupLayerPrivate *private = GET_PRIVATE (item);
+ GList *list;
+
+ for (list = gimp_item_stack_get_item_iter (GIMP_ITEM_STACK (private->children));
+ list;
+ list = g_list_next (list))
+ {
+ GimpItem *child = list->data;
+
+ GIMP_ITEM_GET_CLASS (child)->convert (child, dest_image,
+ G_TYPE_FROM_INSTANCE (child));
+ }
+
+ GIMP_ITEM_CLASS (parent_class)->convert (item, dest_image, old_type);
+}
+
+static void
+gimp_group_layer_start_transform (GimpItem *item,
+ gboolean push_undo)
+{
+ _gimp_group_layer_start_transform (GIMP_GROUP_LAYER (item), push_undo);
+
+ if (GIMP_ITEM_CLASS (parent_class)->start_transform)
+ GIMP_ITEM_CLASS (parent_class)->start_transform (item, push_undo);
+}
+
+static void
+gimp_group_layer_end_transform (GimpItem *item,
+ gboolean push_undo)
+{
+ if (GIMP_ITEM_CLASS (parent_class)->end_transform)
+ GIMP_ITEM_CLASS (parent_class)->end_transform (item, push_undo);
+
+ _gimp_group_layer_end_transform (GIMP_GROUP_LAYER (item), push_undo);
+}
+
+static void
+gimp_group_layer_resize (GimpItem *item,
+ GimpContext *context,
+ GimpFillType fill_type,
+ gint new_width,
+ gint new_height,
+ gint offset_x,
+ gint offset_y)
+{
+ GimpGroupLayer *group = GIMP_GROUP_LAYER (item);
+ GimpGroupLayerPrivate *private = GET_PRIVATE (item);
+ GList *list;
+ gint x, y;
+
+ /* we implement GimpItem::resize(), instead of GimpLayer::resize(), so that
+ * GimpLayer doesn't resize the mask. note that gimp_item_resize() calls
+ * gimp_item_{start,end}_move(), and not gimp_item_{start,end}_transform(),
+ * so that mask resizing is handled by gimp_group_layer_update_size().
+ */
+
+ x = gimp_item_get_offset_x (item) - offset_x;
+ y = gimp_item_get_offset_y (item) - offset_y;
+
+ gimp_group_layer_suspend_resize (group, TRUE);
+
+ list = gimp_item_stack_get_item_iter (GIMP_ITEM_STACK (private->children));
+
+ while (list)
+ {
+ GimpItem *child = list->data;
+ gint child_width;
+ gint child_height;
+ gint child_x;
+ gint child_y;
+
+ list = g_list_next (list);
+
+ if (gimp_rectangle_intersect (x,
+ y,
+ new_width,
+ new_height,
+ gimp_item_get_offset_x (child),
+ gimp_item_get_offset_y (child),
+ gimp_item_get_width (child),
+ gimp_item_get_height (child),
+ &child_x,
+ &child_y,
+ &child_width,
+ &child_height))
+ {
+ gint child_offset_x = gimp_item_get_offset_x (child) - child_x;
+ gint child_offset_y = gimp_item_get_offset_y (child) - child_y;
+
+ gimp_item_resize (child, context, fill_type,
+ child_width, child_height,
+ child_offset_x, child_offset_y);
+ }
+ else if (gimp_item_is_attached (item))
+ {
+ gimp_image_remove_layer (gimp_item_get_image (item),
+ GIMP_LAYER (child),
+ TRUE, NULL);
+ }
+ else
+ {
+ gimp_container_remove (private->children, GIMP_OBJECT (child));
+ }
+ }
+
+ gimp_group_layer_resume_resize (group, TRUE);
+}
+
+static GimpTransformResize
+gimp_group_layer_get_clip (GimpItem *item,
+ GimpTransformResize clip_result)
+{
+ /* TODO: add clipping support, by clipping all sublayers as a unit, instead
+ * of individually.
+ */
+ return GIMP_TRANSFORM_RESIZE_ADJUST;
+}
+
+static gint64
+gimp_group_layer_estimate_memsize (GimpDrawable *drawable,
+ GimpComponentType component_type,
+ gint width,
+ gint height)
+{
+ GimpGroupLayerPrivate *private = GET_PRIVATE (drawable);
+ GList *list;
+ GimpImageBaseType base_type;
+ gint64 memsize = 0;
+
+ for (list = gimp_item_stack_get_item_iter (GIMP_ITEM_STACK (private->children));
+ list;
+ list = g_list_next (list))
+ {
+ GimpDrawable *child = list->data;
+ gint child_width;
+ gint child_height;
+
+ child_width = (gimp_item_get_width (GIMP_ITEM (child)) *
+ width /
+ gimp_item_get_width (GIMP_ITEM (drawable)));
+ child_height = (gimp_item_get_height (GIMP_ITEM (child)) *
+ height /
+ gimp_item_get_height (GIMP_ITEM (drawable)));
+
+ memsize += gimp_drawable_estimate_memsize (child,
+ component_type,
+ child_width,
+ child_height);
+ }
+
+ base_type = gimp_drawable_get_base_type (drawable);
+
+ memsize += gimp_projection_estimate_memsize (base_type, component_type,
+ width, height);
+
+ return memsize +
+ GIMP_DRAWABLE_CLASS (parent_class)->estimate_memsize (drawable,
+ component_type,
+ width, height);
+}
+
+static void
+gimp_group_layer_update_all (GimpDrawable *drawable)
+{
+ GimpGroupLayerPrivate *private = GET_PRIVATE (drawable);
+ GList *list;
+
+ /* redirect stack updates to the drawable, rather than to the projection */
+ private->direct_update++;
+
+ for (list = gimp_item_stack_get_item_iter (GIMP_ITEM_STACK (private->children));
+ list;
+ list = g_list_next (list))
+ {
+ GimpFilter *child = list->data;
+
+ if (gimp_filter_get_active (child))
+ gimp_drawable_update_all (GIMP_DRAWABLE (child));
+ }
+
+ /* redirect stack updates back to the projection */
+ private->direct_update--;
+}
+
+static void
+gimp_group_layer_translate (GimpLayer *layer,
+ gint offset_x,
+ gint offset_y)
+{
+ GimpGroupLayer *group = GIMP_GROUP_LAYER (layer);
+ GimpGroupLayerPrivate *private = GET_PRIVATE (layer);
+ gint x, y;
+ GList *list;
+
+ /* don't use gimp_group_layer_suspend_resize(), but rather increment
+ * private->suspend_resize directly, since we're translating the group layer
+ * here, rather than relying on gimp_group_layer_update_size() to do it.
+ */
+ private->suspend_resize++;
+
+ /* redirect stack updates to the drawable, rather than to the projection */
+ private->direct_update++;
+
+ /* translate the child layers *before* updating the group's offset, so that,
+ * if this is a nested group, the parent's bounds still reflect the original
+ * layer positions. This prevents the original area of the child layers,
+ * which is updated as part of their translation, from being clipped to the
+ * post-translation parent bounds (see issue #3484). The new area of the
+ * child layers, which is likewise updated as part of translation, *may* get
+ * clipped to the old parent bounds, but the corresponding region will be
+ * updated anyway when the parent is resized, once we update the group's
+ * offset.
+ */
+ for (list = gimp_item_stack_get_item_iter (GIMP_ITEM_STACK (private->children));
+ list;
+ list = g_list_next (list))
+ {
+ GimpItem *child = list->data;
+
+ /* don't push an undo here because undo will call us again */
+ gimp_item_translate (child, offset_x, offset_y, FALSE);
+ }
+
+ gimp_item_get_offset (GIMP_ITEM (group), &x, &y);
+
+ x += offset_x;
+ y += offset_y;
+
+ /* update the offset node */
+ if (private->offset_node)
+ gegl_node_set (private->offset_node,
+ "x", (gdouble) -x,
+ "y", (gdouble) -y,
+ NULL);
+
+ /* invalidate the selection boundary because of a layer modification */
+ gimp_drawable_invalidate_boundary (GIMP_DRAWABLE (layer));
+
+ /* update the group layer offset */
+ gimp_item_set_offset (GIMP_ITEM (group), x, y);
+
+ /* redirect stack updates back to the projection */
+ private->direct_update--;
+
+ /* don't use gimp_group_layer_resume_resize(), but rather decrement
+ * private->suspend_resize directly, so that gimp_group_layer_update_size()
+ * isn't called.
+ */
+ private->suspend_resize--;
+}
+
+static void
+gimp_group_layer_scale (GimpLayer *layer,
+ gint new_width,
+ gint new_height,
+ gint new_offset_x,
+ gint new_offset_y,
+ GimpInterpolationType interpolation_type,
+ GimpProgress *progress)
+{
+ GimpGroupLayer *group = GIMP_GROUP_LAYER (layer);
+ GimpGroupLayerPrivate *private = GET_PRIVATE (layer);
+ GimpItem *item = GIMP_ITEM (layer);
+ GimpObjectQueue *queue = NULL;
+ GList *list;
+ gdouble width_factor;
+ gdouble height_factor;
+ gint old_offset_x;
+ gint old_offset_y;
+
+ width_factor = (gdouble) new_width / (gdouble) gimp_item_get_width (item);
+ height_factor = (gdouble) new_height / (gdouble) gimp_item_get_height (item);
+
+ old_offset_x = gimp_item_get_offset_x (item);
+ old_offset_y = gimp_item_get_offset_y (item);
+
+ if (progress)
+ {
+ queue = gimp_object_queue_new (progress);
+ progress = GIMP_PROGRESS (queue);
+
+ gimp_object_queue_push_container (queue, private->children);
+ }
+
+ gimp_group_layer_suspend_resize (group, TRUE);
+
+ list = gimp_item_stack_get_item_iter (GIMP_ITEM_STACK (private->children));
+
+ while (list)
+ {
+ GimpItem *child = list->data;
+
+ list = g_list_next (list);
+
+ if (queue)
+ gimp_object_queue_pop (queue);
+
+ if (! gimp_item_scale_by_factors_with_origin (child,
+ width_factor, height_factor,
+ old_offset_x, old_offset_y,
+ new_offset_x, new_offset_y,
+ interpolation_type,
+ progress))
+ {
+ /* new width or height are 0; remove item */
+ if (gimp_item_is_attached (item))
+ {
+ gimp_image_remove_layer (gimp_item_get_image (item),
+ GIMP_LAYER (child),
+ TRUE, NULL);
+ }
+ else
+ {
+ gimp_container_remove (private->children, GIMP_OBJECT (child));
+ }
+ }
+ }
+
+ gimp_group_layer_resume_resize (group, TRUE);
+
+ g_clear_object (&queue);
+}
+
+static void
+gimp_group_layer_flip (GimpLayer *layer,
+ GimpContext *context,
+ GimpOrientationType flip_type,
+ gdouble axis,
+ gboolean clip_result)
+{
+ GimpGroupLayer *group = GIMP_GROUP_LAYER (layer);
+ GimpGroupLayerPrivate *private = GET_PRIVATE (layer);
+ GList *list;
+
+ gimp_group_layer_suspend_resize (group, TRUE);
+
+ for (list = gimp_item_stack_get_item_iter (GIMP_ITEM_STACK (private->children));
+ list;
+ list = g_list_next (list))
+ {
+ GimpItem *child = list->data;
+
+ gimp_item_flip (child, context,
+ flip_type, axis, clip_result);
+ }
+
+ gimp_group_layer_resume_resize (group, TRUE);
+}
+
+static void
+gimp_group_layer_rotate (GimpLayer *layer,
+ GimpContext *context,
+ GimpRotationType rotate_type,
+ gdouble center_x,
+ gdouble center_y,
+ gboolean clip_result)
+{
+ GimpGroupLayer *group = GIMP_GROUP_LAYER (layer);
+ GimpGroupLayerPrivate *private = GET_PRIVATE (layer);
+ GList *list;
+
+ gimp_group_layer_suspend_resize (group, TRUE);
+
+ for (list = gimp_item_stack_get_item_iter (GIMP_ITEM_STACK (private->children));
+ list;
+ list = g_list_next (list))
+ {
+ GimpItem *child = list->data;
+
+ gimp_item_rotate (child, context,
+ rotate_type, center_x, center_y, clip_result);
+ }
+
+ gimp_group_layer_resume_resize (group, TRUE);
+}
+
+static void
+gimp_group_layer_transform (GimpLayer *layer,
+ GimpContext *context,
+ const GimpMatrix3 *matrix,
+ GimpTransformDirection direction,
+ GimpInterpolationType interpolation_type,
+ GimpTransformResize clip_result,
+ GimpProgress *progress)
+{
+ GimpGroupLayer *group = GIMP_GROUP_LAYER (layer);
+ GimpGroupLayerPrivate *private = GET_PRIVATE (layer);
+ GimpObjectQueue *queue = NULL;
+ GList *list;
+
+ if (progress)
+ {
+ queue = gimp_object_queue_new (progress);
+ progress = GIMP_PROGRESS (queue);
+
+ gimp_object_queue_push_container (queue, private->children);
+ }
+
+ gimp_group_layer_suspend_resize (group, TRUE);
+
+ for (list = gimp_item_stack_get_item_iter (GIMP_ITEM_STACK (private->children));
+ list;
+ list = g_list_next (list))
+ {
+ GimpItem *child = list->data;
+
+ if (queue)
+ gimp_object_queue_pop (queue);
+
+ gimp_item_transform (child, context,
+ matrix, direction,
+ interpolation_type,
+ clip_result, progress);
+ }
+
+ gimp_group_layer_resume_resize (group, TRUE);
+
+ g_clear_object (&queue);
+}
+
+static const Babl *
+get_projection_format (GimpProjectable *projectable,
+ GimpImageBaseType base_type,
+ GimpPrecision precision)
+{
+ GimpImage *image = gimp_item_get_image (GIMP_ITEM (projectable));
+
+ switch (base_type)
+ {
+ case GIMP_RGB:
+ case GIMP_INDEXED:
+ return gimp_image_get_format (image, GIMP_RGB, precision, TRUE);
+
+ case GIMP_GRAY:
+ return gimp_image_get_format (image, GIMP_GRAY, precision, TRUE);
+ }
+
+ g_return_val_if_reached (NULL);
+}
+
+static void
+gimp_group_layer_convert_type (GimpLayer *layer,
+ GimpImage *dest_image,
+ const Babl *new_format,
+ GimpColorProfile *dest_profile,
+ GeglDitherMethod layer_dither_type,
+ GeglDitherMethod mask_dither_type,
+ gboolean push_undo,
+ GimpProgress *progress)
+{
+ GimpGroupLayer *group = GIMP_GROUP_LAYER (layer);
+ GimpGroupLayerPrivate *private = GET_PRIVATE (layer);
+ GeglBuffer *buffer;
+
+ if (push_undo)
+ {
+ GimpImage *image = gimp_item_get_image (GIMP_ITEM (group));
+
+ gimp_image_undo_push_group_layer_convert (image, NULL, group);
+ }
+
+ /* Need to temporarily set the projectable's format to the new
+ * values so the projection will create its tiles with the right
+ * depth
+ */
+ private->convert_format =
+ get_projection_format (GIMP_PROJECTABLE (group),
+ gimp_babl_format_get_base_type (new_format),
+ gimp_babl_format_get_precision (new_format));
+ gimp_projectable_structure_changed (GIMP_PROJECTABLE (group));
+ gimp_group_layer_flush (group);
+
+ buffer = gimp_pickable_get_buffer (GIMP_PICKABLE (private->projection));
+
+ gimp_drawable_set_buffer_full (GIMP_DRAWABLE (group),
+ FALSE, NULL,
+ buffer, NULL,
+ TRUE);
+
+ /* reset, the actual format is right now */
+ private->convert_format = NULL;
+}
+
+static GeglNode *
+gimp_group_layer_get_source_node (GimpDrawable *drawable)
+{
+ GimpGroupLayerPrivate *private = GET_PRIVATE (drawable);
+ GeglNode *input;
+
+ g_warn_if_fail (private->source_node == NULL);
+
+ private->source_node = gegl_node_new ();
+
+ input = gegl_node_get_input_proxy (private->source_node, "input");
+
+ private->parent_source_node =
+ GIMP_DRAWABLE_CLASS (parent_class)->get_source_node (drawable);
+
+ gegl_node_add_child (private->source_node, private->parent_source_node);
+
+ g_object_unref (private->parent_source_node);
+
+ if (gegl_node_has_pad (private->parent_source_node, "input"))
+ {
+ gegl_node_connect_to (input, "output",
+ private->parent_source_node, "input");
+ }
+
+ /* make sure we have a graph */
+ (void) gimp_group_layer_get_graph (GIMP_PROJECTABLE (drawable));
+
+ gegl_node_add_child (private->source_node, private->graph);
+
+ gimp_group_layer_update_source_node (GIMP_GROUP_LAYER (drawable));
+
+ return g_object_ref (private->source_node);
+}
+
+static void
+gimp_group_layer_opacity_changed (GimpLayer *layer)
+{
+ gimp_layer_update_effective_mode (layer);
+
+ if (GIMP_LAYER_CLASS (parent_class)->opacity_changed)
+ GIMP_LAYER_CLASS (parent_class)->opacity_changed (layer);
+}
+
+static void
+gimp_group_layer_effective_mode_changed (GimpLayer *layer)
+{
+ GimpGroupLayer *group = GIMP_GROUP_LAYER (layer);
+ GimpGroupLayerPrivate *private = GET_PRIVATE (layer);
+ GimpLayerMode mode;
+ gboolean pass_through;
+ gboolean update_bounding_box = FALSE;
+
+ gimp_layer_get_effective_mode (layer, &mode, NULL, NULL, NULL);
+
+ pass_through = (mode == GIMP_LAYER_MODE_PASS_THROUGH);
+
+ if (pass_through != private->pass_through)
+ {
+ if (private->pass_through && ! pass_through)
+ {
+ /* when switching from pass-through mode to a non-pass-through mode,
+ * flush the pickable in order to make sure the projection's buffer
+ * gets properly invalidated synchronously, so that it can be used
+ * as a source for the rest of the composition.
+ */
+ gimp_pickable_flush (GIMP_PICKABLE (private->projection));
+ }
+
+ private->pass_through = pass_through;
+
+ update_bounding_box = TRUE;
+ }
+
+ gimp_group_layer_update_source_node (group);
+ gimp_group_layer_update_mode_node (group);
+
+ if (update_bounding_box)
+ gimp_drawable_update_bounding_box (GIMP_DRAWABLE (group));
+
+ if (GIMP_LAYER_CLASS (parent_class)->effective_mode_changed)
+ GIMP_LAYER_CLASS (parent_class)->effective_mode_changed (layer);
+}
+
+static void
+gimp_group_layer_excludes_backdrop_changed (GimpLayer *layer)
+{
+ GimpGroupLayer *group = GIMP_GROUP_LAYER (layer);
+
+ gimp_group_layer_update_source_node (group);
+ gimp_group_layer_update_mode_node (group);
+
+ if (GIMP_LAYER_CLASS (parent_class)->excludes_backdrop_changed)
+ GIMP_LAYER_CLASS (parent_class)->excludes_backdrop_changed (layer);
+}
+
+static GeglRectangle
+gimp_group_layer_get_bounding_box (GimpLayer *layer)
+{
+ GimpGroupLayerPrivate *private = GET_PRIVATE (layer);
+
+ /* for pass-through groups, use the group's calculated bounding box, instead
+ * of the source-node's bounding box, since we don't update the bounding box
+ * on all events that may affect the latter, and since it includes the
+ * bounding box of the backdrop. this means we can't attach filters that may
+ * affect the bounding box to a pass-through group (since their effect weon't
+ * be reflected by the group's bounding box), but attaching filters to pass-
+ * through groups makes little sense anyway.
+ */
+ if (private->pass_through)
+ return private->bounding_box;
+ else
+ return GIMP_LAYER_CLASS (parent_class)->get_bounding_box (layer);
+}
+
+static void
+gimp_group_layer_get_effective_mode (GimpLayer *layer,
+ GimpLayerMode *mode,
+ GimpLayerColorSpace *blend_space,
+ GimpLayerColorSpace *composite_space,
+ GimpLayerCompositeMode *composite_mode)
+{
+ GimpGroupLayerPrivate *private = GET_PRIVATE (layer);
+
+ /* try to strength-reduce pass-through groups to normal groups, which are
+ * cheaper.
+ */
+ if (gimp_layer_get_mode (layer) == GIMP_LAYER_MODE_PASS_THROUGH &&
+ ! no_pass_through_strength_reduction)
+ {
+ /* we perform the strength-reduction if:
+ *
+ * - the group has no active children;
+ *
+ * or,
+ *
+ * - the group has a single active child; or,
+ *
+ * - the effective mode of all the active children is normal, their
+ * effective composite mode is UNION, and their effective blend and
+ * composite spaces are equal;
+ *
+ * - and,
+ *
+ * - the group's opacity is 100%, and it has no mask (or the mask
+ * isn't applied); or,
+ *
+ * - the group's composite space equals the active children's
+ * composite space.
+ */
+
+ GList *list;
+ gboolean reduce = TRUE;
+ gboolean first = TRUE;
+
+ *mode = GIMP_LAYER_MODE_NORMAL;
+ *blend_space = gimp_layer_get_real_blend_space (layer);
+ *composite_space = gimp_layer_get_real_composite_space (layer);
+ *composite_mode = gimp_layer_get_real_composite_mode (layer);
+
+ for (list = gimp_item_stack_get_item_iter (GIMP_ITEM_STACK (private->children));
+ list;
+ list = g_list_next (list))
+ {
+ GimpLayer *child = list->data;
+
+ if (! gimp_filter_get_active (GIMP_FILTER (child)))
+ continue;
+
+ if (first)
+ {
+ gimp_layer_get_effective_mode (child,
+ mode,
+ blend_space,
+ composite_space,
+ composite_mode);
+
+ if (*mode == GIMP_LAYER_MODE_NORMAL_LEGACY)
+ *mode = GIMP_LAYER_MODE_NORMAL;
+
+ first = FALSE;
+ }
+ else
+ {
+ GimpLayerMode other_mode;
+ GimpLayerColorSpace other_blend_space;
+ GimpLayerColorSpace other_composite_space;
+ GimpLayerCompositeMode other_composite_mode;
+
+ if (*mode != GIMP_LAYER_MODE_NORMAL ||
+ *composite_mode != GIMP_LAYER_COMPOSITE_UNION)
+ {
+ reduce = FALSE;
+
+ break;
+ }
+
+ gimp_layer_get_effective_mode (child,
+ &other_mode,
+ &other_blend_space,
+ &other_composite_space,
+ &other_composite_mode);
+
+ if (other_mode == GIMP_LAYER_MODE_NORMAL_LEGACY)
+ other_mode = GIMP_LAYER_MODE_NORMAL;
+
+ if (other_mode != *mode ||
+ other_blend_space != *blend_space ||
+ other_composite_space != *composite_space ||
+ other_composite_mode != *composite_mode)
+ {
+ reduce = FALSE;
+
+ break;
+ }
+ }
+ }
+
+ if (reduce)
+ {
+ gboolean has_mask;
+
+ has_mask = gimp_layer_get_mask (layer) &&
+ gimp_layer_get_apply_mask (layer);
+
+ if (first ||
+ (gimp_layer_get_opacity (layer) == GIMP_OPACITY_OPAQUE &&
+ ! has_mask) ||
+ *composite_space == gimp_layer_get_real_composite_space (layer))
+ {
+ /* strength reduction succeeded! */
+ return;
+ }
+ }
+ }
+
+ /* strength-reduction failed. chain up. */
+ GIMP_LAYER_CLASS (parent_class)->get_effective_mode (layer,
+ mode,
+ blend_space,
+ composite_space,
+ composite_mode);
+}
+
+static gboolean
+gimp_group_layer_get_excludes_backdrop (GimpLayer *layer)
+{
+ GimpGroupLayerPrivate *private = GET_PRIVATE (layer);
+
+ if (private->pass_through)
+ {
+ GList *list;
+
+ for (list = gimp_item_stack_get_item_iter (GIMP_ITEM_STACK (private->children));
+ list;
+ list = g_list_next (list))
+ {
+ GimpFilter *child = list->data;
+
+ if (gimp_filter_get_active (child) &&
+ gimp_layer_get_excludes_backdrop (GIMP_LAYER (child)))
+ return TRUE;
+ }
+
+ return FALSE;
+ }
+ else
+ return GIMP_LAYER_CLASS (parent_class)->get_excludes_backdrop (layer);
+}
+
+static const Babl *
+gimp_group_layer_get_format (GimpProjectable *projectable)
+{
+ GimpGroupLayerPrivate *private = GET_PRIVATE (projectable);
+ GimpImageBaseType base_type;
+ GimpPrecision precision;
+
+ if (private->convert_format)
+ return private->convert_format;
+
+ base_type = gimp_drawable_get_base_type (GIMP_DRAWABLE (projectable));
+ precision = gimp_drawable_get_precision (GIMP_DRAWABLE (projectable));
+
+ return get_projection_format (projectable, base_type, precision);
+}
+
+static GeglRectangle
+gimp_group_layer_projectable_get_bounding_box (GimpProjectable *projectable)
+{
+ GimpGroupLayerPrivate *private = GET_PRIVATE (projectable);
+
+ return private->bounding_box;
+}
+
+static GeglNode *
+gimp_group_layer_get_graph (GimpProjectable *projectable)
+{
+ GimpGroupLayer *group = GIMP_GROUP_LAYER (projectable);
+ GimpGroupLayerPrivate *private = GET_PRIVATE (projectable);
+ GeglNode *input;
+ GeglNode *layers_node;
+ GeglNode *output;
+ gint off_x;
+ gint off_y;
+
+ if (private->graph)
+ return private->graph;
+
+ private->graph = gegl_node_new ();
+
+ input = gegl_node_get_input_proxy (private->graph, "input");
+
+ layers_node =
+ gimp_filter_stack_get_graph (GIMP_FILTER_STACK (private->children));
+
+ gegl_node_add_child (private->graph, layers_node);
+
+ gegl_node_connect_to (input, "output",
+ layers_node, "input");
+
+ gimp_item_get_offset (GIMP_ITEM (group), &off_x, &off_y);
+
+ private->offset_node = gegl_node_new_child (private->graph,
+ "operation", "gegl:translate",
+ "x", (gdouble) -off_x,
+ "y", (gdouble) -off_y,
+ NULL);
+
+ gegl_node_connect_to (layers_node, "output",
+ private->offset_node, "input");
+
+ output = gegl_node_get_output_proxy (private->graph, "output");
+
+ gegl_node_connect_to (private->offset_node, "output",
+ output, "input");
+
+ return private->graph;
+}
+
+static void
+gimp_group_layer_begin_render (GimpProjectable *projectable)
+{
+ GimpGroupLayerPrivate *private = GET_PRIVATE (projectable);
+
+ if (private->source_node == NULL)
+ return;
+
+ if (private->pass_through)
+ gegl_node_disconnect (private->graph, "input");
+}
+
+static void
+gimp_group_layer_end_render (GimpProjectable *projectable)
+{
+ GimpGroupLayerPrivate *private = GET_PRIVATE (projectable);
+
+ if (private->source_node == NULL)
+ return;
+
+ if (private->pass_through)
+ {
+ GeglNode *input;
+
+ input = gegl_node_get_input_proxy (private->source_node, "input");
+
+ gegl_node_connect_to (input, "output",
+ private->graph, "input");
+ }
+}
+
+static void
+gimp_group_layer_pickable_flush (GimpPickable *pickable)
+{
+ GimpGroupLayerPrivate *private = GET_PRIVATE (pickable);
+
+ gimp_pickable_flush (GIMP_PICKABLE (private->projection));
+}
+
+static gdouble
+gimp_group_layer_get_opacity_at (GimpPickable *pickable,
+ gint x,
+ gint y)
+{
+ /* Only consider child layers as having content */
+
+ return GIMP_OPACITY_TRANSPARENT;
+}
+
+
+/* public functions */
+
+GimpLayer *
+gimp_group_layer_new (GimpImage *image)
+{
+ GimpGroupLayer *group;
+ const Babl *format;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+
+ format = gimp_image_get_layer_format (image, TRUE);
+
+ group = GIMP_GROUP_LAYER (gimp_drawable_new (GIMP_TYPE_GROUP_LAYER,
+ image, NULL,
+ 0, 0, 1, 1,
+ format));
+
+ gimp_layer_set_mode (GIMP_LAYER (group),
+ gimp_image_get_default_new_layer_mode (image),
+ FALSE);
+
+ return GIMP_LAYER (group);
+}
+
+GimpProjection *
+gimp_group_layer_get_projection (GimpGroupLayer *group)
+{
+ g_return_val_if_fail (GIMP_IS_GROUP_LAYER (group), NULL);
+
+ return GET_PRIVATE (group)->projection;
+}
+
+void
+gimp_group_layer_suspend_resize (GimpGroupLayer *group,
+ gboolean push_undo)
+{
+ GimpItem *item;
+
+ g_return_if_fail (GIMP_IS_GROUP_LAYER (group));
+
+ item = GIMP_ITEM (group);
+
+ if (! gimp_item_is_attached (item))
+ push_undo = FALSE;
+
+ if (push_undo)
+ gimp_image_undo_push_group_layer_suspend_resize (gimp_item_get_image (item),
+ NULL, group);
+
+ GET_PRIVATE (group)->suspend_resize++;
+}
+
+void
+gimp_group_layer_resume_resize (GimpGroupLayer *group,
+ gboolean push_undo)
+{
+ GimpGroupLayerPrivate *private;
+ GimpItem *item;
+ GimpItem *mask = NULL;
+ GeglBuffer *mask_buffer;
+ GeglRectangle mask_bounds;
+ GimpUndo *undo;
+
+ g_return_if_fail (GIMP_IS_GROUP_LAYER (group));
+
+ private = GET_PRIVATE (group);
+
+ g_return_if_fail (private->suspend_resize > 0);
+
+ item = GIMP_ITEM (group);
+
+ if (! gimp_item_is_attached (item))
+ push_undo = FALSE;
+
+ if (push_undo)
+ {
+ undo =
+ gimp_image_undo_push_group_layer_resume_resize (gimp_item_get_image (item),
+ NULL, group);
+
+ /* if there were any {suspend,resume}_mask() calls during the time the
+ * group's size was suspended, the resume_mask() calls will not have seen
+ * any changes to the mask, and will therefore won't restore the mask
+ * during undo. if the group's bounding box did change while resize was
+ * suspended, and if there are no other {suspend,resume}_mask() blocks
+ * that will see the resized mask, we have to restore the mask during the
+ * resume_resize() undo.
+ *
+ * we ref the mask buffer here, and compare it to the mask buffer after
+ * updating the size.
+ */
+ if (private->suspend_resize == 1 && private->suspend_mask == 0)
+ {
+ mask = GIMP_ITEM (gimp_layer_get_mask (GIMP_LAYER (group)));
+
+ if (mask)
+ {
+ mask_buffer =
+ g_object_ref (gimp_drawable_get_buffer (GIMP_DRAWABLE (mask)));
+
+ mask_bounds.x = gimp_item_get_offset_x (mask);
+ mask_bounds.y = gimp_item_get_offset_y (mask);
+ mask_bounds.width = gimp_item_get_width (mask);
+ mask_bounds.height = gimp_item_get_height (mask);
+ }
+ }
+ }
+
+ private->suspend_resize--;
+
+ if (private->suspend_resize == 0)
+ {
+ gimp_group_layer_update_size (group);
+
+ if (mask)
+ {
+ /* if the mask changed, make sure it's restored during undo, as per
+ * the comment above.
+ */
+ if (gimp_drawable_get_buffer (GIMP_DRAWABLE (mask)) != mask_buffer)
+ {
+ g_return_if_fail (undo != NULL);
+
+ GIMP_GROUP_LAYER_UNDO (undo)->mask_buffer = mask_buffer;
+ GIMP_GROUP_LAYER_UNDO (undo)->mask_bounds = mask_bounds;
+ }
+ else
+ {
+ g_object_unref (mask_buffer);
+ }
+ }
+ }
+}
+
+void
+gimp_group_layer_suspend_mask (GimpGroupLayer *group,
+ gboolean push_undo)
+{
+ GimpGroupLayerPrivate *private;
+ GimpItem *item;
+
+ g_return_if_fail (GIMP_IS_GROUP_LAYER (group));
+
+ private = GET_PRIVATE (group);
+ item = GIMP_ITEM (group);
+
+ /* avoid pushing an undo step if this is a nested suspend_mask() call, since
+ * the value of 'push_undo' in nested calls should be the same as that passed
+ * to the outermost call, and only pushing an undo step for the outermost
+ * call in this case is enough. we can't support cases where the values of
+ * 'push_undo' in nested calls are different in a meaningful way, and
+ * avoiding undo steps for nested calls prevents us from storing multiple
+ * references to the suspend mask buffer on the undo stack. while storing
+ * multiple references to the buffer doesn't waste any memory (since all the
+ * references are to the same buffer), it does cause the undo stack memory-
+ * usage estimation to overshoot, potentially resulting in undo steps being
+ * dropped unnecessarily.
+ */
+ if (! gimp_item_is_attached (item) || private->suspend_mask > 0)
+ push_undo = FALSE;
+
+ if (push_undo)
+ gimp_image_undo_push_group_layer_suspend_mask (gimp_item_get_image (item),
+ NULL, group);
+
+ if (private->suspend_mask == 0)
+ {
+ if (gimp_layer_get_mask (GIMP_LAYER (group)))
+ {
+ GimpItem *mask = GIMP_ITEM (gimp_layer_get_mask (GIMP_LAYER (group)));
+
+ private->suspended_mask_buffer =
+ g_object_ref (gimp_drawable_get_buffer (GIMP_DRAWABLE (mask)));
+
+ private->suspended_mask_bounds.x = gimp_item_get_offset_x (mask);
+ private->suspended_mask_bounds.y = gimp_item_get_offset_y (mask);
+ private->suspended_mask_bounds.width = gimp_item_get_width (mask);
+ private->suspended_mask_bounds.height = gimp_item_get_height (mask);
+ }
+ else
+ {
+ private->suspended_mask_buffer = NULL;
+ }
+ }
+
+ private->suspend_mask++;
+}
+
+void
+gimp_group_layer_resume_mask (GimpGroupLayer *group,
+ gboolean push_undo)
+{
+ GimpGroupLayerPrivate *private;
+ GimpItem *item;
+
+ g_return_if_fail (GIMP_IS_GROUP_LAYER (group));
+
+ private = GET_PRIVATE (group);
+
+ g_return_if_fail (private->suspend_mask > 0);
+
+ item = GIMP_ITEM (group);
+
+ /* avoid pushing an undo step if this is a nested resume_mask() call. see
+ * the comment in gimp_group_layer_suspend_mask().
+ */
+ if (! gimp_item_is_attached (item) || private->suspend_mask > 1)
+ push_undo = FALSE;
+
+ if (push_undo)
+ gimp_image_undo_push_group_layer_resume_mask (gimp_item_get_image (item),
+ NULL, group);
+
+ private->suspend_mask--;
+
+ if (private->suspend_mask == 0)
+ g_clear_object (&private->suspended_mask_buffer);
+}
+
+
+/* protected functions */
+
+void
+_gimp_group_layer_set_suspended_mask (GimpGroupLayer *group,
+ GeglBuffer *buffer,
+ const GeglRectangle *bounds)
+{
+ GimpGroupLayerPrivate *private;
+
+ g_return_if_fail (GIMP_IS_GROUP_LAYER (group));
+ g_return_if_fail (buffer != NULL);
+ g_return_if_fail (bounds != NULL);
+
+ private = GET_PRIVATE (group);
+
+ g_return_if_fail (private->suspend_mask > 0);
+
+ g_object_ref (buffer);
+
+ g_clear_object (&private->suspended_mask_buffer);
+
+ private->suspended_mask_buffer = buffer;
+ private->suspended_mask_bounds = *bounds;
+}
+
+GeglBuffer *
+_gimp_group_layer_get_suspended_mask (GimpGroupLayer *group,
+ GeglRectangle *bounds)
+{
+ GimpGroupLayerPrivate *private;
+ GimpLayerMask *mask;
+
+ g_return_val_if_fail (GIMP_IS_GROUP_LAYER (group), NULL);
+ g_return_val_if_fail (bounds != NULL, NULL);
+
+ private = GET_PRIVATE (group);
+ mask = gimp_layer_get_mask (GIMP_LAYER (group));
+
+ g_return_val_if_fail (private->suspend_mask > 0, NULL);
+
+ if (mask &&
+ gimp_drawable_get_buffer (GIMP_DRAWABLE (mask)) !=
+ private->suspended_mask_buffer)
+ {
+ *bounds = private->suspended_mask_bounds;
+
+ return private->suspended_mask_buffer;
+ }
+
+ return NULL;
+}
+
+void
+_gimp_group_layer_start_transform (GimpGroupLayer *group,
+ gboolean push_undo)
+{
+ GimpGroupLayerPrivate *private;
+ GimpItem *item;
+
+ g_return_if_fail (GIMP_IS_GROUP_LAYER (group));
+
+ private = GET_PRIVATE (group);
+ item = GIMP_ITEM (group);
+
+ g_return_if_fail (private->suspend_mask == 0);
+
+ if (! gimp_item_is_attached (item))
+ push_undo = FALSE;
+
+ if (push_undo)
+ gimp_image_undo_push_group_layer_start_transform (gimp_item_get_image (item),
+ NULL, group);
+
+ private->transforming++;
+}
+
+void
+_gimp_group_layer_end_transform (GimpGroupLayer *group,
+ gboolean push_undo)
+{
+ GimpGroupLayerPrivate *private;
+ GimpItem *item;
+
+ g_return_if_fail (GIMP_IS_GROUP_LAYER (group));
+
+ private = GET_PRIVATE (group);
+ item = GIMP_ITEM (group);
+
+ g_return_if_fail (private->suspend_mask == 0);
+ g_return_if_fail (private->transforming > 0);
+
+ if (! gimp_item_is_attached (item))
+ push_undo = FALSE;
+
+ if (push_undo)
+ gimp_image_undo_push_group_layer_end_transform (gimp_item_get_image (item),
+ NULL, group);
+
+ private->transforming--;
+
+ if (private->transforming == 0)
+ gimp_group_layer_update_mask_size (GIMP_GROUP_LAYER (item));
+}
+
+
+/* private functions */
+
+static void
+gimp_group_layer_child_add (GimpContainer *container,
+ GimpLayer *child,
+ GimpGroupLayer *group)
+{
+ gimp_group_layer_update (group);
+
+ if (gimp_filter_get_active (GIMP_FILTER (child)))
+ {
+ gimp_layer_update_effective_mode (GIMP_LAYER (group));
+
+ if (gimp_layer_get_excludes_backdrop (child))
+ gimp_layer_update_excludes_backdrop (GIMP_LAYER (group));
+ }
+}
+
+static void
+gimp_group_layer_child_remove (GimpContainer *container,
+ GimpLayer *child,
+ GimpGroupLayer *group)
+{
+ gimp_group_layer_update (group);
+
+ if (gimp_filter_get_active (GIMP_FILTER (child)))
+ {
+ gimp_layer_update_effective_mode (GIMP_LAYER (group));
+
+ if (gimp_layer_get_excludes_backdrop (child))
+ gimp_layer_update_excludes_backdrop (GIMP_LAYER (group));
+ }
+}
+
+static void
+gimp_group_layer_child_move (GimpLayer *child,
+ GParamSpec *pspec,
+ GimpGroupLayer *group)
+{
+ gimp_group_layer_update (group);
+}
+
+static void
+gimp_group_layer_child_resize (GimpLayer *child,
+ GimpGroupLayer *group)
+{
+ gimp_group_layer_update (group);
+}
+
+static void
+gimp_group_layer_child_active_changed (GimpLayer *child,
+ GimpGroupLayer *group)
+{
+ gimp_layer_update_effective_mode (GIMP_LAYER (group));
+
+ if (gimp_layer_get_excludes_backdrop (child))
+ gimp_layer_update_excludes_backdrop (GIMP_LAYER (group));
+}
+
+static void
+gimp_group_layer_child_effective_mode_changed (GimpLayer *child,
+ GimpGroupLayer *group)
+{
+ if (gimp_filter_get_active (GIMP_FILTER (child)))
+ gimp_layer_update_effective_mode (GIMP_LAYER (group));
+}
+
+static void
+gimp_group_layer_child_excludes_backdrop_changed (GimpLayer *child,
+ GimpGroupLayer *group)
+{
+ if (gimp_filter_get_active (GIMP_FILTER (child)))
+ gimp_layer_update_excludes_backdrop (GIMP_LAYER (group));
+}
+
+static void
+gimp_group_layer_flush (GimpGroupLayer *group)
+{
+ GimpGroupLayerPrivate *private = GET_PRIVATE (group);
+
+ if (private->pass_through)
+ {
+ /* flush the projectable, not the pickable, because the source
+ * node of pass-through groups doesn't use the projection's
+ * buffer, hence there's no need to invalidate it synchronously.
+ */
+ gimp_projectable_flush (GIMP_PROJECTABLE (group), TRUE);
+ }
+ else
+ {
+ /* make sure we have a buffer, and stop any idle rendering, which is
+ * initiated when a new buffer is allocated. the call to
+ * gimp_pickable_flush() below causes any pending idle rendering to
+ * finish synchronously, so this needs to happen before.
+ */
+ gimp_pickable_get_buffer (GIMP_PICKABLE (private->projection));
+ gimp_projection_stop_rendering (private->projection);
+
+ /* flush the pickable not the projectable because flushing the
+ * pickable will finish all invalidation on the projection so it
+ * can be used as source (note that it will still be constructed
+ * when the actual read happens, so this it not a performance
+ * problem)
+ */
+ gimp_pickable_flush (GIMP_PICKABLE (private->projection));
+ }
+}
+
+static void
+gimp_group_layer_update (GimpGroupLayer *group)
+{
+ if (GET_PRIVATE (group)->suspend_resize == 0)
+ {
+ gimp_group_layer_update_size (group);
+ }
+}
+
+static void
+gimp_group_layer_update_size (GimpGroupLayer *group)
+{
+ GimpGroupLayerPrivate *private = GET_PRIVATE (group);
+ GimpItem *item = GIMP_ITEM (group);
+ GimpLayer *layer = GIMP_LAYER (group);
+ GimpItem *mask = GIMP_ITEM (gimp_layer_get_mask (layer));
+ GeglRectangle old_bounds;
+ GeglRectangle bounds;
+ GeglRectangle old_bounding_box;
+ GeglRectangle bounding_box;
+ gboolean first = TRUE;
+ gboolean size_changed;
+ gboolean resize_mask;
+ GList *list;
+
+ old_bounds.x = gimp_item_get_offset_x (item);
+ old_bounds.y = gimp_item_get_offset_y (item);
+ old_bounds.width = gimp_item_get_width (item);
+ old_bounds.height = gimp_item_get_height (item);
+
+ bounds.x = 0;
+ bounds.y = 0;
+ bounds.width = 1;
+ bounds.height = 1;
+
+ old_bounding_box = private->bounding_box;
+ bounding_box = bounds;
+
+ for (list = gimp_item_stack_get_item_iter (GIMP_ITEM_STACK (private->children));
+ list;
+ list = g_list_next (list))
+ {
+ GimpItem *child = list->data;
+ GeglRectangle child_bounds;
+ GeglRectangle child_bounding_box;
+
+ if (! gimp_viewable_get_size (GIMP_VIEWABLE (child),
+ &child_bounds.width, &child_bounds.height))
+ {
+ /* ignore children without content (empty group layers);
+ * see bug 777017
+ */
+ continue;
+ }
+
+ gimp_item_get_offset (child, &child_bounds.x, &child_bounds.y);
+
+ child_bounding_box =
+ gimp_drawable_get_bounding_box (GIMP_DRAWABLE (child));
+
+ child_bounding_box.x += child_bounds.x;
+ child_bounding_box.y += child_bounds.y;
+
+ if (first)
+ {
+ bounds = child_bounds;
+ bounding_box = child_bounding_box;
+
+ first = FALSE;
+ }
+ else
+ {
+ gegl_rectangle_bounding_box (&bounds,
+ &bounds, &child_bounds);
+ gegl_rectangle_bounding_box (&bounding_box,
+ &bounding_box, &child_bounding_box);
+ }
+ }
+
+ bounding_box.x -= bounds.x;
+ bounding_box.y -= bounds.y;
+
+ size_changed = ! (gegl_rectangle_equal (&bounds, &old_bounds) &&
+ gegl_rectangle_equal (&bounding_box, &old_bounding_box));
+
+ resize_mask = mask && ! gegl_rectangle_equal (&bounds, &old_bounds);
+
+ /* if we show the mask, invalidate the old mask area */
+ if (resize_mask && gimp_layer_get_show_mask (layer))
+ {
+ gimp_drawable_update (GIMP_DRAWABLE (group),
+ gimp_item_get_offset_x (mask) - old_bounds.x,
+ gimp_item_get_offset_y (mask) - old_bounds.y,
+ gimp_item_get_width (mask),
+ gimp_item_get_height (mask));
+ }
+
+ if (private->reallocate_projection || size_changed)
+ {
+ GeglBuffer *buffer;
+
+ /* if the graph is already constructed, set the offset node's
+ * coordinates first, so the graph is in the right state when
+ * the projection is reallocated, see bug #730550.
+ */
+ if (private->offset_node)
+ gegl_node_set (private->offset_node,
+ "x", (gdouble) -bounds.x,
+ "y", (gdouble) -bounds.y,
+ NULL);
+
+ /* update our offset *before* calling gimp_pickable_get_buffer(), so
+ * that if our graph isn't constructed yet, the offset node picks
+ * up the right coordinates in gimp_group_layer_get_graph().
+ */
+ gimp_item_set_offset (item, bounds.x, bounds.y);
+
+ /* update the bounding box before updating the projection, so that it
+ * picks up the right size.
+ */
+ private->bounding_box = bounding_box;
+
+ if (private->reallocate_projection)
+ {
+ private->reallocate_projection = FALSE;
+
+ gimp_projectable_structure_changed (GIMP_PROJECTABLE (group));
+ }
+ else
+ {
+ /* when there's no need to reallocate the projection, we call
+ * gimp_projectable_bounds_changed(), rather than structure_chaned(),
+ * so that the projection simply copies the old content over to the
+ * new buffer with an offset, rather than re-renders the graph.
+ */
+ gimp_projectable_bounds_changed (GIMP_PROJECTABLE (group),
+ old_bounds.x, old_bounds.y);
+ }
+
+ buffer = gimp_pickable_get_buffer (GIMP_PICKABLE (private->projection));
+
+ gimp_drawable_set_buffer_full (GIMP_DRAWABLE (group),
+ FALSE, NULL,
+ buffer, &bounds,
+ FALSE /* don't update the drawable, the
+ * flush() below will take care of
+ * that.
+ */);
+
+ gimp_group_layer_flush (group);
+ }
+
+ /* resize the mask if not transforming (in which case, GimpLayer takes care
+ * of the mask)
+ */
+ if (resize_mask && ! private->transforming)
+ gimp_group_layer_update_mask_size (group);
+
+ /* if we show the mask, invalidate the new mask area */
+ if (resize_mask && gimp_layer_get_show_mask (layer))
+ {
+ gimp_drawable_update (GIMP_DRAWABLE (group),
+ gimp_item_get_offset_x (mask) - bounds.x,
+ gimp_item_get_offset_y (mask) - bounds.y,
+ gimp_item_get_width (mask),
+ gimp_item_get_height (mask));
+ }
+}
+
+static void
+gimp_group_layer_update_mask_size (GimpGroupLayer *group)
+{
+ GimpGroupLayerPrivate *private = GET_PRIVATE (group);
+ GimpItem *item = GIMP_ITEM (group);
+ GimpItem *mask;
+ GeglBuffer *buffer;
+ GeglBuffer *mask_buffer;
+ GeglRectangle bounds;
+ GeglRectangle mask_bounds;
+ GeglRectangle copy_bounds;
+ gboolean intersect;
+
+ mask = GIMP_ITEM (gimp_layer_get_mask (GIMP_LAYER (group)));
+
+ if (! mask)
+ return;
+
+ bounds.x = gimp_item_get_offset_x (item);
+ bounds.y = gimp_item_get_offset_y (item);
+ bounds.width = gimp_item_get_width (item);
+ bounds.height = gimp_item_get_height (item);
+
+ mask_bounds.x = gimp_item_get_offset_x (mask);
+ mask_bounds.y = gimp_item_get_offset_y (mask);
+ mask_bounds.width = gimp_item_get_width (mask);
+ mask_bounds.height = gimp_item_get_height (mask);
+
+ if (gegl_rectangle_equal (&bounds, &mask_bounds))
+ return;
+
+ buffer = gegl_buffer_new (GEGL_RECTANGLE (0, 0, bounds.width, bounds.height),
+ gimp_drawable_get_format (GIMP_DRAWABLE (mask)));
+
+ if (private->suspended_mask_buffer)
+ {
+ /* copy the suspended mask into the new mask */
+ mask_buffer = private->suspended_mask_buffer;
+ mask_bounds = private->suspended_mask_bounds;
+ }
+ else
+ {
+ /* copy the old mask into the new mask */
+ mask_buffer = gimp_drawable_get_buffer (GIMP_DRAWABLE (mask));
+ }
+
+ intersect = gimp_rectangle_intersect (bounds.x,
+ bounds.y,
+ bounds.width,
+ bounds.height,
+ mask_bounds.x,
+ mask_bounds.y,
+ mask_bounds.width,
+ mask_bounds.height,
+ &copy_bounds.x,
+ &copy_bounds.y,
+ &copy_bounds.width,
+ &copy_bounds.height);
+
+ if (intersect)
+ {
+ gimp_gegl_buffer_copy (mask_buffer,
+ GEGL_RECTANGLE (copy_bounds.x - mask_bounds.x,
+ copy_bounds.y - mask_bounds.y,
+ copy_bounds.width,
+ copy_bounds.height),
+ GEGL_ABYSS_NONE,
+ buffer,
+ GEGL_RECTANGLE (copy_bounds.x - bounds.x,
+ copy_bounds.y - bounds.y,
+ copy_bounds.width,
+ copy_bounds.height));
+ }
+
+ gimp_drawable_set_buffer_full (GIMP_DRAWABLE (mask),
+ FALSE, NULL,
+ buffer, &bounds,
+ TRUE);
+
+ g_object_unref (buffer);
+}
+
+static void
+gimp_group_layer_update_source_node (GimpGroupLayer *group)
+{
+ GimpGroupLayerPrivate *private = GET_PRIVATE (group);
+ GeglNode *input;
+ GeglNode *output;
+
+ if (private->source_node == NULL)
+ return;
+
+ input = gegl_node_get_input_proxy (private->source_node, "input");
+ output = gegl_node_get_output_proxy (private->source_node, "output");
+
+ if (private->pass_through)
+ {
+ gegl_node_connect_to (input, "output",
+ private->graph, "input");
+ gegl_node_connect_to (private->graph, "output",
+ output, "input");
+ }
+ else
+ {
+ gegl_node_disconnect (private->graph, "input");
+
+ gegl_node_connect_to (private->parent_source_node, "output",
+ output, "input");
+ }
+}
+
+static void
+gimp_group_layer_update_mode_node (GimpGroupLayer *group)
+{
+ GimpGroupLayerPrivate *private = GET_PRIVATE (group);
+ GeglNode *node;
+ GeglNode *input;
+ GeglNode *mode_node;
+
+ node = gimp_filter_get_node (GIMP_FILTER (group));
+ input = gegl_node_get_input_proxy (node, "input");
+ mode_node = gimp_drawable_get_mode_node (GIMP_DRAWABLE (group));
+
+ if (private->pass_through &&
+ gimp_layer_get_excludes_backdrop (GIMP_LAYER (group)))
+ {
+ gegl_node_disconnect (mode_node, "input");
+ }
+ else
+ {
+ gegl_node_connect_to (input, "output",
+ mode_node, "input");
+ }
+}
+
+static void
+gimp_group_layer_stack_update (GimpDrawableStack *stack,
+ gint x,
+ gint y,
+ gint width,
+ gint height,
+ GimpGroupLayer *group)
+{
+ GimpGroupLayerPrivate *private = GET_PRIVATE (group);
+
+#if 0
+ g_printerr ("%s (%s) %d, %d (%d, %d)\n",
+ G_STRFUNC, gimp_object_get_name (group),
+ x, y, width, height);
+#endif
+
+ if (! private->direct_update)
+ {
+ /* the layer stack's update signal speaks in image coordinates,
+ * pass to the projection as-is.
+ */
+ gimp_projectable_invalidate (GIMP_PROJECTABLE (group),
+ x, y, width, height);
+
+ gimp_group_layer_flush (group);
+ }
+
+ if (private->direct_update || private->pass_through)
+ {
+ /* the layer stack's update signal speaks in image coordinates,
+ * transform to layer coordinates when emitting our own update signal.
+ */
+ gimp_drawable_update (GIMP_DRAWABLE (group),
+ x - gimp_item_get_offset_x (GIMP_ITEM (group)),
+ y - gimp_item_get_offset_y (GIMP_ITEM (group)),
+ width, height);
+ }
+}
+
+static void
+gimp_group_layer_proj_update (GimpProjection *proj,
+ gboolean now,
+ gint x,
+ gint y,
+ gint width,
+ gint height,
+ GimpGroupLayer *group)
+{
+ GimpGroupLayerPrivate *private = GET_PRIVATE (group);
+
+#if 0
+ g_printerr ("%s (%s) %d, %d (%d, %d)\n",
+ G_STRFUNC, gimp_object_get_name (group),
+ x, y, width, height);
+#endif
+
+ if (! private->pass_through)
+ {
+ /* TODO: groups can currently have a gegl:transform op attached as a filter
+ * when using a transform tool, in which case the updated region needs
+ * undergo the same transformation. more generally, when a drawable has
+ * filters they may influence the area affected by drawable updates.
+ *
+ * this needs to be addressed much more generally at some point, but for now
+ * we just resort to updating the entire group when it has a filter (i.e.,
+ * when it's being used with a transform tool). we restrict this to groups,
+ * and don't do this more generally in gimp_drawable_update(), because this
+ * negatively impacts the performance of the warp tool, which does perform
+ * accurate drawable updates while using a filter.
+ */
+ if (gimp_drawable_has_filters (GIMP_DRAWABLE (group)))
+ {
+ width = -1;
+ height = -1;
+ }
+
+ /* the projection speaks in image coordinates, transform to layer
+ * coordinates when emitting our own update signal.
+ */
+ gimp_drawable_update (GIMP_DRAWABLE (group),
+ x - gimp_item_get_offset_x (GIMP_ITEM (group)),
+ y - gimp_item_get_offset_y (GIMP_ITEM (group)),
+ width, height);
+ }
+}
diff --git a/app/core/gimpgrouplayer.h b/app/core/gimpgrouplayer.h
new file mode 100644
index 0000000..5fef1e9
--- /dev/null
+++ b/app/core/gimpgrouplayer.h
@@ -0,0 +1,78 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * GimpGroupLayer
+ * Copyright (C) 2009 Michael Natterer <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_GROUP_LAYER_H__
+#define __GIMP_GROUP_LAYER_H__
+
+
+#include "core/gimplayer.h"
+
+
+#define GIMP_TYPE_GROUP_LAYER (gimp_group_layer_get_type ())
+#define GIMP_GROUP_LAYER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_GROUP_LAYER, GimpGroupLayer))
+#define GIMP_GROUP_LAYER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_GROUP_LAYER, GimpGroupLayerClass))
+#define GIMP_IS_GROUP_LAYER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_GROUP_LAYER))
+#define GIMP_IS_GROUP_LAYER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_GROUP_LAYER))
+#define GIMP_GROUP_LAYER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_GROUP_LAYER, GimpGroupLayerClass))
+
+
+typedef struct _GimpGroupLayerClass GimpGroupLayerClass;
+
+struct _GimpGroupLayer
+{
+ GimpLayer parent_instance;
+};
+
+struct _GimpGroupLayerClass
+{
+ GimpLayerClass parent_class;
+};
+
+
+GType gimp_group_layer_get_type (void) G_GNUC_CONST;
+
+GimpLayer * gimp_group_layer_new (GimpImage *image);
+
+GimpProjection * gimp_group_layer_get_projection (GimpGroupLayer *group);
+
+void gimp_group_layer_suspend_resize (GimpGroupLayer *group,
+ gboolean push_undo);
+void gimp_group_layer_resume_resize (GimpGroupLayer *group,
+ gboolean push_undo);
+
+void gimp_group_layer_suspend_mask (GimpGroupLayer *group,
+ gboolean push_undo);
+void gimp_group_layer_resume_mask (GimpGroupLayer *group,
+ gboolean push_undo);
+
+
+void _gimp_group_layer_set_suspended_mask (GimpGroupLayer *group,
+ GeglBuffer *buffer,
+ const GeglRectangle *bounds);
+GeglBuffer * _gimp_group_layer_get_suspended_mask (GimpGroupLayer *group,
+ GeglRectangle *bounds);
+
+void _gimp_group_layer_start_transform (GimpGroupLayer *group,
+ gboolean push_undo);
+void _gimp_group_layer_end_transform (GimpGroupLayer *group,
+ gboolean push_undo);
+
+
+#endif /* __GIMP_GROUP_LAYER_H__ */
diff --git a/app/core/gimpgrouplayerundo.c b/app/core/gimpgrouplayerundo.c
new file mode 100644
index 0000000..7ea70f4
--- /dev/null
+++ b/app/core/gimpgrouplayerundo.c
@@ -0,0 +1,253 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "core-types.h"
+
+#include "gimp-memsize.h"
+#include "gimpimage.h"
+#include "gimpgrouplayer.h"
+#include "gimpgrouplayerundo.h"
+
+
+static void gimp_group_layer_undo_constructed (GObject *object);
+
+static gint64 gimp_group_layer_undo_get_memsize (GimpObject *object,
+ gint64 *gui_size);
+
+static void gimp_group_layer_undo_pop (GimpUndo *undo,
+ GimpUndoMode undo_mode,
+ GimpUndoAccumulator *accum);
+static void gimp_group_layer_undo_free (GimpUndo *undo,
+ GimpUndoMode undo_mode);
+
+
+G_DEFINE_TYPE (GimpGroupLayerUndo, gimp_group_layer_undo, GIMP_TYPE_ITEM_UNDO)
+
+#define parent_class gimp_group_layer_undo_parent_class
+
+
+static void
+gimp_group_layer_undo_class_init (GimpGroupLayerUndoClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpObjectClass *gimp_object_class = GIMP_OBJECT_CLASS (klass);
+ GimpUndoClass *undo_class = GIMP_UNDO_CLASS (klass);
+
+ object_class->constructed = gimp_group_layer_undo_constructed;
+
+ gimp_object_class->get_memsize = gimp_group_layer_undo_get_memsize;
+
+ undo_class->pop = gimp_group_layer_undo_pop;
+ undo_class->free = gimp_group_layer_undo_free;
+}
+
+static void
+gimp_group_layer_undo_init (GimpGroupLayerUndo *undo)
+{
+}
+
+static void
+gimp_group_layer_undo_constructed (GObject *object)
+{
+ GimpGroupLayerUndo *group_layer_undo = GIMP_GROUP_LAYER_UNDO (object);
+ GimpGroupLayer *group;
+
+ G_OBJECT_CLASS (parent_class)->constructed (object);
+
+ g_return_if_fail (GIMP_IS_GROUP_LAYER (GIMP_ITEM_UNDO (object)->item));
+
+ group = GIMP_GROUP_LAYER (GIMP_ITEM_UNDO (object)->item);
+
+ switch (GIMP_UNDO (object)->undo_type)
+ {
+ case GIMP_UNDO_GROUP_LAYER_SUSPEND_RESIZE:
+ case GIMP_UNDO_GROUP_LAYER_RESUME_RESIZE:
+ case GIMP_UNDO_GROUP_LAYER_SUSPEND_MASK:
+ case GIMP_UNDO_GROUP_LAYER_START_TRANSFORM:
+ case GIMP_UNDO_GROUP_LAYER_END_TRANSFORM:
+ break;
+
+ case GIMP_UNDO_GROUP_LAYER_RESUME_MASK:
+ group_layer_undo->mask_buffer =
+ _gimp_group_layer_get_suspended_mask(group,
+ &group_layer_undo->mask_bounds);
+
+ if (group_layer_undo->mask_buffer)
+ g_object_ref (group_layer_undo->mask_buffer);
+ break;
+
+ case GIMP_UNDO_GROUP_LAYER_CONVERT:
+ group_layer_undo->prev_type = gimp_drawable_get_base_type (GIMP_DRAWABLE (group));
+ group_layer_undo->prev_precision = gimp_drawable_get_precision (GIMP_DRAWABLE (group));
+ group_layer_undo->prev_has_alpha = gimp_drawable_has_alpha (GIMP_DRAWABLE (group));
+ break;
+
+ default:
+ g_return_if_reached ();
+ }
+}
+
+static gint64
+gimp_group_layer_undo_get_memsize (GimpObject *object,
+ gint64 *gui_size)
+{
+ GimpGroupLayerUndo *group_layer_undo = GIMP_GROUP_LAYER_UNDO (object);
+ gint64 memsize = 0;
+
+ memsize += gimp_gegl_buffer_get_memsize (group_layer_undo->mask_buffer);
+
+ return memsize + GIMP_OBJECT_CLASS (parent_class)->get_memsize (object,
+ gui_size);
+}
+
+static void
+gimp_group_layer_undo_pop (GimpUndo *undo,
+ GimpUndoMode undo_mode,
+ GimpUndoAccumulator *accum)
+{
+ GimpGroupLayerUndo *group_layer_undo = GIMP_GROUP_LAYER_UNDO (undo);
+ GimpGroupLayer *group;
+
+ group = GIMP_GROUP_LAYER (GIMP_ITEM_UNDO (undo)->item);
+
+ GIMP_UNDO_CLASS (parent_class)->pop (undo, undo_mode, accum);
+
+ switch (undo->undo_type)
+ {
+ case GIMP_UNDO_GROUP_LAYER_SUSPEND_RESIZE:
+ case GIMP_UNDO_GROUP_LAYER_RESUME_RESIZE:
+ if ((undo_mode == GIMP_UNDO_MODE_UNDO &&
+ undo->undo_type == GIMP_UNDO_GROUP_LAYER_SUSPEND_RESIZE) ||
+ (undo_mode == GIMP_UNDO_MODE_REDO &&
+ undo->undo_type == GIMP_UNDO_GROUP_LAYER_RESUME_RESIZE))
+ {
+ /* resume group layer auto-resizing */
+
+ gimp_group_layer_resume_resize (group, FALSE);
+ }
+ else
+ {
+ /* suspend group layer auto-resizing */
+
+ gimp_group_layer_suspend_resize (group, FALSE);
+
+ if (undo->undo_type == GIMP_UNDO_GROUP_LAYER_RESUME_RESIZE &&
+ group_layer_undo->mask_buffer)
+ {
+ GimpLayerMask *mask = gimp_layer_get_mask (GIMP_LAYER (group));
+
+ gimp_drawable_set_buffer_full (GIMP_DRAWABLE (mask),
+ FALSE, NULL,
+ group_layer_undo->mask_buffer,
+ &group_layer_undo->mask_bounds,
+ TRUE);
+ }
+ }
+ break;
+
+ case GIMP_UNDO_GROUP_LAYER_SUSPEND_MASK:
+ case GIMP_UNDO_GROUP_LAYER_RESUME_MASK:
+ if ((undo_mode == GIMP_UNDO_MODE_UNDO &&
+ undo->undo_type == GIMP_UNDO_GROUP_LAYER_SUSPEND_MASK) ||
+ (undo_mode == GIMP_UNDO_MODE_REDO &&
+ undo->undo_type == GIMP_UNDO_GROUP_LAYER_RESUME_MASK))
+ {
+ /* resume group layer mask auto-resizing */
+
+ gimp_group_layer_resume_mask (group, FALSE);
+ }
+ else
+ {
+ /* suspend group layer mask auto-resizing */
+
+ gimp_group_layer_suspend_mask (group, FALSE);
+
+ if (undo->undo_type == GIMP_UNDO_GROUP_LAYER_RESUME_MASK &&
+ group_layer_undo->mask_buffer)
+ {
+ _gimp_group_layer_set_suspended_mask (
+ group,
+ group_layer_undo->mask_buffer,
+ &group_layer_undo->mask_bounds);
+ }
+ }
+ break;
+
+ case GIMP_UNDO_GROUP_LAYER_START_TRANSFORM:
+ case GIMP_UNDO_GROUP_LAYER_END_TRANSFORM:
+ if ((undo_mode == GIMP_UNDO_MODE_UNDO &&
+ undo->undo_type == GIMP_UNDO_GROUP_LAYER_START_TRANSFORM) ||
+ (undo_mode == GIMP_UNDO_MODE_REDO &&
+ undo->undo_type == GIMP_UNDO_GROUP_LAYER_END_TRANSFORM))
+ {
+ /* end group layer transform operation */
+
+ _gimp_group_layer_end_transform (group, FALSE);
+ }
+ else
+ {
+ /* start group layer transform operation */
+
+ _gimp_group_layer_start_transform (group, FALSE);
+ }
+ break;
+
+ case GIMP_UNDO_GROUP_LAYER_CONVERT:
+ {
+ GimpImageBaseType type;
+ GimpPrecision precision;
+ gboolean has_alpha;
+
+ type = gimp_drawable_get_base_type (GIMP_DRAWABLE (group));
+ precision = gimp_drawable_get_precision (GIMP_DRAWABLE (group));
+ has_alpha = gimp_drawable_has_alpha (GIMP_DRAWABLE (group));
+
+ gimp_drawable_convert_type (GIMP_DRAWABLE (group),
+ gimp_item_get_image (GIMP_ITEM (group)),
+ group_layer_undo->prev_type,
+ group_layer_undo->prev_precision,
+ group_layer_undo->prev_has_alpha,
+ NULL,
+ 0, 0,
+ FALSE, NULL);
+
+ group_layer_undo->prev_type = type;
+ group_layer_undo->prev_precision = precision;
+ group_layer_undo->prev_has_alpha = has_alpha;
+ }
+ break;
+
+ default:
+ g_return_if_reached ();
+ }
+}
+
+static void
+gimp_group_layer_undo_free (GimpUndo *undo,
+ GimpUndoMode undo_mode)
+{
+ GimpGroupLayerUndo *group_layer_undo = GIMP_GROUP_LAYER_UNDO (undo);
+
+ g_clear_object (&group_layer_undo->mask_buffer);
+
+ GIMP_UNDO_CLASS (parent_class)->free (undo, undo_mode);
+}
diff --git a/app/core/gimpgrouplayerundo.h b/app/core/gimpgrouplayerundo.h
new file mode 100644
index 0000000..14c7043
--- /dev/null
+++ b/app/core/gimpgrouplayerundo.h
@@ -0,0 +1,57 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_GROUP_LAYER_UNDO_H__
+#define __GIMP_GROUP_LAYER_UNDO_H__
+
+
+#include "gimpitemundo.h"
+
+
+#define GIMP_TYPE_GROUP_LAYER_UNDO (gimp_group_layer_undo_get_type ())
+#define GIMP_GROUP_LAYER_UNDO(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_GROUP_LAYER_UNDO, GimpGroupLayerUndo))
+#define GIMP_GROUP_LAYER_UNDO_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_GROUP_LAYER_UNDO, GimpGroupLayerUndoClass))
+#define GIMP_IS_GROUP_LAYER_UNDO(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_GROUP_LAYER_UNDO))
+#define GIMP_IS_GROUP_LAYER_UNDO_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_GROUP_LAYER_UNDO))
+#define GIMP_GROUP_LAYER_UNDO_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_GROUP_LAYER_UNDO, GimpGroupLayerUndoClass))
+
+
+typedef struct _GimpGroupLayerUndo GimpGroupLayerUndo;
+typedef struct _GimpGroupLayerUndoClass GimpGroupLayerUndoClass;
+
+struct _GimpGroupLayerUndo
+{
+ GimpItemUndo parent_instance;
+
+ GeglBuffer *mask_buffer;
+ GeglRectangle mask_bounds;
+
+ GimpImageBaseType prev_type;
+ GimpPrecision prev_precision;
+ gboolean prev_has_alpha;
+};
+
+struct _GimpGroupLayerUndoClass
+{
+ GimpItemUndoClass parent_class;
+};
+
+
+GType gimp_group_layer_undo_get_type (void) G_GNUC_CONST;
+
+
+#endif /* __GIMP_GROUP_LAYER_UNDO_H__ */
diff --git a/app/core/gimpguide.c b/app/core/gimpguide.c
new file mode 100644
index 0000000..1438958
--- /dev/null
+++ b/app/core/gimpguide.c
@@ -0,0 +1,242 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * GimpGuide
+ * Copyright (C) 2003 Henrik Brix Andersen <brix@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gio/gio.h>
+
+#include "libgimpbase/gimpbase.h"
+#include "libgimpconfig/gimpconfig.h"
+
+#include "core-types.h"
+
+#include "gimpguide.h"
+
+
+enum
+{
+ PROP_0,
+ PROP_ORIENTATION,
+ PROP_POSITION,
+ PROP_STYLE
+};
+
+
+struct _GimpGuidePrivate
+{
+ GimpOrientationType orientation;
+ gint position;
+
+ GimpGuideStyle style;
+};
+
+
+static void gimp_guide_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+static void gimp_guide_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+
+
+G_DEFINE_TYPE_WITH_PRIVATE (GimpGuide, gimp_guide, GIMP_TYPE_AUX_ITEM)
+
+
+static void
+gimp_guide_class_init (GimpGuideClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->get_property = gimp_guide_get_property;
+ object_class->set_property = gimp_guide_set_property;
+
+ GIMP_CONFIG_PROP_ENUM (object_class, PROP_ORIENTATION,
+ "orientation",
+ NULL, NULL,
+ GIMP_TYPE_ORIENTATION_TYPE,
+ GIMP_ORIENTATION_UNKNOWN,
+ 0);
+
+ GIMP_CONFIG_PROP_INT (object_class, PROP_POSITION,
+ "position",
+ NULL, NULL,
+ GIMP_GUIDE_POSITION_UNDEFINED,
+ GIMP_MAX_IMAGE_SIZE,
+ GIMP_GUIDE_POSITION_UNDEFINED,
+ 0);
+
+ GIMP_CONFIG_PROP_ENUM (object_class, PROP_STYLE,
+ "style",
+ NULL, NULL,
+ GIMP_TYPE_GUIDE_STYLE,
+ GIMP_GUIDE_STYLE_NONE,
+ 0);
+}
+
+static void
+gimp_guide_init (GimpGuide *guide)
+{
+ guide->priv = gimp_guide_get_instance_private (guide);
+}
+
+static void
+gimp_guide_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpGuide *guide = GIMP_GUIDE (object);
+
+ switch (property_id)
+ {
+ case PROP_ORIENTATION:
+ g_value_set_enum (value, guide->priv->orientation);
+ break;
+ case PROP_POSITION:
+ g_value_set_int (value, guide->priv->position);
+ break;
+ case PROP_STYLE:
+ g_value_set_enum (value, guide->priv->style);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_guide_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpGuide *guide = GIMP_GUIDE (object);
+
+ switch (property_id)
+ {
+ case PROP_ORIENTATION:
+ guide->priv->orientation = g_value_get_enum (value);
+ break;
+ case PROP_POSITION:
+ guide->priv->position = g_value_get_int (value);
+ break;
+ case PROP_STYLE:
+ guide->priv->style = g_value_get_enum (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+GimpGuide *
+gimp_guide_new (GimpOrientationType orientation,
+ guint32 guide_ID)
+{
+ return g_object_new (GIMP_TYPE_GUIDE,
+ "id", guide_ID,
+ "orientation", orientation,
+ "style", GIMP_GUIDE_STYLE_NORMAL,
+ NULL);
+}
+
+/**
+ * gimp_guide_custom_new:
+ * @orientation: the #GimpOrientationType
+ * @guide_ID: the unique guide ID
+ * @guide_style: the #GimpGuideStyle
+ *
+ * This function returns a new guide and will flag it as "custom".
+ * Custom guides are used for purpose "other" than the basic guides
+ * a user can create oneself, for instance as symmetry guides, to
+ * drive GEGL ops, etc.
+ * They are not saved in the XCF file. If an op, a symmetry or a plugin
+ * wishes to save its state, it has to do it internally.
+ * Moreover they don't follow guide snapping settings and never snap.
+ *
+ * Returns: the custom #GimpGuide.
+ **/
+GimpGuide *
+gimp_guide_custom_new (GimpOrientationType orientation,
+ guint32 guide_ID,
+ GimpGuideStyle guide_style)
+{
+ return g_object_new (GIMP_TYPE_GUIDE,
+ "id", guide_ID,
+ "orientation", orientation,
+ "style", guide_style,
+ NULL);
+}
+
+GimpOrientationType
+gimp_guide_get_orientation (GimpGuide *guide)
+{
+ g_return_val_if_fail (GIMP_IS_GUIDE (guide), GIMP_ORIENTATION_UNKNOWN);
+
+ return guide->priv->orientation;
+}
+
+void
+gimp_guide_set_orientation (GimpGuide *guide,
+ GimpOrientationType orientation)
+{
+ g_return_if_fail (GIMP_IS_GUIDE (guide));
+
+ guide->priv->orientation = orientation;
+
+ g_object_notify (G_OBJECT (guide), "orientation");
+}
+
+gint
+gimp_guide_get_position (GimpGuide *guide)
+{
+ g_return_val_if_fail (GIMP_IS_GUIDE (guide), GIMP_GUIDE_POSITION_UNDEFINED);
+
+ return guide->priv->position;
+}
+
+void
+gimp_guide_set_position (GimpGuide *guide,
+ gint position)
+{
+ g_return_if_fail (GIMP_IS_GUIDE (guide));
+
+ guide->priv->position = position;
+
+ g_object_notify (G_OBJECT (guide), "position");
+}
+
+GimpGuideStyle
+gimp_guide_get_style (GimpGuide *guide)
+{
+ g_return_val_if_fail (GIMP_IS_GUIDE (guide), GIMP_GUIDE_STYLE_NONE);
+
+ return guide->priv->style;
+}
+
+gboolean
+gimp_guide_is_custom (GimpGuide *guide)
+{
+ g_return_val_if_fail (GIMP_IS_GUIDE (guide), FALSE);
+
+ return guide->priv->style != GIMP_GUIDE_STYLE_NORMAL;
+}
diff --git a/app/core/gimpguide.h b/app/core/gimpguide.h
new file mode 100644
index 0000000..285e8eb
--- /dev/null
+++ b/app/core/gimpguide.h
@@ -0,0 +1,75 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * GimpGuide
+ * Copyright (C) 2003 Henrik Brix Andersen <brix@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_GUIDE_H__
+#define __GIMP_GUIDE_H__
+
+
+#include "gimpauxitem.h"
+
+
+#define GIMP_GUIDE_POSITION_UNDEFINED G_MININT
+
+
+#define GIMP_TYPE_GUIDE (gimp_guide_get_type ())
+#define GIMP_GUIDE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_GUIDE, GimpGuide))
+#define GIMP_GUIDE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_GUIDE, GimpGuideClass))
+#define GIMP_IS_GUIDE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_GUIDE))
+#define GIMP_IS_GUIDE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_GUIDE))
+#define GIMP_GUIDE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_GUIDE, GimpGuideClass))
+
+
+typedef struct _GimpGuidePrivate GimpGuidePrivate;
+typedef struct _GimpGuideClass GimpGuideClass;
+
+struct _GimpGuide
+{
+ GimpAuxItem parent_instance;
+
+ GimpGuidePrivate *priv;
+};
+
+struct _GimpGuideClass
+{
+ GimpAuxItemClass parent_class;
+};
+
+
+GType gimp_guide_get_type (void) G_GNUC_CONST;
+
+GimpGuide * gimp_guide_new (GimpOrientationType orientation,
+ guint32 guide_ID);
+GimpGuide * gimp_guide_custom_new (GimpOrientationType orientation,
+ guint32 guide_ID,
+ GimpGuideStyle guide_style);
+
+GimpOrientationType gimp_guide_get_orientation (GimpGuide *guide);
+void gimp_guide_set_orientation (GimpGuide *guide,
+ GimpOrientationType orientation);
+
+gint gimp_guide_get_position (GimpGuide *guide);
+void gimp_guide_set_position (GimpGuide *guide,
+ gint position);
+
+GimpGuideStyle gimp_guide_get_style (GimpGuide *guide);
+gboolean gimp_guide_is_custom (GimpGuide *guide);
+
+
+#endif /* __GIMP_GUIDE_H__ */
diff --git a/app/core/gimpguideundo.c b/app/core/gimpguideundo.c
new file mode 100644
index 0000000..c3e3de4
--- /dev/null
+++ b/app/core/gimpguideundo.c
@@ -0,0 +1,116 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "core-types.h"
+
+#include "gimpimage.h"
+#include "gimpimage-guides.h"
+#include "gimpguide.h"
+#include "gimpguideundo.h"
+
+
+static void gimp_guide_undo_constructed (GObject *object);
+
+static void gimp_guide_undo_pop (GimpUndo *undo,
+ GimpUndoMode undo_mode,
+ GimpUndoAccumulator *accum);
+
+
+G_DEFINE_TYPE (GimpGuideUndo, gimp_guide_undo,
+ GIMP_TYPE_AUX_ITEM_UNDO)
+
+#define parent_class gimp_guide_undo_parent_class
+
+
+static void
+gimp_guide_undo_class_init (GimpGuideUndoClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpUndoClass *undo_class = GIMP_UNDO_CLASS (klass);
+
+ object_class->constructed = gimp_guide_undo_constructed;
+
+ undo_class->pop = gimp_guide_undo_pop;
+}
+
+static void
+gimp_guide_undo_init (GimpGuideUndo *undo)
+{
+}
+
+static void
+gimp_guide_undo_constructed (GObject *object)
+{
+ GimpGuideUndo *guide_undo = GIMP_GUIDE_UNDO (object);
+ GimpGuide *guide;
+
+ G_OBJECT_CLASS (parent_class)->constructed (object);
+
+ guide = GIMP_GUIDE (GIMP_AUX_ITEM_UNDO (object)->aux_item);
+
+ gimp_assert (GIMP_IS_GUIDE (guide));
+
+ guide_undo->orientation = gimp_guide_get_orientation (guide);
+ guide_undo->position = gimp_guide_get_position (guide);
+}
+
+static void
+gimp_guide_undo_pop (GimpUndo *undo,
+ GimpUndoMode undo_mode,
+ GimpUndoAccumulator *accum)
+{
+ GimpGuideUndo *guide_undo = GIMP_GUIDE_UNDO (undo);
+ GimpGuide *guide;
+ GimpOrientationType orientation;
+ gint position;
+ gboolean moved = FALSE;
+
+ GIMP_UNDO_CLASS (parent_class)->pop (undo, undo_mode, accum);
+
+ guide = GIMP_GUIDE (GIMP_AUX_ITEM_UNDO (undo)->aux_item);
+
+ orientation = gimp_guide_get_orientation (guide);
+ position = gimp_guide_get_position (guide);
+
+ if (position == GIMP_GUIDE_POSITION_UNDEFINED)
+ {
+ gimp_image_add_guide (undo->image, guide, guide_undo->position);
+ }
+ else if (guide_undo->position == GIMP_GUIDE_POSITION_UNDEFINED)
+ {
+ gimp_image_remove_guide (undo->image, guide, FALSE);
+ }
+ else
+ {
+ gimp_guide_set_position (guide, guide_undo->position);
+
+ moved = TRUE;
+ }
+
+ gimp_guide_set_orientation (guide, guide_undo->orientation);
+
+ if (moved || guide_undo->orientation != orientation)
+ gimp_image_guide_moved (undo->image, guide);
+
+ guide_undo->position = position;
+ guide_undo->orientation = orientation;
+}
diff --git a/app/core/gimpguideundo.h b/app/core/gimpguideundo.h
new file mode 100644
index 0000000..da77ff3
--- /dev/null
+++ b/app/core/gimpguideundo.h
@@ -0,0 +1,53 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_GUIDE_UNDO_H__
+#define __GIMP_GUIDE_UNDO_H__
+
+
+#include "gimpauxitemundo.h"
+
+
+#define GIMP_TYPE_GUIDE_UNDO (gimp_guide_undo_get_type ())
+#define GIMP_GUIDE_UNDO(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_GUIDE_UNDO, GimpGuideUndo))
+#define GIMP_GUIDE_UNDO_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_GUIDE_UNDO, GimpGuideUndoClass))
+#define GIMP_IS_GUIDE_UNDO(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_GUIDE_UNDO))
+#define GIMP_IS_GUIDE_UNDO_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_GUIDE_UNDO))
+#define GIMP_GUIDE_UNDO_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_GUIDE_UNDO, GimpGuideUndoClass))
+
+
+typedef struct _GimpGuideUndo GimpGuideUndo;
+typedef struct _GimpGuideUndoClass GimpGuideUndoClass;
+
+struct _GimpGuideUndo
+{
+ GimpAuxItemUndo parent_instance;
+
+ GimpOrientationType orientation;
+ gint position;
+};
+
+struct _GimpGuideUndoClass
+{
+ GimpAuxItemUndoClass parent_class;
+};
+
+
+GType gimp_guide_undo_get_type (void) G_GNUC_CONST;
+
+
+#endif /* __GIMP_GUIDE_UNDO_H__ */
diff --git a/app/core/gimphistogram.c b/app/core/gimphistogram.c
new file mode 100644
index 0000000..571679b
--- /dev/null
+++ b/app/core/gimphistogram.c
@@ -0,0 +1,1261 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimphistogram module Copyright (C) 1999 Jay Cox <jaycox@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+#include <cairo.h>
+#include <gegl.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+#include "libgimpmath/gimpmath.h"
+#include "libgimpcolor/gimpcolor.h"
+
+#include "core-types.h"
+
+#include "gegl/gimp-babl.h"
+#include "gegl/gimp-gegl-loops.h"
+
+#include "gimp-atomic.h"
+#include "gimp-parallel.h"
+#include "gimpasync.h"
+#include "gimphistogram.h"
+#include "gimpwaitable.h"
+
+
+#define MAX_N_COMPONENTS 4
+#define N_DERIVED_CHANNELS 2
+
+#define PIXELS_PER_THREAD \
+ (/* each thread costs as much as */ 64.0 * 64.0 /* pixels */)
+
+
+enum
+{
+ PROP_0,
+ PROP_N_COMPONENTS,
+ PROP_N_BINS,
+ PROP_VALUES
+};
+
+struct _GimpHistogramPrivate
+{
+ gboolean linear;
+ gint n_channels;
+ gint n_bins;
+ gdouble *values;
+ GimpAsync *calculate_async;
+};
+
+typedef struct
+{
+ /* input */
+ GimpHistogram *histogram;
+ GeglBuffer *buffer;
+ GeglRectangle buffer_rect;
+ GeglBuffer *mask;
+ GeglRectangle mask_rect;
+
+ /* output */
+ gint n_components;
+ gint n_bins;
+ gdouble *values;
+} CalculateContext;
+
+typedef struct
+{
+ GimpAsync *async;
+ CalculateContext *context;
+
+ const Babl *format;
+ GSList *values_list;
+} CalculateData;
+
+
+/* local function prototypes */
+
+static void gimp_histogram_finalize (GObject *object);
+static void gimp_histogram_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_histogram_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static gint64 gimp_histogram_get_memsize (GimpObject *object,
+ gint64 *gui_size);
+
+static gboolean gimp_histogram_map_channel (GimpHistogram *histogram,
+ GimpHistogramChannel *channel);
+
+static void gimp_histogram_set_values (GimpHistogram *histogram,
+ gint n_components,
+ gint n_bins,
+ gdouble *values);
+
+static void gimp_histogram_calculate_internal (GimpAsync *async,
+ CalculateContext *context);
+static void gimp_histogram_calculate_area (const GeglRectangle *area,
+ CalculateData *data);
+static void gimp_histogram_calculate_async_callback (GimpAsync *async,
+ CalculateContext *context);
+
+
+G_DEFINE_TYPE_WITH_PRIVATE (GimpHistogram, gimp_histogram, GIMP_TYPE_OBJECT)
+
+#define parent_class gimp_histogram_parent_class
+
+
+static void
+gimp_histogram_class_init (GimpHistogramClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpObjectClass *gimp_object_class = GIMP_OBJECT_CLASS (klass);
+
+ object_class->finalize = gimp_histogram_finalize;
+ object_class->set_property = gimp_histogram_set_property;
+ object_class->get_property = gimp_histogram_get_property;
+
+ gimp_object_class->get_memsize = gimp_histogram_get_memsize;
+
+ g_object_class_install_property (object_class, PROP_N_COMPONENTS,
+ g_param_spec_int ("n-components", NULL, NULL,
+ 0, MAX_N_COMPONENTS, 0,
+ GIMP_PARAM_READABLE));
+
+ g_object_class_install_property (object_class, PROP_N_BINS,
+ g_param_spec_int ("n-bins", NULL, NULL,
+ 256, 1024, 1024,
+ GIMP_PARAM_READABLE));
+
+ /* this is just for notifications */
+ g_object_class_install_property (object_class, PROP_VALUES,
+ g_param_spec_boolean ("values", NULL, NULL,
+ FALSE,
+ G_PARAM_READABLE));
+}
+
+static void
+gimp_histogram_init (GimpHistogram *histogram)
+{
+ histogram->priv = gimp_histogram_get_instance_private (histogram);
+}
+
+static void
+gimp_histogram_finalize (GObject *object)
+{
+ GimpHistogram *histogram = GIMP_HISTOGRAM (object);
+
+ gimp_histogram_clear_values (histogram, 0);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gimp_histogram_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id)
+ {
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_histogram_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpHistogram *histogram = GIMP_HISTOGRAM (object);
+
+ switch (property_id)
+ {
+ case PROP_N_COMPONENTS:
+ g_value_set_int (value, gimp_histogram_n_components (histogram));
+ break;
+
+ case PROP_N_BINS:
+ g_value_set_int (value, histogram->priv->n_bins);
+ break;
+
+ case PROP_VALUES:
+ /* return a silly boolean */
+ g_value_set_boolean (value, histogram->priv->values != NULL);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static gint64
+gimp_histogram_get_memsize (GimpObject *object,
+ gint64 *gui_size)
+{
+ GimpHistogram *histogram = GIMP_HISTOGRAM (object);
+ gint64 memsize = 0;
+
+ if (histogram->priv->values)
+ memsize += (histogram->priv->n_channels *
+ histogram->priv->n_bins * sizeof (gdouble));
+
+ return memsize + GIMP_OBJECT_CLASS (parent_class)->get_memsize (object,
+ gui_size);
+}
+
+/* public functions */
+
+GimpHistogram *
+gimp_histogram_new (gboolean linear)
+{
+ GimpHistogram *histogram = g_object_new (GIMP_TYPE_HISTOGRAM, NULL);
+
+ histogram->priv->linear = linear;
+
+ return histogram;
+}
+
+/**
+ * gimp_histogram_duplicate:
+ * @histogram: a %GimpHistogram
+ *
+ * Creates a duplicate of @histogram. The duplicate has a reference
+ * count of 1 and contains the values from @histogram.
+ *
+ * Return value: a newly allocated %GimpHistogram
+ **/
+GimpHistogram *
+gimp_histogram_duplicate (GimpHistogram *histogram)
+{
+ GimpHistogram *dup;
+
+ g_return_val_if_fail (GIMP_IS_HISTOGRAM (histogram), NULL);
+
+ if (histogram->priv->calculate_async)
+ gimp_waitable_wait (GIMP_WAITABLE (histogram->priv->calculate_async));
+
+ dup = gimp_histogram_new (histogram->priv->linear);
+
+ dup->priv->n_channels = histogram->priv->n_channels;
+ dup->priv->n_bins = histogram->priv->n_bins;
+ dup->priv->values = g_memdup (histogram->priv->values,
+ sizeof (gdouble) *
+ dup->priv->n_channels *
+ dup->priv->n_bins);
+
+ return dup;
+}
+
+void
+gimp_histogram_calculate (GimpHistogram *histogram,
+ GeglBuffer *buffer,
+ const GeglRectangle *buffer_rect,
+ GeglBuffer *mask,
+ const GeglRectangle *mask_rect)
+{
+ CalculateContext context = {};
+
+ g_return_if_fail (GIMP_IS_HISTOGRAM (histogram));
+ g_return_if_fail (GEGL_IS_BUFFER (buffer));
+ g_return_if_fail (buffer_rect != NULL);
+
+ if (histogram->priv->calculate_async)
+ gimp_async_cancel_and_wait (histogram->priv->calculate_async);
+
+ context.histogram = histogram;
+ context.buffer = buffer;
+ context.buffer_rect = *buffer_rect;
+
+ if (mask)
+ {
+ context.mask = mask;
+
+ if (mask_rect)
+ context.mask_rect = *mask_rect;
+ else
+ context.mask_rect = *gegl_buffer_get_extent (mask);
+ }
+
+ gimp_histogram_calculate_internal (NULL, &context);
+
+ gimp_histogram_set_values (histogram,
+ context.n_components, context.n_bins,
+ context.values);
+}
+
+GimpAsync *
+gimp_histogram_calculate_async (GimpHistogram *histogram,
+ GeglBuffer *buffer,
+ const GeglRectangle *buffer_rect,
+ GeglBuffer *mask,
+ const GeglRectangle *mask_rect)
+{
+ CalculateContext *context;
+ GeglRectangle rect;
+
+ g_return_val_if_fail (GIMP_IS_HISTOGRAM (histogram), NULL);
+ g_return_val_if_fail (GEGL_IS_BUFFER (buffer), NULL);
+ g_return_val_if_fail (buffer_rect != NULL, NULL);
+
+ if (histogram->priv->calculate_async)
+ gimp_async_cancel_and_wait (histogram->priv->calculate_async);
+
+ gegl_rectangle_align_to_buffer (&rect, buffer_rect, buffer,
+ GEGL_RECTANGLE_ALIGNMENT_SUPERSET);
+
+ context = g_slice_new0 (CalculateContext);
+
+ context->histogram = histogram;
+ context->buffer = gegl_buffer_new (&rect,
+ gegl_buffer_get_format (buffer));
+ context->buffer_rect = *buffer_rect;
+
+ gimp_gegl_buffer_copy (buffer, &rect, GEGL_ABYSS_NONE,
+ context->buffer, NULL);
+
+ if (mask)
+ {
+ if (mask_rect)
+ context->mask_rect = *mask_rect;
+ else
+ context->mask_rect = *gegl_buffer_get_extent (mask);
+
+ gegl_rectangle_align_to_buffer (&rect, &context->mask_rect, mask,
+ GEGL_RECTANGLE_ALIGNMENT_SUPERSET);
+
+ context->mask = gegl_buffer_new (&rect, gegl_buffer_get_format (mask));
+
+ gimp_gegl_buffer_copy (mask, &rect, GEGL_ABYSS_NONE,
+ context->mask, NULL);
+ }
+
+ histogram->priv->calculate_async = gimp_parallel_run_async (
+ (GimpRunAsyncFunc) gimp_histogram_calculate_internal,
+ context);
+
+ gimp_async_add_callback (
+ histogram->priv->calculate_async,
+ (GimpAsyncCallback) gimp_histogram_calculate_async_callback,
+ context);
+
+ return histogram->priv->calculate_async;
+}
+
+void
+gimp_histogram_clear_values (GimpHistogram *histogram,
+ gint n_components)
+{
+ g_return_if_fail (GIMP_IS_HISTOGRAM (histogram));
+
+ if (histogram->priv->calculate_async)
+ gimp_async_cancel_and_wait (histogram->priv->calculate_async);
+
+ gimp_histogram_set_values (histogram, n_components, 0, NULL);
+}
+
+
+#define HISTOGRAM_VALUE(c,i) (priv->values[(c) * priv->n_bins + (i)])
+
+
+gdouble
+gimp_histogram_get_maximum (GimpHistogram *histogram,
+ GimpHistogramChannel channel)
+{
+ GimpHistogramPrivate *priv;
+ gdouble max = 0.0;
+ gint x;
+
+ g_return_val_if_fail (GIMP_IS_HISTOGRAM (histogram), 0.0);
+
+ priv = histogram->priv;
+
+ if (! priv->values ||
+ ! gimp_histogram_map_channel (histogram, &channel))
+ {
+ return 0.0;
+ }
+
+ if (channel == GIMP_HISTOGRAM_RGB)
+ {
+ for (x = 0; x < priv->n_bins; x++)
+ {
+ max = MAX (max, HISTOGRAM_VALUE (GIMP_HISTOGRAM_RED, x));
+ max = MAX (max, HISTOGRAM_VALUE (GIMP_HISTOGRAM_GREEN, x));
+ max = MAX (max, HISTOGRAM_VALUE (GIMP_HISTOGRAM_BLUE, x));
+ }
+ }
+ else
+ {
+ for (x = 0; x < priv->n_bins; x++)
+ {
+ max = MAX (max, HISTOGRAM_VALUE (channel, x));
+ }
+ }
+
+ return max;
+}
+
+gdouble
+gimp_histogram_get_value (GimpHistogram *histogram,
+ GimpHistogramChannel channel,
+ gint bin)
+{
+ GimpHistogramPrivate *priv;
+
+ g_return_val_if_fail (GIMP_IS_HISTOGRAM (histogram), 0.0);
+
+ priv = histogram->priv;
+
+ if (! priv->values ||
+ (bin < 0 || bin >= priv->n_bins) ||
+ ! gimp_histogram_map_channel (histogram, &channel))
+ {
+ return 0.0;
+ }
+
+ if (channel == GIMP_HISTOGRAM_RGB)
+ {
+ gdouble min = HISTOGRAM_VALUE (GIMP_HISTOGRAM_RED, bin);
+
+ min = MIN (min, HISTOGRAM_VALUE (GIMP_HISTOGRAM_GREEN, bin));
+
+ return MIN (min, HISTOGRAM_VALUE (GIMP_HISTOGRAM_BLUE, bin));
+ }
+ else
+ {
+ return HISTOGRAM_VALUE (channel, bin);
+ }
+}
+
+gdouble
+gimp_histogram_get_component (GimpHistogram *histogram,
+ gint component,
+ gint bin)
+{
+ g_return_val_if_fail (GIMP_IS_HISTOGRAM (histogram), 0.0);
+
+ if (gimp_histogram_n_components (histogram) > 2)
+ component++;
+
+ return gimp_histogram_get_value (histogram, component, bin);
+}
+
+gint
+gimp_histogram_n_components (GimpHistogram *histogram)
+{
+ g_return_val_if_fail (GIMP_IS_HISTOGRAM (histogram), 0);
+
+ if (histogram->priv->n_channels > 0)
+ return histogram->priv->n_channels - N_DERIVED_CHANNELS;
+ else
+ return 0;
+}
+
+gint
+gimp_histogram_n_bins (GimpHistogram *histogram)
+{
+ g_return_val_if_fail (GIMP_IS_HISTOGRAM (histogram), 0);
+
+ return histogram->priv->n_bins;
+}
+
+gboolean
+gimp_histogram_has_channel (GimpHistogram *histogram,
+ GimpHistogramChannel channel)
+{
+ g_return_val_if_fail (GIMP_IS_HISTOGRAM (histogram), FALSE);
+
+ switch (channel)
+ {
+ case GIMP_HISTOGRAM_VALUE:
+ return TRUE;
+
+ case GIMP_HISTOGRAM_RED:
+ case GIMP_HISTOGRAM_GREEN:
+ case GIMP_HISTOGRAM_BLUE:
+ case GIMP_HISTOGRAM_LUMINANCE:
+ case GIMP_HISTOGRAM_RGB:
+ return gimp_histogram_n_components (histogram) >= 3;
+
+ case GIMP_HISTOGRAM_ALPHA:
+ return gimp_histogram_n_components (histogram) == 2 ||
+ gimp_histogram_n_components (histogram) == 4;
+ }
+
+ g_return_val_if_reached (FALSE);
+}
+
+gdouble
+gimp_histogram_get_count (GimpHistogram *histogram,
+ GimpHistogramChannel channel,
+ gint start,
+ gint end)
+{
+ GimpHistogramPrivate *priv;
+ gint i;
+ gdouble count = 0.0;
+
+ g_return_val_if_fail (GIMP_IS_HISTOGRAM (histogram), 0.0);
+
+ priv = histogram->priv;
+
+ if (! priv->values ||
+ start > end ||
+ ! gimp_histogram_map_channel (histogram, &channel))
+ {
+ return 0.0;
+ }
+
+ if (channel == GIMP_HISTOGRAM_RGB)
+ return (gimp_histogram_get_count (histogram,
+ GIMP_HISTOGRAM_RED, start, end) +
+ gimp_histogram_get_count (histogram,
+ GIMP_HISTOGRAM_GREEN, start, end) +
+ gimp_histogram_get_count (histogram,
+ GIMP_HISTOGRAM_BLUE, start, end));
+
+ start = CLAMP (start, 0, priv->n_bins - 1);
+ end = CLAMP (end, 0, priv->n_bins - 1);
+
+ for (i = start; i <= end; i++)
+ count += HISTOGRAM_VALUE (channel, i);
+
+ return count;
+}
+
+gdouble
+gimp_histogram_get_mean (GimpHistogram *histogram,
+ GimpHistogramChannel channel,
+ gint start,
+ gint end)
+{
+ GimpHistogramPrivate *priv;
+ gint i;
+ gdouble mean = 0.0;
+ gdouble count;
+
+ g_return_val_if_fail (GIMP_IS_HISTOGRAM (histogram), 0.0);
+
+ priv = histogram->priv;
+
+ if (! priv->values ||
+ start > end ||
+ ! gimp_histogram_map_channel (histogram, &channel))
+ {
+ return 0.0;
+ }
+
+ start = CLAMP (start, 0, priv->n_bins - 1);
+ end = CLAMP (end, 0, priv->n_bins - 1);
+
+ if (channel == GIMP_HISTOGRAM_RGB)
+ {
+ for (i = start; i <= end; i++)
+ {
+ gdouble factor = (gdouble) i / (gdouble) (priv->n_bins - 1);
+
+ mean += (factor * HISTOGRAM_VALUE (GIMP_HISTOGRAM_RED, i) +
+ factor * HISTOGRAM_VALUE (GIMP_HISTOGRAM_GREEN, i) +
+ factor * HISTOGRAM_VALUE (GIMP_HISTOGRAM_BLUE, i));
+ }
+ }
+ else
+ {
+ for (i = start; i <= end; i++)
+ {
+ gdouble factor = (gdouble) i / (gdouble) (priv->n_bins - 1);
+
+ mean += factor * HISTOGRAM_VALUE (channel, i);
+ }
+ }
+
+ count = gimp_histogram_get_count (histogram, channel, start, end);
+
+ if (count > 0.0)
+ return mean / count;
+
+ return mean;
+}
+
+gdouble
+gimp_histogram_get_median (GimpHistogram *histogram,
+ GimpHistogramChannel channel,
+ gint start,
+ gint end)
+{
+ GimpHistogramPrivate *priv;
+ gint i;
+ gdouble sum = 0.0;
+ gdouble count;
+
+ g_return_val_if_fail (GIMP_IS_HISTOGRAM (histogram), -1.0);
+
+ priv = histogram->priv;
+
+ if (! priv->values ||
+ start > end ||
+ ! gimp_histogram_map_channel (histogram, &channel))
+ {
+ return 0.0;
+ }
+
+ start = CLAMP (start, 0, priv->n_bins - 1);
+ end = CLAMP (end, 0, priv->n_bins - 1);
+
+ count = gimp_histogram_get_count (histogram, channel, start, end);
+
+ if (channel == GIMP_HISTOGRAM_RGB)
+ {
+ for (i = start; i <= end; i++)
+ {
+ sum += (HISTOGRAM_VALUE (GIMP_HISTOGRAM_RED, i) +
+ HISTOGRAM_VALUE (GIMP_HISTOGRAM_GREEN, i) +
+ HISTOGRAM_VALUE (GIMP_HISTOGRAM_BLUE, i));
+
+ if (sum * 2 > count)
+ return ((gdouble) i / (gdouble) (priv->n_bins - 1));
+ }
+ }
+ else
+ {
+ for (i = start; i <= end; i++)
+ {
+ sum += HISTOGRAM_VALUE (channel, i);
+
+ if (sum * 2 > count)
+ return ((gdouble) i / (gdouble) (priv->n_bins - 1));
+ }
+ }
+
+ return -1.0;
+}
+
+/*
+ * adapted from GNU ocrad 0.14 : page_image_io.cc : otsu_th
+ *
+ * N. Otsu, "A threshold selection method from gray-level histograms,"
+ * IEEE Trans. Systems, Man, and Cybernetics, vol. 9, no. 1, pp. 62-66, 1979.
+ */
+gdouble
+gimp_histogram_get_threshold (GimpHistogram *histogram,
+ GimpHistogramChannel channel,
+ gint start,
+ gint end)
+{
+ GimpHistogramPrivate *priv;
+ gint i;
+ gint maxval;
+ gdouble *hist = NULL;
+ gdouble *chist = NULL;
+ gdouble *cmom = NULL;
+ gdouble hist_max = 0.0;
+ gdouble chist_max = 0.0;
+ gdouble cmom_max = 0.0;
+ gdouble bvar_max = 0.0;
+ gint threshold = 127;
+
+ g_return_val_if_fail (GIMP_IS_HISTOGRAM (histogram), -1);
+
+ priv = histogram->priv;
+
+ if (! priv->values ||
+ start > end ||
+ ! gimp_histogram_map_channel (histogram, &channel))
+ {
+ return 0;
+ }
+
+ start = CLAMP (start, 0, priv->n_bins - 1);
+ end = CLAMP (end, 0, priv->n_bins - 1);
+
+ maxval = end - start;
+
+ hist = g_newa (gdouble, maxval + 1);
+ chist = g_newa (gdouble, maxval + 1);
+ cmom = g_newa (gdouble, maxval + 1);
+
+ if (channel == GIMP_HISTOGRAM_RGB)
+ {
+ for (i = start; i <= end; i++)
+ hist[i - start] = (HISTOGRAM_VALUE (GIMP_HISTOGRAM_RED, i) +
+ HISTOGRAM_VALUE (GIMP_HISTOGRAM_GREEN, i) +
+ HISTOGRAM_VALUE (GIMP_HISTOGRAM_BLUE, i));
+ }
+ else
+ {
+ for (i = start; i <= end; i++)
+ hist[i - start] = HISTOGRAM_VALUE (channel, i);
+ }
+
+ hist_max = hist[0];
+ chist[0] = hist[0];
+ cmom[0] = 0;
+
+ for (i = 1; i <= maxval; i++)
+ {
+ if (hist[i] > hist_max)
+ hist_max = hist[i];
+
+ chist[i] = chist[i-1] + hist[i];
+ cmom[i] = cmom[i-1] + i * hist[i];
+ }
+
+ chist_max = chist[maxval];
+ cmom_max = cmom[maxval];
+ bvar_max = 0;
+
+ for (i = 0; i < maxval; ++i)
+ {
+ if (chist[i] > 0 && chist[i] < chist_max)
+ {
+ gdouble bvar;
+
+ bvar = (gdouble) cmom[i] / chist[i];
+ bvar -= (cmom_max - cmom[i]) / (chist_max - chist[i]);
+ bvar *= bvar;
+ bvar *= chist[i];
+ bvar *= chist_max - chist[i];
+
+ if (bvar > bvar_max)
+ {
+ bvar_max = bvar;
+ threshold = start + i;
+ }
+ }
+ }
+
+ return threshold;
+}
+
+gdouble
+gimp_histogram_get_std_dev (GimpHistogram *histogram,
+ GimpHistogramChannel channel,
+ gint start,
+ gint end)
+{
+ GimpHistogramPrivate *priv;
+ gint i;
+ gdouble dev = 0.0;
+ gdouble count;
+ gdouble mean;
+
+ g_return_val_if_fail (GIMP_IS_HISTOGRAM (histogram), 0.0);
+
+ priv = histogram->priv;
+
+ if (! priv->values ||
+ start > end ||
+ ! gimp_histogram_map_channel (histogram, &channel))
+ {
+ return 0.0;
+ }
+
+ mean = gimp_histogram_get_mean (histogram, channel, start, end);
+ count = gimp_histogram_get_count (histogram, channel, start, end);
+
+ if (count == 0.0)
+ count = 1.0;
+
+ for (i = start; i <= end; i++)
+ {
+ gdouble value;
+
+ if (channel == GIMP_HISTOGRAM_RGB)
+ {
+ value = (HISTOGRAM_VALUE (GIMP_HISTOGRAM_RED, i) +
+ HISTOGRAM_VALUE (GIMP_HISTOGRAM_GREEN, i) +
+ HISTOGRAM_VALUE (GIMP_HISTOGRAM_BLUE, i));
+ }
+ else
+ {
+ value = gimp_histogram_get_value (histogram, channel, i);
+ }
+
+ dev += value * SQR (((gdouble) i / (gdouble) (priv->n_bins - 1)) - mean);
+ }
+
+ return sqrt (dev / count);
+}
+
+
+/* private functions */
+
+static gboolean
+gimp_histogram_map_channel (GimpHistogram *histogram,
+ GimpHistogramChannel *channel)
+{
+ GimpHistogramPrivate *priv = histogram->priv;
+
+ if (*channel == GIMP_HISTOGRAM_RGB)
+ return gimp_histogram_n_components (histogram) >= 3;
+
+ switch (*channel)
+ {
+ case GIMP_HISTOGRAM_ALPHA:
+ if (gimp_histogram_n_components (histogram) == 2)
+ *channel = 1;
+ break;
+
+ case GIMP_HISTOGRAM_LUMINANCE:
+ *channel = gimp_histogram_n_components (histogram) + 1;
+ break;
+
+ default:
+ break;
+ }
+
+ return *channel < priv->n_channels;
+}
+
+static void
+gimp_histogram_set_values (GimpHistogram *histogram,
+ gint n_components,
+ gint n_bins,
+ gdouble *values)
+{
+ GimpHistogramPrivate *priv = histogram->priv;
+ gint n_channels = n_components;
+ gboolean notify_n_components = FALSE;
+ gboolean notify_n_bins = FALSE;
+
+ if (n_channels > 0)
+ n_channels += N_DERIVED_CHANNELS;
+
+ if (n_channels != priv->n_channels)
+ {
+ priv->n_channels = n_channels;
+
+ notify_n_components = TRUE;
+ }
+
+ if (n_bins != priv->n_bins)
+ {
+ priv->n_bins = n_bins;
+
+ notify_n_bins = TRUE;
+ }
+
+ if (values != priv->values)
+ {
+ if (priv->values)
+ g_free (priv->values);
+
+ priv->values = values;
+ }
+
+ if (notify_n_components)
+ g_object_notify (G_OBJECT (histogram), "n-components");
+
+ if (notify_n_bins)
+ g_object_notify (G_OBJECT (histogram), "n-bins");
+
+ g_object_notify (G_OBJECT (histogram), "values");
+}
+
+static void
+gimp_histogram_calculate_internal (GimpAsync *async,
+ CalculateContext *context)
+{
+ CalculateData data;
+ GimpHistogramPrivate *priv;
+ const Babl *format;
+
+ priv = context->histogram->priv;
+
+ format = gegl_buffer_get_format (context->buffer);
+
+ if (babl_format_get_type (format, 0) == babl_type ("u8"))
+ context->n_bins = 256;
+ else
+ context->n_bins = 1024;
+
+ if (babl_format_is_palette (format))
+ {
+ if (babl_format_has_alpha (format))
+ {
+ if (priv->linear)
+ format = babl_format ("RGB float");
+ else
+ format = babl_format ("R'G'B' float");
+ }
+ else
+ {
+ if (priv->linear)
+ format = babl_format ("RGBA float");
+ else
+ format = babl_format ("R'G'B'A float");
+ }
+ }
+ else
+ {
+ const Babl *model = babl_format_get_model (format);
+
+ if (model == babl_model ("Y") ||
+ model == babl_model ("Y'"))
+ {
+ if (priv->linear)
+ format = babl_format ("Y float");
+ else
+ format = babl_format ("Y' float");
+ }
+ else if (model == babl_model ("YA") ||
+ model == babl_model ("Y'A"))
+ {
+ if (priv->linear)
+ format = babl_format ("YA float");
+ else
+ format = babl_format ("Y'A float");
+ }
+ else if (model == babl_model ("RGB") ||
+ model == babl_model ("R'G'B'"))
+ {
+ if (priv->linear)
+ format = babl_format ("RGB float");
+ else
+ format = babl_format ("R'G'B' float");
+ }
+ else if (model == babl_model ("RGBA") ||
+ model == babl_model ("R'G'B'A"))
+ {
+ if (priv->linear)
+ format = babl_format ("RGBA float");
+ else
+ format = babl_format ("R'G'B'A float");
+ }
+ else
+ {
+ if (async)
+ gimp_async_abort (async);
+
+ g_return_if_reached ();
+ }
+ }
+
+ context->n_components = babl_format_get_n_components (format);
+
+ data.async = async;
+ data.context = context;
+ data.format = format;
+ data.values_list = NULL;
+
+ gegl_parallel_distribute_area (
+ &context->buffer_rect, PIXELS_PER_THREAD, GEGL_SPLIT_STRATEGY_AUTO,
+ (GeglParallelDistributeAreaFunc) gimp_histogram_calculate_area,
+ &data);
+
+ if (! async || ! gimp_async_is_canceled (async))
+ {
+ gdouble *total_values = NULL;
+ gint n_values = (context->n_components + N_DERIVED_CHANNELS) *
+ context->n_bins;
+ GSList *iter;
+
+ for (iter = data.values_list; iter; iter = g_slist_next (iter))
+ {
+ gdouble *values = iter->data;
+
+ if (! total_values)
+ {
+ total_values = values;
+ }
+ else
+ {
+ gint i;
+
+ for (i = 0; i < n_values; i++)
+ total_values[i] += values[i];
+
+ g_free (values);
+ }
+ }
+
+ g_slist_free (data.values_list);
+
+ context->values = total_values;
+
+ if (async)
+ gimp_async_finish (async, NULL);
+ }
+ else
+ {
+ g_slist_free_full (data.values_list, g_free);
+
+ if (async)
+ gimp_async_abort (async);
+ }
+}
+
+static void
+gimp_histogram_calculate_area (const GeglRectangle *area,
+ CalculateData *data)
+{
+ GimpAsync *async;
+ CalculateContext *context;
+ GeglBufferIterator *iter;
+ gdouble *values;
+ gint n_components;
+ gint n_bins;
+ gfloat n_bins_1f;
+ gfloat temp;
+
+ async = data->async;
+ context = data->context;
+
+ n_bins = context->n_bins;
+ n_components = context->n_components;
+
+ values = g_new0 (gdouble, (n_components + N_DERIVED_CHANNELS) * n_bins);
+ gimp_atomic_slist_push_head (&data->values_list, values);
+
+ iter = gegl_buffer_iterator_new (context->buffer, area, 0,
+ data->format,
+ GEGL_ACCESS_READ, GEGL_ABYSS_NONE, 2);
+
+ if (context->mask)
+ {
+ GeglRectangle mask_area = *area;
+
+ mask_area.x += context->mask_rect.x - context->buffer_rect.x;
+ mask_area.y += context->mask_rect.y - context->buffer_rect.y;
+
+ gegl_buffer_iterator_add (iter, context->mask, &mask_area, 0,
+ babl_format ("Y float"),
+ GEGL_ACCESS_READ, GEGL_ABYSS_NONE);
+ }
+
+ n_bins_1f = n_bins - 1;
+
+#define VALUE(c,i) (*(temp = (i) * n_bins_1f, \
+ &values[(c) * n_bins + \
+ SIGNED_ROUND (SAFE_CLAMP (temp, \
+ 0.0f, \
+ n_bins_1f))]))
+
+#define CHECK_CANCELED(length) \
+ G_STMT_START \
+ { \
+ if ((length) % 128 == 0 && async && gimp_async_is_canceled (async)) \
+ { \
+ gegl_buffer_iterator_stop (iter); \
+ \
+ return; \
+ } \
+ } \
+ G_STMT_END
+
+ while (gegl_buffer_iterator_next (iter))
+ {
+ const gfloat *data = iter->items[0].data;
+ gint length = iter->length;
+ gfloat max;
+ gfloat luminance;
+
+ CHECK_CANCELED (0);
+
+ if (context->mask)
+ {
+ const gfloat *mask_data = iter->items[1].data;
+
+ switch (n_components)
+ {
+ case 1:
+ while (length--)
+ {
+ const gdouble masked = *mask_data;
+
+ VALUE (0, data[0]) += masked;
+
+ data += n_components;
+ mask_data += 1;
+
+ CHECK_CANCELED (length);
+ }
+ break;
+
+ case 2:
+ while (length--)
+ {
+ const gdouble masked = *mask_data;
+ const gdouble weight = data[1];
+
+ VALUE (0, data[0]) += weight * masked;
+ VALUE (1, data[1]) += masked;
+
+ data += n_components;
+ mask_data += 1;
+
+ CHECK_CANCELED (length);
+ }
+ break;
+
+ case 3: /* calculate separate value values */
+ while (length--)
+ {
+ const gdouble masked = *mask_data;
+
+ VALUE (1, data[0]) += masked;
+ VALUE (2, data[1]) += masked;
+ VALUE (3, data[2]) += masked;
+
+ max = MAX (data[0], data[1]);
+ max = MAX (data[2], max);
+ VALUE (0, max) += masked;
+
+ luminance = GIMP_RGB_LUMINANCE (data[0], data[1], data[2]);
+ VALUE (4, luminance) += masked;
+
+ data += n_components;
+ mask_data += 1;
+
+ CHECK_CANCELED (length);
+ }
+ break;
+
+ case 4: /* calculate separate value values */
+ while (length--)
+ {
+ const gdouble masked = *mask_data;
+ const gdouble weight = data[3];
+
+ VALUE (1, data[0]) += weight * masked;
+ VALUE (2, data[1]) += weight * masked;
+ VALUE (3, data[2]) += weight * masked;
+ VALUE (4, data[3]) += masked;
+
+ max = MAX (data[0], data[1]);
+ max = MAX (data[2], max);
+ VALUE (0, max) += weight * masked;
+
+ luminance = GIMP_RGB_LUMINANCE (data[0], data[1], data[2]);
+ VALUE (5, luminance) += weight * masked;
+
+ data += n_components;
+ mask_data += 1;
+
+ CHECK_CANCELED (length);
+ }
+ break;
+ }
+ }
+ else /* no mask */
+ {
+ switch (n_components)
+ {
+ case 1:
+ while (length--)
+ {
+ VALUE (0, data[0]) += 1.0;
+
+ data += n_components;
+
+ CHECK_CANCELED (length);
+ }
+ break;
+
+ case 2:
+ while (length--)
+ {
+ const gdouble weight = data[1];
+
+ VALUE (0, data[0]) += weight;
+ VALUE (1, data[1]) += 1.0;
+
+ data += n_components;
+
+ CHECK_CANCELED (length);
+ }
+ break;
+
+ case 3: /* calculate separate value values */
+ while (length--)
+ {
+ VALUE (1, data[0]) += 1.0;
+ VALUE (2, data[1]) += 1.0;
+ VALUE (3, data[2]) += 1.0;
+
+ max = MAX (data[0], data[1]);
+ max = MAX (data[2], max);
+ VALUE (0, max) += 1.0;
+
+ luminance = GIMP_RGB_LUMINANCE (data[0], data[1], data[2]);
+ VALUE (4, luminance) += 1.0;
+
+ data += n_components;
+
+ CHECK_CANCELED (length);
+ }
+ break;
+
+ case 4: /* calculate separate value values */
+ while (length--)
+ {
+ const gdouble weight = data[3];
+
+ VALUE (1, data[0]) += weight;
+ VALUE (2, data[1]) += weight;
+ VALUE (3, data[2]) += weight;
+ VALUE (4, data[3]) += 1.0;
+
+ max = MAX (data[0], data[1]);
+ max = MAX (data[2], max);
+ VALUE (0, max) += weight;
+
+ luminance = GIMP_RGB_LUMINANCE (data[0], data[1], data[2]);
+ VALUE (5, luminance) += weight;
+
+ data += n_components;
+
+ CHECK_CANCELED (length);
+ }
+ break;
+ }
+ }
+ }
+
+#undef VALUE
+#undef CHECK_CANCELED
+}
+
+static void
+gimp_histogram_calculate_async_callback (GimpAsync *async,
+ CalculateContext *context)
+{
+ context->histogram->priv->calculate_async = NULL;
+
+ if (gimp_async_is_finished (async))
+ {
+ gimp_histogram_set_values (context->histogram,
+ context->n_components, context->n_bins,
+ context->values);
+ }
+
+ g_object_unref (context->buffer);
+ if (context->mask)
+ g_object_unref (context->mask);
+
+ g_slice_free (CalculateContext, context);
+}
diff --git a/app/core/gimphistogram.h b/app/core/gimphistogram.h
new file mode 100644
index 0000000..ba1e0ef
--- /dev/null
+++ b/app/core/gimphistogram.h
@@ -0,0 +1,105 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimphistogram module Copyright (C) 1999 Jay Cox <jaycox@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_HISTOGRAM_H__
+#define __GIMP_HISTOGRAM_H__
+
+
+#include "gimpobject.h"
+
+
+#define GIMP_TYPE_HISTOGRAM (gimp_histogram_get_type ())
+#define GIMP_HISTOGRAM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_HISTOGRAM, GimpHistogram))
+#define GIMP_HISTOGRAM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_HISTOGRAM, GimpHistogramClass))
+#define GIMP_IS_HISTOGRAM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_HISTOGRAM))
+#define GIMP_IS_HISTOGRAM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_HISTOGRAM))
+#define GIMP_HISTOGRAM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_HISTOGRAM, GimpHistogramClass))
+
+
+typedef struct _GimpHistogramPrivate GimpHistogramPrivate;
+typedef struct _GimpHistogramClass GimpHistogramClass;
+
+struct _GimpHistogram
+{
+ GimpObject parent_instance;
+
+ GimpHistogramPrivate *priv;
+};
+
+struct _GimpHistogramClass
+{
+ GimpObjectClass parent_class;
+};
+
+
+GType gimp_histogram_get_type (void) G_GNUC_CONST;
+
+GimpHistogram * gimp_histogram_new (gboolean linear);
+
+GimpHistogram * gimp_histogram_duplicate (GimpHistogram *histogram);
+
+void gimp_histogram_calculate (GimpHistogram *histogram,
+ GeglBuffer *buffer,
+ const GeglRectangle *buffer_rect,
+ GeglBuffer *mask,
+ const GeglRectangle *mask_rect);
+GimpAsync * gimp_histogram_calculate_async (GimpHistogram *histogram,
+ GeglBuffer *buffer,
+ const GeglRectangle *buffer_rect,
+ GeglBuffer *mask,
+ const GeglRectangle *mask_rect);
+
+void gimp_histogram_clear_values (GimpHistogram *histogram,
+ gint n_components);
+
+gdouble gimp_histogram_get_maximum (GimpHistogram *histogram,
+ GimpHistogramChannel channel);
+gdouble gimp_histogram_get_count (GimpHistogram *histogram,
+ GimpHistogramChannel channel,
+ gint start,
+ gint end);
+gdouble gimp_histogram_get_mean (GimpHistogram *histogram,
+ GimpHistogramChannel channel,
+ gint start,
+ gint end);
+gdouble gimp_histogram_get_median (GimpHistogram *histogram,
+ GimpHistogramChannel channel,
+ gint start,
+ gint end);
+gdouble gimp_histogram_get_std_dev (GimpHistogram *histogram,
+ GimpHistogramChannel channel,
+ gint start,
+ gint end);
+gdouble gimp_histogram_get_threshold (GimpHistogram *histogram,
+ GimpHistogramChannel channel,
+ gint start,
+ gint end);
+gdouble gimp_histogram_get_value (GimpHistogram *histogram,
+ GimpHistogramChannel channel,
+ gint bin);
+gdouble gimp_histogram_get_component (GimpHistogram *histogram,
+ gint component,
+ gint bin);
+gint gimp_histogram_n_components (GimpHistogram *histogram);
+gint gimp_histogram_n_bins (GimpHistogram *histogram);
+gboolean gimp_histogram_has_channel (GimpHistogram *histogram,
+ GimpHistogramChannel channel);
+
+
+#endif /* __GIMP_HISTOGRAM_H__ */
diff --git a/app/core/gimpidtable.c b/app/core/gimpidtable.c
new file mode 100644
index 0000000..b69a13d
--- /dev/null
+++ b/app/core/gimpidtable.c
@@ -0,0 +1,227 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpidtable.c
+ * Copyright (C) 2011 Martin Nordholts <martinn@src.gnome.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gio/gio.h>
+#include <gegl.h>
+
+#include "core-types.h"
+
+#include "gimp-memsize.h"
+#include "gimpidtable.h"
+
+
+#define GIMP_ID_TABLE_START_ID 1
+#define GIMP_ID_TABLE_END_ID G_MAXINT
+
+
+struct _GimpIdTablePrivate
+{
+ GHashTable *id_table;
+ gint next_id;
+};
+
+
+static void gimp_id_table_finalize (GObject *object);
+static gint64 gimp_id_table_get_memsize (GimpObject *object,
+ gint64 *gui_size);
+
+
+G_DEFINE_TYPE_WITH_PRIVATE (GimpIdTable, gimp_id_table, GIMP_TYPE_OBJECT)
+
+#define parent_class gimp_id_table_parent_class
+
+
+static void
+gimp_id_table_class_init (GimpIdTableClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpObjectClass *gimp_object_class = GIMP_OBJECT_CLASS (klass);
+
+ object_class->finalize = gimp_id_table_finalize;
+
+ gimp_object_class->get_memsize = gimp_id_table_get_memsize;
+}
+
+static void
+gimp_id_table_init (GimpIdTable *id_table)
+{
+ id_table->priv = gimp_id_table_get_instance_private (id_table);
+
+ id_table->priv->id_table = g_hash_table_new (g_direct_hash, NULL);
+ id_table->priv->next_id = GIMP_ID_TABLE_START_ID;
+}
+
+static void
+gimp_id_table_finalize (GObject *object)
+{
+ GimpIdTable *id_table = GIMP_ID_TABLE (object);
+
+ g_clear_pointer (&id_table->priv->id_table, g_hash_table_unref);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static gint64
+gimp_id_table_get_memsize (GimpObject *object,
+ gint64 *gui_size)
+{
+ GimpIdTable *id_table = GIMP_ID_TABLE (object);
+ gint64 memsize = 0;
+
+ memsize += gimp_g_hash_table_get_memsize (id_table->priv->id_table, 0);
+
+ return memsize + GIMP_OBJECT_CLASS (parent_class)->get_memsize (object,
+ gui_size);
+}
+
+/**
+ * gimp_id_table_new:
+ *
+ * Returns: A new #GimpIdTable.
+ **/
+GimpIdTable *
+gimp_id_table_new (void)
+{
+ return g_object_new (GIMP_TYPE_ID_TABLE, NULL);
+}
+
+/**
+ * gimp_id_table_insert:
+ * @id_table: A #GimpIdTable
+ * @data: Data to insert and assign an id to
+ *
+ * Insert data in the id table. The data will get an, in this table,
+ * unused ID assigned to it that can be used to later lookup the data.
+ *
+ * Returns: The assigned ID.
+ **/
+gint
+gimp_id_table_insert (GimpIdTable *id_table, gpointer data)
+{
+ gint new_id;
+ gint start_id;
+
+ g_return_val_if_fail (GIMP_IS_ID_TABLE (id_table), 0);
+
+ start_id = id_table->priv->next_id;
+
+ do
+ {
+ new_id = id_table->priv->next_id++;
+
+ if (id_table->priv->next_id == GIMP_ID_TABLE_END_ID)
+ id_table->priv->next_id = GIMP_ID_TABLE_START_ID;
+
+ if (start_id == id_table->priv->next_id)
+ {
+ /* We looped once over all used ids. Very unlikely to happen.
+ And if it does, there is probably not much to be done.
+ It is just good design not to allow a theoretical infinite loop. */
+ g_error ("%s: out of ids!", G_STRFUNC);
+ break;
+ }
+ }
+ while (gimp_id_table_lookup (id_table, new_id));
+
+ return gimp_id_table_insert_with_id (id_table, new_id, data);
+}
+
+/**
+ * gimp_id_table_insert_with_id:
+ * @id_table: An #GimpIdTable
+ * @id: The ID to use. Must be greater than 0.
+ * @data: The data to associate with the id
+ *
+ * Insert data in the id table with a specific ID. If data already
+ * exsts with the given ID, this function fails.
+ *
+ * Returns: The used ID if successful, -1 if it was already in use.
+ **/
+gint
+gimp_id_table_insert_with_id (GimpIdTable *id_table, gint id, gpointer data)
+{
+ g_return_val_if_fail (GIMP_IS_ID_TABLE (id_table), 0);
+ g_return_val_if_fail (id > 0, 0);
+
+ if (gimp_id_table_lookup (id_table, id))
+ return -1;
+
+ g_hash_table_insert (id_table->priv->id_table, GINT_TO_POINTER (id), data);
+
+ return id;
+}
+
+/**
+ * gimp_id_table_replace:
+ * @id_table: An #GimpIdTable
+ * @id: The ID to use. Must be greater than 0.
+ * @data: The data to insert/replace
+ *
+ * Replaces (if an item with the given ID exists) or inserts a new
+ * entry in the id table.
+ **/
+void
+gimp_id_table_replace (GimpIdTable *id_table, gint id, gpointer data)
+{
+ g_return_if_fail (GIMP_IS_ID_TABLE (id_table));
+ g_return_if_fail (id > 0);
+
+ g_hash_table_replace (id_table->priv->id_table, GINT_TO_POINTER (id), data);
+}
+
+/**
+ * gimp_id_table_lookup:
+ * @id_table: An #GimpIdTable
+ * @id: The ID of the data to lookup
+ *
+ * Lookup data based on ID.
+ *
+ * Returns: The data, or NULL if no data with the given ID was found.
+ **/
+gpointer
+gimp_id_table_lookup (GimpIdTable *id_table, gint id)
+{
+ g_return_val_if_fail (GIMP_IS_ID_TABLE (id_table), NULL);
+
+ return g_hash_table_lookup (id_table->priv->id_table, GINT_TO_POINTER (id));
+}
+
+
+/**
+ * gimp_id_table_remove:
+ * @id_table: An #GimpIdTable
+ * @id: The ID of the data to remove.
+ *
+ * Remove the data from the table with the given ID.
+ *
+ * Returns: %TRUE if data with the ID existed and was successfully
+ * removed, %FALSE otherwise.
+ **/
+gboolean
+gimp_id_table_remove (GimpIdTable *id_table, gint id)
+{
+ g_return_val_if_fail (GIMP_IS_ID_TABLE (id_table), FALSE);
+
+ g_return_val_if_fail (id_table != NULL, FALSE);
+
+ return g_hash_table_remove (id_table->priv->id_table, GINT_TO_POINTER (id));
+}
diff --git a/app/core/gimpidtable.h b/app/core/gimpidtable.h
new file mode 100644
index 0000000..88097fd
--- /dev/null
+++ b/app/core/gimpidtable.h
@@ -0,0 +1,68 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpidtable.h
+ * Copyright (C) 2011 Martin Nordholts <martinn@src.gnome.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_ID_TABLE_H__
+#define __GIMP_ID_TABLE_H__
+
+
+#include "gimpobject.h"
+
+
+#define GIMP_TYPE_ID_TABLE (gimp_id_table_get_type ())
+#define GIMP_ID_TABLE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_ID_TABLE, GimpIdTable))
+#define GIMP_ID_TABLE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_ID_TABLE, GimpIdTableClass))
+#define GIMP_IS_ID_TABLE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_ID_TABLE))
+#define GIMP_IS_ID_TABLE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_ID_TABLE))
+#define GIMP_ID_TABLE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_ID_TABLE, GimpIdTableClass))
+
+
+typedef struct _GimpIdTableClass GimpIdTableClass;
+typedef struct _GimpIdTablePrivate GimpIdTablePrivate;
+
+struct _GimpIdTable
+{
+ GimpObject parent_instance;
+
+ GimpIdTablePrivate *priv;
+};
+
+struct _GimpIdTableClass
+{
+ GimpObjectClass parent_class;
+};
+
+
+GType gimp_id_table_get_type (void) G_GNUC_CONST;
+GimpIdTable * gimp_id_table_new (void);
+gint gimp_id_table_insert (GimpIdTable *id_table,
+ gpointer data);
+gint gimp_id_table_insert_with_id (GimpIdTable *id_table,
+ gint id,
+ gpointer data);
+void gimp_id_table_replace (GimpIdTable *id_table,
+ gint id,
+ gpointer data);
+gpointer gimp_id_table_lookup (GimpIdTable *id_table,
+ gint id);
+gboolean gimp_id_table_remove (GimpIdTable *id_table,
+ gint id);
+
+
+#endif /* __GIMP_ID_TABLE_H__ */
diff --git a/app/core/gimpimage-arrange.c b/app/core/gimpimage-arrange.c
new file mode 100644
index 0000000..6b6b05e
--- /dev/null
+++ b/app/core/gimpimage-arrange.c
@@ -0,0 +1,388 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpmath/gimpmath.h"
+
+#include "core-types.h"
+
+#include "gimpimage.h"
+#include "gimpimage-arrange.h"
+#include "gimpimage-guides.h"
+#include "gimpimage-undo.h"
+#include "gimpitem.h"
+#include "gimpguide.h"
+
+#include "gimp-intl.h"
+
+
+static GList * sort_by_offset (GList *list);
+static void compute_offsets (GList *list,
+ GimpAlignmentType alignment);
+static void compute_offset (GObject *object,
+ GimpAlignmentType alignment);
+static gint offset_compare (gconstpointer a,
+ gconstpointer b);
+
+
+/**
+ * gimp_image_arrange_objects:
+ * @image: The #GimpImage to which the objects belong.
+ * @list: A #GList of objects to be aligned.
+ * @alignment: The point on each target object to bring into alignment.
+ * @reference: The #GObject to align the targets with, or #NULL.
+ * @reference_alignment: The point on the reference object to align the target item with..
+ * @offset: How much to shift the target from perfect alignment..
+ *
+ * This function shifts the positions of a set of target objects,
+ * which can be "items" or guides, to bring them into a specified type
+ * of alignment with a reference object, which can be an item, guide,
+ * or image. If the requested alignment does not make sense (i.e.,
+ * trying to align a vertical guide vertically), nothing happens and
+ * no error message is generated.
+ *
+ * The objects in the list are sorted into increasing order before
+ * being arranged, where the order is defined by the type of alignment
+ * being requested. If the @reference argument is #NULL, then the
+ * first object in the sorted list is used as reference.
+ *
+ * When there are multiple target objects, they are arranged so that
+ * the spacing between consecutive ones is given by the argument
+ * @offset but for HFILL and VFILL - in this case, @offset works as an
+ * internal margin for the distribution (and it can be negative).
+ */
+void
+gimp_image_arrange_objects (GimpImage *image,
+ GList *list,
+ GimpAlignmentType alignment,
+ GObject *reference,
+ GimpAlignmentType reference_alignment,
+ gint offset)
+{
+ gboolean do_x = FALSE;
+ gboolean do_y = FALSE;
+ gint z0 = 0;
+ GList *object_list;
+
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+ g_return_if_fail (G_IS_OBJECT (reference) || reference == NULL);
+
+ /* get offsets used for sorting */
+ switch (alignment)
+ {
+ /* order vertically for horizontal alignment */
+ case GIMP_ALIGN_LEFT:
+ case GIMP_ALIGN_HCENTER:
+ case GIMP_ALIGN_RIGHT:
+ do_x = TRUE;
+ compute_offsets (list, GIMP_ALIGN_TOP);
+ break;
+
+ /* order horizontally for horizontal arrangement */
+ case GIMP_ARRANGE_LEFT:
+ case GIMP_ARRANGE_HCENTER:
+ case GIMP_ARRANGE_RIGHT:
+ case GIMP_ARRANGE_HFILL:
+ do_x = TRUE;
+ compute_offsets (list, alignment);
+ break;
+
+ /* order horizontally for vertical alignment */
+ case GIMP_ALIGN_TOP:
+ case GIMP_ALIGN_VCENTER:
+ case GIMP_ALIGN_BOTTOM:
+ do_y = TRUE;
+ compute_offsets (list, GIMP_ALIGN_LEFT);
+ break;
+
+ /* order vertically for vertical arrangement */
+ case GIMP_ARRANGE_TOP:
+ case GIMP_ARRANGE_VCENTER:
+ case GIMP_ARRANGE_BOTTOM:
+ case GIMP_ARRANGE_VFILL:
+ do_y = TRUE;
+ compute_offsets (list, alignment);
+ break;
+
+ default:
+ g_return_if_reached ();
+ }
+
+ object_list = sort_by_offset (list);
+
+ /* now get offsets used for aligning */
+ compute_offsets (list, alignment);
+
+ if (reference == NULL)
+ {
+ reference = G_OBJECT (object_list->data);
+ object_list = g_list_next (object_list);
+ reference_alignment = alignment;
+ }
+ else
+ {
+ compute_offset (reference, reference_alignment);
+ }
+
+ z0 = GPOINTER_TO_INT (g_object_get_data (reference, "align-offset"));
+
+ if (object_list)
+ {
+ GList *list;
+ gint n;
+ gint distr_width = 0;
+ gint distr_height = 0;
+ gdouble fill_offset = 0;
+
+ if (reference_alignment == GIMP_ARRANGE_HFILL)
+ {
+ distr_width = GPOINTER_TO_INT (g_object_get_data
+ (reference, "align-width"));
+ /* The offset parameter works as an internal margin */
+ fill_offset = (distr_width - 2 * offset) /
+ (gint) g_list_length (object_list);
+ }
+ if (reference_alignment == GIMP_ARRANGE_VFILL)
+ {
+ distr_height = GPOINTER_TO_INT (g_object_get_data
+ (reference, "align-height"));
+ fill_offset = (distr_height - 2 * offset) /
+ (gint) g_list_length (object_list);
+ }
+
+ /* FIXME: undo group type is wrong */
+ gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_ITEM_DISPLACE,
+ C_("undo-type", "Arrange Objects"));
+
+ for (list = object_list, n = 1;
+ list;
+ list = g_list_next (list), n++)
+ {
+ GObject *target = list->data;
+ gint xtranslate = 0;
+ gint ytranslate = 0;
+ gint z1;
+
+ z1 = GPOINTER_TO_INT (g_object_get_data (target, "align-offset"));
+
+ if (reference_alignment == GIMP_ARRANGE_HFILL)
+ {
+ gint width = GPOINTER_TO_INT (g_object_get_data (target,
+ "align-width"));
+ xtranslate = ROUND (z0 - z1 + (n - 0.5) * fill_offset -
+ width / 2.0 + offset);
+ }
+ else if (reference_alignment == GIMP_ARRANGE_VFILL)
+ {
+ gint height = GPOINTER_TO_INT (g_object_get_data (target,
+ "align-height"));
+ ytranslate = ROUND (z0 - z1 + (n - 0.5) * fill_offset -
+ height / 2.0 + offset);
+ }
+ else /* the normal computing, when we don't depend on the
+ * width or height of the reference object
+ */
+ {
+ if (do_x)
+ xtranslate = z0 - z1 + n * offset;
+
+ if (do_y)
+ ytranslate = z0 - z1 + n * offset;
+ }
+
+ /* now actually align the target object */
+ if (GIMP_IS_ITEM (target))
+ {
+ gimp_item_translate (GIMP_ITEM (target),
+ xtranslate, ytranslate, TRUE);
+ }
+ else if (GIMP_IS_GUIDE (target))
+ {
+ GimpGuide *guide = GIMP_GUIDE (target);
+
+ switch (gimp_guide_get_orientation (guide))
+ {
+ case GIMP_ORIENTATION_VERTICAL:
+ gimp_image_move_guide (image, guide, z1 + xtranslate, TRUE);
+ break;
+
+ case GIMP_ORIENTATION_HORIZONTAL:
+ gimp_image_move_guide (image, guide, z1 + ytranslate, TRUE);
+ break;
+
+ default:
+ break;
+ }
+ }
+ }
+
+ gimp_image_undo_group_end (image);
+ }
+
+ g_list_free (object_list);
+}
+
+static GList *
+sort_by_offset (GList *list)
+{
+ return g_list_sort (g_list_copy (list),
+ offset_compare);
+
+}
+
+static gint
+offset_compare (gconstpointer a,
+ gconstpointer b)
+{
+ gint offset1 = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (a),
+ "align-offset"));
+ gint offset2 = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (b),
+ "align-offset"));
+
+ return offset1 - offset2;
+}
+
+/* this function computes the position of the alignment point
+ * for each object in the list, and attaches it to the
+ * object as object data.
+ */
+static void
+compute_offsets (GList *list,
+ GimpAlignmentType alignment)
+{
+ GList *l;
+
+ for (l = list; l; l = g_list_next (l))
+ compute_offset (l->data, alignment);
+}
+
+static void
+compute_offset (GObject *object,
+ GimpAlignmentType alignment)
+{
+ gint object_offset_x = 0;
+ gint object_offset_y = 0;
+ gint object_height = 0;
+ gint object_width = 0;
+ gint offset = 0;
+
+ if (GIMP_IS_IMAGE (object))
+ {
+ GimpImage *image = GIMP_IMAGE (object);
+
+ object_offset_x = 0;
+ object_offset_y = 0;
+ object_height = gimp_image_get_height (image);
+ object_width = gimp_image_get_width (image);
+ }
+ else if (GIMP_IS_ITEM (object))
+ {
+ GimpItem *item = GIMP_ITEM (object);
+ gint off_x, off_y;
+
+ gimp_item_bounds (item,
+ &object_offset_x,
+ &object_offset_y,
+ &object_width,
+ &object_height);
+
+ gimp_item_get_offset (item, &off_x, &off_y);
+ object_offset_x += off_x;
+ object_offset_y += off_y;
+ }
+ else if (GIMP_IS_GUIDE (object))
+ {
+ GimpGuide *guide = GIMP_GUIDE (object);
+
+ switch (gimp_guide_get_orientation (guide))
+ {
+ case GIMP_ORIENTATION_VERTICAL:
+ object_offset_x = gimp_guide_get_position (guide);
+ object_width = 0;
+ break;
+
+ case GIMP_ORIENTATION_HORIZONTAL:
+ object_offset_y = gimp_guide_get_position (guide);
+ object_height = 0;
+ break;
+
+ default:
+ break;
+ }
+ }
+ else
+ {
+ g_printerr ("Alignment object is not an image, item or guide.\n");
+ }
+
+ switch (alignment)
+ {
+ case GIMP_ALIGN_LEFT:
+ case GIMP_ARRANGE_LEFT:
+ case GIMP_ARRANGE_HFILL:
+ offset = object_offset_x;
+ break;
+
+ case GIMP_ALIGN_HCENTER:
+ case GIMP_ARRANGE_HCENTER:
+ offset = object_offset_x + object_width / 2;
+ break;
+
+ case GIMP_ALIGN_RIGHT:
+ case GIMP_ARRANGE_RIGHT:
+ offset = object_offset_x + object_width;
+ break;
+
+ case GIMP_ALIGN_TOP:
+ case GIMP_ARRANGE_TOP:
+ case GIMP_ARRANGE_VFILL:
+ offset = object_offset_y;
+ break;
+
+ case GIMP_ALIGN_VCENTER:
+ case GIMP_ARRANGE_VCENTER:
+ offset = object_offset_y + object_height / 2;
+ break;
+
+ case GIMP_ALIGN_BOTTOM:
+ case GIMP_ARRANGE_BOTTOM:
+ offset = object_offset_y + object_height;
+ break;
+
+ default:
+ g_return_if_reached ();
+ }
+
+ g_object_set_data (object, "align-offset",
+ GINT_TO_POINTER (offset));
+
+ /* These are only used for HFILL and VFILL, but since the call to
+ * gimp_image_arrange_objects allows for two different alignments
+ * (object and reference_alignment) we better be on the safe side in
+ * case they differ. (the current implementation of the align tool
+ * always pass the same value to both parameters)
+ */
+ g_object_set_data (object, "align-width",
+ GINT_TO_POINTER (object_width));
+
+ g_object_set_data (object, "align-height",
+ GINT_TO_POINTER (object_height));
+}
diff --git a/app/core/gimpimage-arrange.h b/app/core/gimpimage-arrange.h
new file mode 100644
index 0000000..1109a5d
--- /dev/null
+++ b/app/core/gimpimage-arrange.h
@@ -0,0 +1,29 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_IMAGE_ARRANGE_H__
+#define __GIMP_IMAGE_ARRANGE_H__
+
+
+void gimp_image_arrange_objects (GimpImage *image,
+ GList *list,
+ GimpAlignmentType alignment,
+ GObject *reference,
+ GimpAlignmentType reference_alignment,
+ gint offset);
+
+#endif /* __GIMP_IMAGE_ARRANGE_H__ */
diff --git a/app/core/gimpimage-color-profile.c b/app/core/gimpimage-color-profile.c
new file mode 100644
index 0000000..a7030f3
--- /dev/null
+++ b/app/core/gimpimage-color-profile.c
@@ -0,0 +1,822 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpimage-color-profile.c
+ * Copyright (C) 2015 Michael Natterer <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <cairo.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpconfig/gimpconfig.h"
+#include "libgimpcolor/gimpcolor.h"
+#include "libgimpbase/gimpbase.h"
+
+#include "core-types.h"
+
+#include "config/gimpdialogconfig.h"
+
+#include "gegl/gimp-babl.h"
+#include "gegl/gimp-gegl-loops.h"
+
+#include "gimp.h"
+#include "gimpcontext.h"
+#include "gimpdrawable.h"
+#include "gimperror.h"
+#include "gimpimage.h"
+#include "gimpimage-color-profile.h"
+#include "gimpimage-colormap.h"
+#include "gimpimage-private.h"
+#include "gimpimage-undo.h"
+#include "gimpimage-undo-push.h"
+#include "gimpobjectqueue.h"
+#include "gimpprogress.h"
+
+#include "gimp-intl.h"
+
+
+/* local function prototypes */
+
+static void gimp_image_convert_profile_layers (GimpImage *image,
+ GimpColorProfile *src_profile,
+ GimpColorProfile *dest_profile,
+ GimpColorRenderingIntent intent,
+ gboolean bpc,
+ GimpProgress *progress);
+static void gimp_image_convert_profile_colormap (GimpImage *image,
+ GimpColorProfile *src_profile,
+ GimpColorProfile *dest_profile,
+ GimpColorRenderingIntent intent,
+ gboolean bpc,
+ GimpProgress *progress);
+
+static void gimp_image_create_color_transforms (GimpImage *image);
+
+
+/* public functions */
+
+gboolean
+gimp_image_get_is_color_managed (GimpImage *image)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);
+
+ return GIMP_IMAGE_GET_PRIVATE (image)->is_color_managed;
+}
+
+void
+gimp_image_set_is_color_managed (GimpImage *image,
+ gboolean is_color_managed,
+ gboolean push_undo)
+{
+ GimpImagePrivate *private;
+
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+
+ private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ is_color_managed = is_color_managed ? TRUE : FALSE;
+
+ if (is_color_managed != private->is_color_managed)
+ {
+ if (push_undo)
+ gimp_image_undo_push_image_color_managed (image, NULL);
+
+ private->is_color_managed = is_color_managed;
+
+ gimp_color_managed_profile_changed (GIMP_COLOR_MANAGED (image));
+ }
+}
+
+gboolean
+gimp_image_validate_icc_parasite (GimpImage *image,
+ const GimpParasite *icc_parasite,
+ gboolean *is_builtin,
+ GError **error)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);
+ g_return_val_if_fail (icc_parasite != NULL, FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ if (strcmp (gimp_parasite_name (icc_parasite),
+ GIMP_ICC_PROFILE_PARASITE_NAME) != 0)
+ {
+ g_set_error_literal (error, GIMP_ERROR, GIMP_FAILED,
+ _("ICC profile validation failed: "
+ "Parasite's name is not 'icc-profile'"));
+ return FALSE;
+ }
+
+ if (gimp_parasite_flags (icc_parasite) != (GIMP_PARASITE_PERSISTENT |
+ GIMP_PARASITE_UNDOABLE))
+ {
+ g_set_error_literal (error, GIMP_ERROR, GIMP_FAILED,
+ _("ICC profile validation failed: "
+ "Parasite's flags are not (PERSISTENT | UNDOABLE)"));
+ return FALSE;
+ }
+
+ return gimp_image_validate_icc_profile (image,
+ gimp_parasite_data (icc_parasite),
+ gimp_parasite_data_size (icc_parasite),
+ is_builtin,
+ error);
+}
+
+const GimpParasite *
+gimp_image_get_icc_parasite (GimpImage *image)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+
+ return gimp_image_parasite_find (image, GIMP_ICC_PROFILE_PARASITE_NAME);
+}
+
+void
+gimp_image_set_icc_parasite (GimpImage *image,
+ const GimpParasite *icc_parasite)
+{
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+
+ if (icc_parasite)
+ {
+ g_return_if_fail (gimp_image_validate_icc_parasite (image, icc_parasite,
+ NULL, NULL) == TRUE);
+
+ gimp_image_parasite_attach (image, icc_parasite, TRUE);
+ }
+ else
+ {
+ gimp_image_parasite_detach (image, GIMP_ICC_PROFILE_PARASITE_NAME, TRUE);
+ }
+}
+
+gboolean
+gimp_image_validate_icc_profile (GimpImage *image,
+ const guint8 *data,
+ gsize length,
+ gboolean *is_builtin,
+ GError **error)
+{
+ GimpColorProfile *profile;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);
+ g_return_val_if_fail (data != NULL || length == 0, FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ profile = gimp_color_profile_new_from_icc_profile (data, length, error);
+
+ if (! profile)
+ {
+ g_prefix_error (error, _("ICC profile validation failed: "));
+ return FALSE;
+ }
+
+ if (! gimp_image_validate_color_profile (image, profile, is_builtin, error))
+ {
+ g_object_unref (profile);
+ return FALSE;
+ }
+
+ g_object_unref (profile);
+
+ return TRUE;
+}
+
+const guint8 *
+gimp_image_get_icc_profile (GimpImage *image,
+ gsize *length)
+{
+ const GimpParasite *parasite;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);
+
+ parasite = gimp_image_parasite_find (image, GIMP_ICC_PROFILE_PARASITE_NAME);
+
+ if (parasite)
+ {
+ if (length)
+ *length = gimp_parasite_data_size (parasite);
+
+ return gimp_parasite_data (parasite);
+ }
+
+ if (length)
+ *length = 0;
+
+ return NULL;
+}
+
+gboolean
+gimp_image_set_icc_profile (GimpImage *image,
+ const guint8 *data,
+ gsize length,
+ GError **error)
+{
+ GimpParasite *parasite = NULL;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);
+ g_return_val_if_fail (data == NULL || length != 0, FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ if (data)
+ {
+ gboolean is_builtin;
+
+ parasite = gimp_parasite_new (GIMP_ICC_PROFILE_PARASITE_NAME,
+ GIMP_PARASITE_PERSISTENT |
+ GIMP_PARASITE_UNDOABLE,
+ length, data);
+
+ if (! gimp_image_validate_icc_parasite (image, parasite, &is_builtin,
+ error))
+ {
+ gimp_parasite_free (parasite);
+ return FALSE;
+ }
+
+ /* don't tag the image with the built-in profile */
+ if (is_builtin)
+ {
+ gimp_parasite_free (parasite);
+ parasite = NULL;
+ }
+ }
+
+ gimp_image_set_icc_parasite (image, parasite);
+
+ if (parasite)
+ gimp_parasite_free (parasite);
+
+ return TRUE;
+}
+
+gboolean
+gimp_image_validate_color_profile (GimpImage *image,
+ GimpColorProfile *profile,
+ gboolean *is_builtin,
+ GError **error)
+{
+ const Babl *format;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);
+ g_return_val_if_fail (GIMP_IS_COLOR_PROFILE (profile), FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ format = gimp_image_get_layer_format (image, TRUE);
+
+ return gimp_image_validate_color_profile_by_format (format,
+ profile, is_builtin,
+ error);
+}
+
+GimpColorProfile *
+gimp_image_get_color_profile (GimpImage *image)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+
+ return GIMP_IMAGE_GET_PRIVATE (image)->color_profile;
+}
+
+gboolean
+gimp_image_set_color_profile (GimpImage *image,
+ GimpColorProfile *profile,
+ GError **error)
+{
+ const guint8 *data = NULL;
+ gsize length = 0;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);
+ g_return_val_if_fail (profile == NULL || GIMP_IS_COLOR_PROFILE (profile),
+ FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ if (profile)
+ data = gimp_color_profile_get_icc_profile (profile, &length);
+
+ return gimp_image_set_icc_profile (image, data, length, error);
+}
+
+gboolean
+gimp_image_validate_color_profile_by_format (const Babl *format,
+ GimpColorProfile *profile,
+ gboolean *is_builtin,
+ GError **error)
+{
+ g_return_val_if_fail (format != NULL, FALSE);
+ g_return_val_if_fail (GIMP_IS_COLOR_PROFILE (profile), FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ if (gimp_babl_format_get_base_type (format) == GIMP_GRAY)
+ {
+ if (! gimp_color_profile_is_gray (profile))
+ {
+ g_set_error_literal (error, GIMP_ERROR, GIMP_FAILED,
+ _("ICC profile validation failed: "
+ "Color profile is not for grayscale color space"));
+ return FALSE;
+ }
+ }
+ else
+ {
+ if (! gimp_color_profile_is_rgb (profile))
+ {
+ g_set_error_literal (error, GIMP_ERROR, GIMP_FAILED,
+ _("ICC profile validation failed: "
+ "Color profile is not for RGB color space"));
+ return FALSE;
+ }
+ }
+
+ if (is_builtin)
+ {
+ GimpColorProfile *builtin;
+
+ builtin = gimp_babl_format_get_color_profile (format);
+
+ *is_builtin = gimp_color_profile_is_equal (profile, builtin);
+ }
+
+ return TRUE;
+}
+
+GimpColorProfile *
+gimp_image_get_builtin_color_profile (GimpImage *image)
+{
+ const Babl *format;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+
+ format = gimp_image_get_layer_format (image, FALSE);
+
+ return gimp_babl_format_get_color_profile (format);
+}
+
+gboolean
+gimp_image_convert_color_profile (GimpImage *image,
+ GimpColorProfile *dest_profile,
+ GimpColorRenderingIntent intent,
+ gboolean bpc,
+ GimpProgress *progress,
+ GError **error)
+{
+ GimpColorProfile *src_profile;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);
+ g_return_val_if_fail (GIMP_IS_COLOR_PROFILE (dest_profile), FALSE);
+ g_return_val_if_fail (progress == NULL || GIMP_IS_PROGRESS (progress), FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ if (! gimp_image_validate_color_profile (image, dest_profile, NULL, error))
+ return FALSE;
+
+ src_profile = gimp_color_managed_get_color_profile (GIMP_COLOR_MANAGED (image));
+
+ if (! src_profile || gimp_color_profile_is_equal (src_profile, dest_profile))
+ return TRUE;
+
+ if (progress)
+ gimp_progress_start (progress, FALSE,
+ _("Converting from '%s' to '%s'"),
+ gimp_color_profile_get_label (src_profile),
+ gimp_color_profile_get_label (dest_profile));
+
+ gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_IMAGE_CONVERT,
+ _("Color profile conversion"));
+
+ switch (gimp_image_get_base_type (image))
+ {
+ case GIMP_RGB:
+ case GIMP_GRAY:
+ gimp_image_convert_profile_layers (image,
+ src_profile, dest_profile,
+ intent, bpc,
+ progress);
+ break;
+
+ case GIMP_INDEXED:
+ gimp_image_convert_profile_colormap (image,
+ src_profile, dest_profile,
+ intent, bpc,
+ progress);
+ break;
+ }
+
+ gimp_image_set_is_color_managed (image, TRUE, TRUE);
+ gimp_image_set_color_profile (image, dest_profile, NULL);
+ /* omg... */
+ gimp_image_parasite_detach (image, "icc-profile-name", TRUE);
+
+ gimp_image_undo_group_end (image);
+
+ if (progress)
+ gimp_progress_end (progress);
+
+ return TRUE;
+}
+
+void
+gimp_image_import_color_profile (GimpImage *image,
+ GimpContext *context,
+ GimpProgress *progress,
+ gboolean interactive)
+{
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+ g_return_if_fail (GIMP_IS_CONTEXT (context));
+ g_return_if_fail (progress == NULL || GIMP_IS_PROGRESS (progress));
+
+ if (gimp_image_get_is_color_managed (image) &&
+ gimp_image_get_color_profile (image))
+ {
+ GimpColorProfilePolicy policy;
+ GimpColorProfile *dest_profile = NULL;
+ GimpColorRenderingIntent intent;
+ gboolean bpc;
+
+ policy = GIMP_DIALOG_CONFIG (image->gimp->config)->color_profile_policy;
+ intent = GIMP_COLOR_RENDERING_INTENT_RELATIVE_COLORIMETRIC;
+ bpc = TRUE;
+
+ if (policy == GIMP_COLOR_PROFILE_POLICY_ASK)
+ {
+ if (interactive)
+ {
+ gboolean dont_ask = FALSE;
+
+ policy = gimp_query_profile_policy (image->gimp, image, context,
+ &dest_profile,
+ &intent, &bpc,
+ &dont_ask);
+
+ if (dont_ask)
+ {
+ g_object_set (G_OBJECT (image->gimp->config),
+ "color-profile-policy", policy,
+ NULL);
+ }
+ }
+ else
+ {
+ policy = GIMP_COLOR_PROFILE_POLICY_KEEP;
+ }
+ }
+
+ if (policy == GIMP_COLOR_PROFILE_POLICY_CONVERT)
+ {
+ if (! dest_profile)
+ {
+ dest_profile = gimp_image_get_builtin_color_profile (image);
+ g_object_ref (dest_profile);
+ }
+
+ gimp_image_convert_color_profile (image, dest_profile,
+ intent, bpc,
+ progress, NULL);
+
+ g_object_unref (dest_profile);
+ }
+ }
+}
+
+GimpColorTransform *
+gimp_image_get_color_transform_to_srgb_u8 (GimpImage *image)
+{
+ GimpImagePrivate *private;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+
+ private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ gimp_image_create_color_transforms (image);
+
+ if (private->is_color_managed)
+ return private->transform_to_srgb_u8;
+
+ return NULL;
+}
+
+GimpColorTransform *
+gimp_image_get_color_transform_from_srgb_u8 (GimpImage *image)
+{
+ GimpImagePrivate *private;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+
+ private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ gimp_image_create_color_transforms (image);
+
+ if (private->is_color_managed)
+ return private->transform_from_srgb_u8;
+
+ return NULL;
+}
+
+GimpColorTransform *
+gimp_image_get_color_transform_to_srgb_double (GimpImage *image)
+{
+ GimpImagePrivate *private;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+
+ private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ gimp_image_create_color_transforms (image);
+
+ if (private->is_color_managed)
+ return private->transform_to_srgb_double;
+
+ return NULL;
+}
+
+GimpColorTransform *
+gimp_image_get_color_transform_from_srgb_double (GimpImage *image)
+{
+ GimpImagePrivate *private;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+
+ private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ gimp_image_create_color_transforms (image);
+
+ if (private->is_color_managed)
+ return private->transform_from_srgb_double;
+
+ return NULL;
+}
+
+void
+gimp_image_color_profile_pixel_to_srgb (GimpImage *image,
+ const Babl *pixel_format,
+ gpointer pixel,
+ GimpRGB *color)
+{
+ GimpColorTransform *transform;
+
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+
+ transform = gimp_image_get_color_transform_to_srgb_double (image);
+
+ if (transform)
+ {
+ gimp_color_transform_process_pixels (transform,
+ pixel_format,
+ pixel,
+ babl_format ("R'G'B'A double"),
+ color,
+ 1);
+ }
+ else
+ {
+ gimp_rgba_set_pixel (color, pixel_format, pixel);
+ }
+}
+
+void
+gimp_image_color_profile_srgb_to_pixel (GimpImage *image,
+ const GimpRGB *color,
+ const Babl *pixel_format,
+ gpointer pixel)
+{
+ GimpColorTransform *transform;
+
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+
+ transform = gimp_image_get_color_transform_from_srgb_double (image);
+
+ if (transform)
+ {
+ gimp_color_transform_process_pixels (transform,
+ babl_format ("R'G'B'A double"),
+ color,
+ pixel_format,
+ pixel,
+ 1);
+ }
+ else
+ {
+ gimp_rgba_get_pixel (color, pixel_format, pixel);
+ }
+}
+
+
+/* internal API */
+
+void
+_gimp_image_free_color_profile (GimpImage *image)
+{
+ GimpImagePrivate *private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ g_clear_object (&private->color_profile);
+
+ _gimp_image_free_color_transforms (image);
+}
+
+void
+_gimp_image_free_color_transforms (GimpImage *image)
+{
+ GimpImagePrivate *private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ g_clear_object (&private->transform_to_srgb_u8);
+ g_clear_object (&private->transform_from_srgb_u8);
+ g_clear_object (&private->transform_to_srgb_double);
+ g_clear_object (&private->transform_from_srgb_double);
+
+ private->color_transforms_created = FALSE;
+}
+
+void
+_gimp_image_update_color_profile (GimpImage *image,
+ const GimpParasite *icc_parasite)
+{
+ GimpImagePrivate *private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ _gimp_image_free_color_profile (image);
+
+ if (icc_parasite)
+ {
+ private->color_profile =
+ gimp_color_profile_new_from_icc_profile (gimp_parasite_data (icc_parasite),
+ gimp_parasite_data_size (icc_parasite),
+ NULL);
+ }
+
+ gimp_color_managed_profile_changed (GIMP_COLOR_MANAGED (image));
+}
+
+
+/* private functions */
+
+static void
+gimp_image_convert_profile_layers (GimpImage *image,
+ GimpColorProfile *src_profile,
+ GimpColorProfile *dest_profile,
+ GimpColorRenderingIntent intent,
+ gboolean bpc,
+ GimpProgress *progress)
+{
+ GimpObjectQueue *queue;
+ GList *layers;
+ GList *list;
+ GimpDrawable *drawable;
+
+ queue = gimp_object_queue_new (progress);
+ progress = GIMP_PROGRESS (queue);
+
+ layers = gimp_image_get_layer_list (image);
+
+ for (list = layers; list; list = g_list_next (list))
+ {
+ if (! gimp_viewable_get_children (list->data))
+ gimp_object_queue_push (queue, list->data);
+ }
+
+ g_list_free (layers);
+
+ while ((drawable = gimp_object_queue_pop (queue)))
+ {
+ GimpItem *item = GIMP_ITEM (drawable);
+ GeglBuffer *buffer;
+ gboolean alpha;
+
+ alpha = gimp_drawable_has_alpha (drawable);
+
+ buffer = gegl_buffer_new (GEGL_RECTANGLE (0, 0,
+ gimp_item_get_width (item),
+ gimp_item_get_height (item)),
+ gimp_image_get_layer_format (image, alpha));
+
+ gimp_drawable_push_undo (drawable, NULL, NULL,
+ 0, 0,
+ gimp_item_get_width (GIMP_ITEM (drawable)),
+ gimp_item_get_height (GIMP_ITEM (drawable)));
+
+ gimp_gegl_convert_color_profile (gimp_drawable_get_buffer (drawable),
+ NULL,
+ src_profile,
+ buffer,
+ NULL,
+ dest_profile,
+ intent, bpc,
+ progress);
+
+ gimp_drawable_set_buffer (drawable, TRUE, NULL, buffer);
+ g_object_unref (buffer);
+ }
+
+ g_object_unref (queue);
+}
+
+static void
+gimp_image_convert_profile_colormap (GimpImage *image,
+ GimpColorProfile *src_profile,
+ GimpColorProfile *dest_profile,
+ GimpColorRenderingIntent intent,
+ gboolean bpc,
+ GimpProgress *progress)
+{
+ GimpColorTransform *transform;
+ GimpColorTransformFlags flags = 0;
+ guchar *cmap;
+ gint n_colors;
+
+ n_colors = gimp_image_get_colormap_size (image);
+ cmap = g_memdup (gimp_image_get_colormap (image), n_colors * 3);
+
+ if (bpc)
+ flags |= GIMP_COLOR_TRANSFORM_FLAGS_BLACK_POINT_COMPENSATION;
+
+ transform = gimp_color_transform_new (src_profile,
+ babl_format ("R'G'B' u8"),
+ dest_profile,
+ babl_format ("R'G'B' u8"),
+ intent, flags);
+
+ if (transform)
+ {
+ gimp_color_transform_process_pixels (transform,
+ babl_format ("R'G'B' u8"), cmap,
+ babl_format ("R'G'B' u8"), cmap,
+ n_colors);
+ g_object_unref (transform);
+
+ gimp_image_set_colormap (image, cmap, n_colors, TRUE);
+ }
+ else
+ {
+ g_warning ("cmsCreateTransform() failed!");
+ }
+
+ g_free (cmap);
+}
+
+static void
+gimp_image_create_color_transforms (GimpImage *image)
+{
+ GimpImagePrivate *private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ if (private->color_profile &&
+ ! private->color_transforms_created)
+ {
+ GimpColorProfile *srgb_profile;
+ GimpColorTransformFlags flags = 0;
+
+ srgb_profile = gimp_color_profile_new_rgb_srgb ();
+
+ flags |= GIMP_COLOR_TRANSFORM_FLAGS_NOOPTIMIZE;
+ flags |= GIMP_COLOR_TRANSFORM_FLAGS_BLACK_POINT_COMPENSATION;
+
+ private->transform_to_srgb_u8 =
+ gimp_color_transform_new (private->color_profile,
+ gimp_image_get_layer_format (image, TRUE),
+ srgb_profile,
+ babl_format ("R'G'B'A u8"),
+ GIMP_COLOR_RENDERING_INTENT_PERCEPTUAL,
+ flags);
+
+ private->transform_from_srgb_u8 =
+ gimp_color_transform_new (srgb_profile,
+ babl_format ("R'G'B'A u8"),
+ private->color_profile,
+ gimp_image_get_layer_format (image, TRUE),
+ GIMP_COLOR_RENDERING_INTENT_PERCEPTUAL,
+ flags);
+
+ private->transform_to_srgb_double =
+ gimp_color_transform_new (private->color_profile,
+ gimp_image_get_layer_format (image, TRUE),
+ srgb_profile,
+ babl_format ("R'G'B'A double"),
+ GIMP_COLOR_RENDERING_INTENT_PERCEPTUAL,
+ flags);
+
+ private->transform_from_srgb_double =
+ gimp_color_transform_new (srgb_profile,
+ babl_format ("R'G'B'A double"),
+ private->color_profile,
+ gimp_image_get_layer_format (image, TRUE),
+ GIMP_COLOR_RENDERING_INTENT_PERCEPTUAL,
+ flags);
+
+ g_object_unref (srgb_profile);
+
+ private->color_transforms_created = TRUE;
+ }
+}
diff --git a/app/core/gimpimage-color-profile.h b/app/core/gimpimage-color-profile.h
new file mode 100644
index 0000000..ae89038
--- /dev/null
+++ b/app/core/gimpimage-color-profile.h
@@ -0,0 +1,113 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpimage-color-profile.h
+ * Copyright (C) 2015 Michael Natterer <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_IMAGE_COLOR_PROFILE_H__
+#define __GIMP_IMAGE_COLOR_PROFILE_H__
+
+
+#define GIMP_ICC_PROFILE_PARASITE_NAME "icc-profile"
+
+
+gboolean gimp_image_get_is_color_managed (GimpImage *image);
+void gimp_image_set_is_color_managed (GimpImage *image,
+ gboolean is_color_managed,
+ gboolean push_undo);
+
+gboolean gimp_image_validate_icc_parasite (GimpImage *image,
+ const GimpParasite *icc_parasite,
+ gboolean *is_builtin,
+ GError **error);
+const GimpParasite * gimp_image_get_icc_parasite (GimpImage *image);
+void gimp_image_set_icc_parasite (GimpImage *image,
+ const GimpParasite *icc_parasite);
+
+gboolean gimp_image_validate_icc_profile (GimpImage *image,
+ const guint8 *data,
+ gsize length,
+ gboolean *is_builtin,
+ GError **error);
+const guint8 * gimp_image_get_icc_profile (GimpImage *image,
+ gsize *length);
+gboolean gimp_image_set_icc_profile (GimpImage *image,
+ const guint8 *data,
+ gsize length,
+ GError **error);
+
+gboolean gimp_image_validate_color_profile (GimpImage *image,
+ GimpColorProfile *profile,
+ gboolean *is_builtin,
+ GError **error);
+GimpColorProfile * gimp_image_get_color_profile (GimpImage *image);
+gboolean gimp_image_set_color_profile (GimpImage *image,
+ GimpColorProfile *profile,
+ GError **error);
+
+gboolean gimp_image_validate_color_profile_by_format
+ (const Babl *format,
+ GimpColorProfile *profile,
+ gboolean *is_builtin,
+ GError **error);
+
+GimpColorProfile * gimp_image_get_builtin_color_profile
+ (GimpImage *image);
+
+gboolean gimp_image_convert_color_profile (GimpImage *image,
+ GimpColorProfile *dest_profile,
+ GimpColorRenderingIntent intent,
+ gboolean bpc,
+ GimpProgress *progress,
+ GError **error);
+
+void gimp_image_import_color_profile (GimpImage *image,
+ GimpContext *context,
+ GimpProgress *progress,
+ gboolean interactive);
+
+GimpColorTransform * gimp_image_get_color_transform_to_srgb_u8
+ (GimpImage *image);
+GimpColorTransform * gimp_image_get_color_transform_from_srgb_u8
+ (GimpImage *image);
+
+GimpColorTransform * gimp_image_get_color_transform_to_srgb_double
+ (GimpImage *image);
+GimpColorTransform * gimp_image_get_color_transform_from_srgb_double
+ (GimpImage *image);
+
+void gimp_image_color_profile_pixel_to_srgb
+ (GimpImage *image,
+ const Babl *pixel_format,
+ gpointer pixel,
+ GimpRGB *color);
+void gimp_image_color_profile_srgb_to_pixel
+ (GimpImage *image,
+ const GimpRGB *color,
+ const Babl *pixel_format,
+ gpointer pixel);
+
+
+/* internal API, to be called only from gimpimage.c */
+
+void _gimp_image_free_color_profile (GimpImage *image);
+void _gimp_image_free_color_transforms (GimpImage *image);
+void _gimp_image_update_color_profile (GimpImage *image,
+ const GimpParasite *icc_parasite);
+
+
+#endif /* __GIMP_IMAGE_COLOR_PROFILE_H__ */
diff --git a/app/core/gimpimage-colormap.c b/app/core/gimpimage-colormap.c
new file mode 100644
index 0000000..8bf40a9
--- /dev/null
+++ b/app/core/gimpimage-colormap.c
@@ -0,0 +1,362 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <cairo.h>
+#include <gegl.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+#include "libgimpcolor/gimpcolor.h"
+
+#include "core-types.h"
+
+#include "gimp.h"
+#include "gimpcontainer.h"
+#include "gimpdatafactory.h"
+#include "gimpimage.h"
+#include "gimpimage-colormap.h"
+#include "gimpimage-private.h"
+#include "gimpimage-undo-push.h"
+#include "gimppalette.h"
+
+#include "gimp-intl.h"
+
+
+/* local function prototype */
+
+static void gimp_image_colormap_set_palette_entry (GimpImage *image,
+ const GimpRGB *color,
+ gint index);
+
+
+/* public functions */
+
+void
+gimp_image_colormap_init (GimpImage *image)
+{
+ GimpImagePrivate *private;
+ GimpContainer *palettes;
+ gchar *palette_name;
+ gchar *palette_id;
+
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+
+ private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ g_return_if_fail (private->colormap == NULL);
+ g_return_if_fail (private->palette == NULL);
+
+ palette_name = g_strdup_printf (_("Colormap of Image #%d (%s)"),
+ gimp_image_get_ID (image),
+ gimp_image_get_display_name (image));
+ palette_id = g_strdup_printf ("gimp-indexed-image-palette-%d",
+ gimp_image_get_ID (image));
+
+ private->n_colors = 0;
+ private->colormap = g_new0 (guchar, GIMP_IMAGE_COLORMAP_SIZE);
+ private->palette = GIMP_PALETTE (gimp_palette_new (NULL, palette_name));
+
+ if (! private->babl_palette_rgb)
+ {
+ gchar *format_name = g_strdup_printf ("-gimp-indexed-format-%d",
+ gimp_image_get_ID (image));
+
+ babl_new_palette (format_name,
+ &private->babl_palette_rgb,
+ &private->babl_palette_rgba);
+
+ g_free (format_name);
+ }
+
+ gimp_palette_set_columns (private->palette, 16);
+
+ gimp_data_make_internal (GIMP_DATA (private->palette), palette_id);
+
+ palettes = gimp_data_factory_get_container (image->gimp->palette_factory);
+
+ gimp_container_add (palettes, GIMP_OBJECT (private->palette));
+
+ g_free (palette_name);
+ g_free (palette_id);
+}
+
+void
+gimp_image_colormap_dispose (GimpImage *image)
+{
+ GimpImagePrivate *private;
+ GimpContainer *palettes;
+
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+
+ private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ g_return_if_fail (private->colormap != NULL);
+ g_return_if_fail (GIMP_IS_PALETTE (private->palette));
+
+ palettes = gimp_data_factory_get_container (image->gimp->palette_factory);
+
+ gimp_container_remove (palettes, GIMP_OBJECT (private->palette));
+}
+
+void
+gimp_image_colormap_free (GimpImage *image)
+{
+ GimpImagePrivate *private;
+
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+
+ private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ g_return_if_fail (private->colormap != NULL);
+ g_return_if_fail (GIMP_IS_PALETTE (private->palette));
+
+ g_clear_pointer (&private->colormap, g_free);
+ g_clear_object (&private->palette);
+
+ /* don't touch the image's babl_palettes because we might still have
+ * buffers with that palette on the undo stack, and on undoing the
+ * image back to indexed, we must have exactly these palettes around
+ */
+}
+
+const Babl *
+gimp_image_colormap_get_rgb_format (GimpImage *image)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+
+ return GIMP_IMAGE_GET_PRIVATE (image)->babl_palette_rgb;
+}
+
+const Babl *
+gimp_image_colormap_get_rgba_format (GimpImage *image)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+
+ return GIMP_IMAGE_GET_PRIVATE (image)->babl_palette_rgba;
+}
+
+GimpPalette *
+gimp_image_get_colormap_palette (GimpImage *image)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+
+ return GIMP_IMAGE_GET_PRIVATE (image)->palette;
+}
+
+const guchar *
+gimp_image_get_colormap (GimpImage *image)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+
+ return GIMP_IMAGE_GET_PRIVATE (image)->colormap;
+}
+
+gint
+gimp_image_get_colormap_size (GimpImage *image)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), 0);
+
+ return GIMP_IMAGE_GET_PRIVATE (image)->n_colors;
+}
+
+void
+gimp_image_set_colormap (GimpImage *image,
+ const guchar *colormap,
+ gint n_colors,
+ gboolean push_undo)
+{
+ GimpImagePrivate *private;
+ GimpPaletteEntry *entry;
+ gint i;
+
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+ g_return_if_fail (colormap != NULL || n_colors == 0);
+ g_return_if_fail (n_colors >= 0 && n_colors <= 256);
+
+ private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ if (push_undo)
+ gimp_image_undo_push_image_colormap (image, C_("undo-type", "Set Colormap"));
+
+ if (private->colormap)
+ memset (private->colormap, 0, GIMP_IMAGE_COLORMAP_SIZE);
+ else
+ gimp_image_colormap_init (image);
+
+ if (colormap)
+ memcpy (private->colormap, colormap, n_colors * 3);
+
+ /* make sure the image's colormap always has at least one color. when
+ * n_colors == 0, use black.
+ */
+ private->n_colors = MAX (n_colors, 1);
+
+ gimp_data_freeze (GIMP_DATA (private->palette));
+
+ while ((entry = gimp_palette_get_entry (private->palette, 0)))
+ gimp_palette_delete_entry (private->palette, entry);
+
+ for (i = 0; i < private->n_colors; i++)
+ gimp_image_colormap_set_palette_entry (image, NULL, i);
+
+ gimp_data_thaw (GIMP_DATA (private->palette));
+
+ gimp_image_colormap_changed (image, -1);
+}
+
+void
+gimp_image_unset_colormap (GimpImage *image,
+ gboolean push_undo)
+{
+ GimpImagePrivate *private;
+
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+
+ private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ if (push_undo)
+ gimp_image_undo_push_image_colormap (image,
+ C_("undo-type", "Unset Colormap"));
+
+ if (private->colormap)
+ {
+ gimp_image_colormap_dispose (image);
+ gimp_image_colormap_free (image);
+ }
+
+ private->n_colors = 0;
+
+ gimp_image_colormap_changed (image, -1);
+}
+
+void
+gimp_image_get_colormap_entry (GimpImage *image,
+ gint color_index,
+ GimpRGB *color)
+{
+ GimpImagePrivate *private;
+
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+
+ private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ g_return_if_fail (private->colormap != NULL);
+ g_return_if_fail (color_index >= 0 && color_index < private->n_colors);
+ g_return_if_fail (color != NULL);
+
+ gimp_rgba_set_uchar (color,
+ private->colormap[color_index * 3],
+ private->colormap[color_index * 3 + 1],
+ private->colormap[color_index * 3 + 2],
+ 255);
+}
+
+void
+gimp_image_set_colormap_entry (GimpImage *image,
+ gint color_index,
+ const GimpRGB *color,
+ gboolean push_undo)
+{
+ GimpImagePrivate *private;
+
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+
+ private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ g_return_if_fail (private->colormap != NULL);
+ g_return_if_fail (color_index >= 0 && color_index < private->n_colors);
+ g_return_if_fail (color != NULL);
+
+ if (push_undo)
+ gimp_image_undo_push_image_colormap (image,
+ C_("undo-type", "Change Colormap entry"));
+
+ gimp_rgb_get_uchar (color,
+ &private->colormap[color_index * 3],
+ &private->colormap[color_index * 3 + 1],
+ &private->colormap[color_index * 3 + 2]);
+
+ if (private->palette)
+ gimp_image_colormap_set_palette_entry (image, color, color_index);
+
+ gimp_image_colormap_changed (image, color_index);
+}
+
+void
+gimp_image_add_colormap_entry (GimpImage *image,
+ const GimpRGB *color)
+{
+ GimpImagePrivate *private;
+
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+
+ private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ g_return_if_fail (private->colormap != NULL);
+ g_return_if_fail (private->n_colors < 256);
+ g_return_if_fail (color != NULL);
+
+ gimp_image_undo_push_image_colormap (image,
+ C_("undo-type", "Add Color to Colormap"));
+
+ gimp_rgb_get_uchar (color,
+ &private->colormap[private->n_colors * 3],
+ &private->colormap[private->n_colors * 3 + 1],
+ &private->colormap[private->n_colors * 3 + 2]);
+
+ private->n_colors++;
+
+ if (private->palette)
+ gimp_image_colormap_set_palette_entry (image, color, private->n_colors - 1);
+
+ gimp_image_colormap_changed (image, -1);
+}
+
+
+/* private functions */
+
+static void
+gimp_image_colormap_set_palette_entry (GimpImage *image,
+ const GimpRGB *c,
+ gint index)
+{
+ GimpImagePrivate *private = GIMP_IMAGE_GET_PRIVATE (image);
+ GimpRGB color;
+ gchar name[64];
+
+ /* Avoid converting to char then back to double if we have the
+ * original GimpRGB color.
+ */
+ if (c)
+ color = *c;
+ else
+ gimp_rgba_set_uchar (&color,
+ private->colormap[3 * index + 0],
+ private->colormap[3 * index + 1],
+ private->colormap[3 * index + 2],
+ 255);
+
+ g_snprintf (name, sizeof (name), "#%d", index);
+
+ if (gimp_palette_get_n_colors (private->palette) < private->n_colors)
+ gimp_palette_add_entry (private->palette, index, name, &color);
+ else
+ gimp_palette_set_entry (private->palette, index, name, &color);
+}
diff --git a/app/core/gimpimage-colormap.h b/app/core/gimpimage-colormap.h
new file mode 100644
index 0000000..703f5a2
--- /dev/null
+++ b/app/core/gimpimage-colormap.h
@@ -0,0 +1,55 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattisbvf
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_IMAGE_COLORMAP_H__
+#define __GIMP_IMAGE_COLORMAP_H__
+
+
+#define GIMP_IMAGE_COLORMAP_SIZE 768
+
+
+void gimp_image_colormap_init (GimpImage *image);
+void gimp_image_colormap_dispose (GimpImage *image);
+void gimp_image_colormap_free (GimpImage *image);
+
+const Babl * gimp_image_colormap_get_rgb_format (GimpImage *image);
+const Babl * gimp_image_colormap_get_rgba_format (GimpImage *image);
+
+GimpPalette * gimp_image_get_colormap_palette (GimpImage *image);
+
+const guchar * gimp_image_get_colormap (GimpImage *image);
+gint gimp_image_get_colormap_size (GimpImage *image);
+void gimp_image_set_colormap (GimpImage *image,
+ const guchar *colormap,
+ gint n_colors,
+ gboolean push_undo);
+void gimp_image_unset_colormap (GimpImage *image,
+ gboolean push_undo);
+
+void gimp_image_get_colormap_entry (GimpImage *image,
+ gint color_index,
+ GimpRGB *color);
+void gimp_image_set_colormap_entry (GimpImage *image,
+ gint color_index,
+ const GimpRGB *color,
+ gboolean push_undo);
+
+void gimp_image_add_colormap_entry (GimpImage *image,
+ const GimpRGB *color);
+
+
+#endif /* __GIMP_IMAGE_COLORMAP_H__ */
diff --git a/app/core/gimpimage-convert-data.h b/app/core/gimpimage-convert-data.h
new file mode 100644
index 0000000..dd0dd11
--- /dev/null
+++ b/app/core/gimpimage-convert-data.h
@@ -0,0 +1,143 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+/* Misc data definitions used by the convert.c code module. Moved
+ out here simply to unclutter convert.c, mostly. */
+
+#ifndef __GIMP_IMAGE_CONVERT_DATA_H__
+#define __GIMP_IMAGE_CONVERT_DATA_H__
+
+#include <glib.h>
+
+/* 'web safe' palette. */
+static const guchar webpal[] =
+{
+ 255,255,255,255,255,204,255,255,153,255,255,102,255,255,51,255,255,0,255,
+ 204,255,255,204,204,255,204,153,255,204,102,255,204,51,255,204,0,255,153,
+ 255,255,153,204,255,153,153,255,153,102,255,153,51,255,153,0,255,102,255,
+ 255,102,204,255,102,153,255,102,102,255,102,51,255,102,0,255,51,255,255,
+ 51,204,255,51,153,255,51,102,255,51,51,255,51,0,255,0,255,255,0,
+ 204,255,0,153,255,0,102,255,0,51,255,0,0,204,255,255,204,255,204,
+ 204,255,153,204,255,102,204,255,51,204,255,0,204,204,255,204,204,204,204,
+ 204,153,204,204,102,204,204,51,204,204,0,204,153,255,204,153,204,204,153,
+ 153,204,153,102,204,153,51,204,153,0,204,102,255,204,102,204,204,102,153,
+ 204,102,102,204,102,51,204,102,0,204,51,255,204,51,204,204,51,153,204,
+ 51,102,204,51,51,204,51,0,204,0,255,204,0,204,204,0,153,204,0,
+ 102,204,0,51,204,0,0,153,255,255,153,255,204,153,255,153,153,255,102,
+ 153,255,51,153,255,0,153,204,255,153,204,204,153,204,153,153,204,102,153,
+ 204,51,153,204,0,153,153,255,153,153,204,153,153,153,153,153,102,153,153,
+ 51,153,153,0,153,102,255,153,102,204,153,102,153,153,102,102,153,102,51,
+ 153,102,0,153,51,255,153,51,204,153,51,153,153,51,102,153,51,51,153,
+ 51,0,153,0,255,153,0,204,153,0,153,153,0,102,153,0,51,153,0,
+ 0,102,255,255,102,255,204,102,255,153,102,255,102,102,255,51,102,255,0,
+ 102,204,255,102,204,204,102,204,153,102,204,102,102,204,51,102,204,0,102,
+ 153,255,102,153,204,102,153,153,102,153,102,102,153,51,102,153,0,102,102,
+ 255,102,102,204,102,102,153,102,102,102,102,102,51,102,102,0,102,51,255,
+ 102,51,204,102,51,153,102,51,102,102,51,51,102,51,0,102,0,255,102,
+ 0,204,102,0,153,102,0,102,102,0,51,102,0,0,51,255,255,51,255,
+ 204,51,255,153,51,255,102,51,255,51,51,255,0,51,204,255,51,204,204,
+ 51,204,153,51,204,102,51,204,51,51,204,0,51,153,255,51,153,204,51,
+ 153,153,51,153,102,51,153,51,51,153,0,51,102,255,51,102,204,51,102,
+ 153,51,102,102,51,102,51,51,102,0,51,51,255,51,51,204,51,51,153,
+ 51,51,102,51,51,51,51,51,0,51,0,255,51,0,204,51,0,153,51,
+ 0,102,51,0,51,51,0,0,0,255,255,0,255,204,0,255,153,0,255,
+ 102,0,255,51,0,255,0,0,204,255,0,204,204,0,204,153,0,204,102,
+ 0,204,51,0,204,0,0,153,255,0,153,204,0,153,153,0,153,102,0,
+ 153,51,0,153,0,0,102,255,0,102,204,0,102,153,0,102,102,0,102,
+ 51,0,102,0,0,51,255,0,51,204,0,51,153,0,51,102,0,51,51,
+ 0,51,0,0,0,255,0,0,204,0,0,153,0,0,102,0,0,51,0,0,0
+};
+
+
+/* the dither matrix, used for alpha dither and 'positional' dither */
+#define DM_WIDTH 32
+#define DM_WIDTHMASK ((DM_WIDTH)-1)
+#define DM_HEIGHT 32
+#define DM_HEIGHTMASK ((DM_HEIGHT)-1)
+/* matrix values should be scaled/biased to 1..255 range */
+/* this array is not const because it may be overwritten. */
+static guchar DM[32][32] = {
+ { 1,191, 48,239, 12,203, 60,251, 3,194, 51,242, 15,206, 63,254, 1,192, 49,240, 13,204, 61,252, 4,195, 52,243, 16,207, 64,255},
+ {128, 64,175,112,140, 76,187,124,131, 67,178,115,143, 79,190,127,128, 65,176,112,140, 77,188,124,131, 68,179,115,143, 80,191,127},
+ { 32,223, 16,207, 44,235, 28,219, 35,226, 19,210, 47,238, 31,222, 33,224, 17,208, 45,236, 29,220, 36,227, 20,211, 48,239, 32,223},
+ {159, 96,144, 80,171,108,155, 92,162, 99,146, 83,174,111,158, 95,160, 97,144, 81,172,109,156, 93,163,100,147, 84,175,111,159, 96},
+ { 8,199, 56,247, 4,195, 52,243, 11,202, 59,250, 7,198, 55,246, 9,200, 57,248, 5,196, 53,244, 12,203, 60,251, 8,199, 56,247},
+ {136, 72,183,120,132, 68,179,116,139, 75,186,123,135, 71,182,119,136, 73,184,120,132, 69,180,116,139, 76,187,123,135, 72,183,119},
+ { 40,231, 24,215, 36,227, 20,211, 43,234, 27,218, 39,230, 23,214, 41,232, 25,216, 37,228, 21,212, 44,235, 28,219, 40,231, 24,215},
+ {167,104,151, 88,163,100,147, 84,170,107,154, 91,166,103,150, 87,168,105,152, 89,164,101,148, 85,171,108,155, 92,167,104,151, 88},
+ { 2,193, 50,241, 14,205, 62,253, 1,192, 49,240, 13,204, 61,252, 3,194, 51,242, 15,206, 63,254, 2,193, 50,241, 14,205, 62,253},
+ {130, 66,177,114,142, 78,189,126,129, 65,176,113,141, 77,188,125,130, 67,178,114,142, 79,190,126,129, 66,177,113,141, 78,189,125},
+ { 34,225, 18,209, 46,237, 30,221, 33,224, 17,208, 45,236, 29,220, 35,226, 19,210, 47,238, 31,222, 34,225, 18,209, 46,237, 30,221},
+ {161, 98,146, 82,173,110,157, 94,160, 97,145, 81,172,109,156, 93,162, 99,146, 83,174,110,158, 95,161, 98,145, 82,173,109,157, 94},
+ { 10,201, 58,249, 6,197, 54,245, 9,200, 57,248, 5,196, 53,244, 11,202, 59,250, 7,198, 55,246, 10,201, 58,249, 6,197, 54,245},
+ {138, 74,185,122,134, 70,181,118,137, 73,184,121,133, 69,180,117,138, 75,186,122,134, 71,182,118,137, 74,185,121,133, 70,181,117},
+ { 42,233, 26,217, 38,229, 22,213, 41,232, 25,216, 37,228, 21,212, 43,234, 27,218, 39,230, 23,214, 42,233, 26,217, 38,229, 22,213},
+ {169,106,153, 90,165,102,149, 86,168,105,152, 89,164,101,148, 85,170,107,154, 91,166,103,150, 87,169,106,153, 90,165,102,149, 86},
+ { 1,192, 49,239, 13,204, 61,251, 4,195, 52,242, 16,207, 64,254, 1,191, 48,239, 13,203, 60,251, 4,194, 51,242, 16,206, 63,254},
+ {128, 65,176,112,140, 76,188,124,131, 68,179,115,143, 79,191,127,128, 64,176,112,140, 76,187,124,131, 67,179,115,143, 79,190,127},
+ { 33,223, 17,208, 45,235, 29,219, 36,226, 20,211, 48,238, 32,222, 33,223, 17,207, 44,235, 29,219, 36,226, 20,210, 47,238, 32,222},
+ {160, 96,144, 80,172,108,156, 92,163, 99,147, 83,175,111,159, 95,160, 96,144, 80,172,108,156, 92,163, 99,147, 83,175,111,159, 95},
+ { 9,200, 57,247, 5,196, 53,243, 12,203, 60,250, 8,199, 56,246, 9,199, 56,247, 5,195, 52,243, 12,202, 59,250, 8,198, 55,246},
+ {136, 73,184,120,132, 69,180,116,139, 75,187,123,135, 72,183,119,136, 72,183,120,132, 68,180,116,139, 75,186,123,135, 71,182,119},
+ { 41,231, 25,216, 37,227, 21,212, 44,234, 28,218, 40,230, 24,215, 40,231, 25,215, 37,227, 21,211, 43,234, 28,218, 39,230, 24,214},
+ {168,104,152, 88,164,100,148, 84,171,107,155, 91,167,103,151, 87,168,104,152, 88,164,100,148, 84,171,107,155, 91,167,103,151, 87},
+ { 3,194, 51,241, 15,206, 63,253, 2,193, 50,240, 14,205, 62,252, 3,193, 50,241, 15,205, 62,253, 2,192, 49,240, 14,204, 61,252},
+ {130, 67,178,114,142, 78,190,126,129, 66,177,113,141, 77,189,125,130, 66,178,114,142, 78,189,126,129, 65,177,113,141, 77,188,125},
+ { 35,225, 19,210, 47,237, 31,221, 34,224, 18,209, 46,236, 30,220, 35,225, 19,209, 46,237, 31,221, 34,224, 18,208, 45,236, 30,220},
+ {162, 98,146, 82,174,110,158, 94,161, 97,145, 81,173,109,157, 93,162, 98,146, 82,174,110,158, 94,161, 97,145, 81,173,109,157, 93},
+ { 11,202, 59,249, 7,198, 55,245, 10,201, 58,248, 6,197, 54,244, 11,201, 58,249, 7,197, 54,245, 10,200, 57,248, 6,196, 53,244},
+ {138, 74,186,122,134, 71,182,118,137, 73,185,121,133, 70,181,117,138, 74,185,122,134, 70,182,118,137, 73,184,121,133, 69,181,117},
+ { 43,233, 27,218, 39,229, 23,214, 42,232, 26,217, 38,228, 22,213, 42,233, 27,217, 38,229, 23,213, 41,232, 26,216, 37,228, 22,212},
+ {170,106,154, 90,166,102,150, 86,169,105,153, 89,165,101,149, 85,170,106,154, 90,166,102,150, 86,169,105,153, 89,165,101,149, 85}
+};
+
+static const guchar DM_ORIGINAL[32][32] = {
+ { 1,191, 48,239, 12,203, 60,251, 3,194, 51,242, 15,206, 63,254, 1,192, 49,240, 13,204, 61,252, 4,195, 52,243, 16,207, 64,255},
+ {128, 64,175,112,140, 76,187,124,131, 67,178,115,143, 79,190,127,128, 65,176,112,140, 77,188,124,131, 68,179,115,143, 80,191,127},
+ { 32,223, 16,207, 44,235, 28,219, 35,226, 19,210, 47,238, 31,222, 33,224, 17,208, 45,236, 29,220, 36,227, 20,211, 48,239, 32,223},
+ {159, 96,144, 80,171,108,155, 92,162, 99,146, 83,174,111,158, 95,160, 97,144, 81,172,109,156, 93,163,100,147, 84,175,111,159, 96},
+ { 8,199, 56,247, 4,195, 52,243, 11,202, 59,250, 7,198, 55,246, 9,200, 57,248, 5,196, 53,244, 12,203, 60,251, 8,199, 56,247},
+ {136, 72,183,120,132, 68,179,116,139, 75,186,123,135, 71,182,119,136, 73,184,120,132, 69,180,116,139, 76,187,123,135, 72,183,119},
+ { 40,231, 24,215, 36,227, 20,211, 43,234, 27,218, 39,230, 23,214, 41,232, 25,216, 37,228, 21,212, 44,235, 28,219, 40,231, 24,215},
+ {167,104,151, 88,163,100,147, 84,170,107,154, 91,166,103,150, 87,168,105,152, 89,164,101,148, 85,171,108,155, 92,167,104,151, 88},
+ { 2,193, 50,241, 14,205, 62,253, 1,192, 49,240, 13,204, 61,252, 3,194, 51,242, 15,206, 63,254, 2,193, 50,241, 14,205, 62,253},
+ {130, 66,177,114,142, 78,189,126,129, 65,176,113,141, 77,188,125,130, 67,178,114,142, 79,190,126,129, 66,177,113,141, 78,189,125},
+ { 34,225, 18,209, 46,237, 30,221, 33,224, 17,208, 45,236, 29,220, 35,226, 19,210, 47,238, 31,222, 34,225, 18,209, 46,237, 30,221},
+ {161, 98,146, 82,173,110,157, 94,160, 97,145, 81,172,109,156, 93,162, 99,146, 83,174,110,158, 95,161, 98,145, 82,173,109,157, 94},
+ { 10,201, 58,249, 6,197, 54,245, 9,200, 57,248, 5,196, 53,244, 11,202, 59,250, 7,198, 55,246, 10,201, 58,249, 6,197, 54,245},
+ {138, 74,185,122,134, 70,181,118,137, 73,184,121,133, 69,180,117,138, 75,186,122,134, 71,182,118,137, 74,185,121,133, 70,181,117},
+ { 42,233, 26,217, 38,229, 22,213, 41,232, 25,216, 37,228, 21,212, 43,234, 27,218, 39,230, 23,214, 42,233, 26,217, 38,229, 22,213},
+ {169,106,153, 90,165,102,149, 86,168,105,152, 89,164,101,148, 85,170,107,154, 91,166,103,150, 87,169,106,153, 90,165,102,149, 86},
+ { 1,192, 49,239, 13,204, 61,251, 4,195, 52,242, 16,207, 64,254, 1,191, 48,239, 13,203, 60,251, 4,194, 51,242, 16,206, 63,254},
+ {128, 65,176,112,140, 76,188,124,131, 68,179,115,143, 79,191,127,128, 64,176,112,140, 76,187,124,131, 67,179,115,143, 79,190,127},
+ { 33,223, 17,208, 45,235, 29,219, 36,226, 20,211, 48,238, 32,222, 33,223, 17,207, 44,235, 29,219, 36,226, 20,210, 47,238, 32,222},
+ {160, 96,144, 80,172,108,156, 92,163, 99,147, 83,175,111,159, 95,160, 96,144, 80,172,108,156, 92,163, 99,147, 83,175,111,159, 95},
+ { 9,200, 57,247, 5,196, 53,243, 12,203, 60,250, 8,199, 56,246, 9,199, 56,247, 5,195, 52,243, 12,202, 59,250, 8,198, 55,246},
+ {136, 73,184,120,132, 69,180,116,139, 75,187,123,135, 72,183,119,136, 72,183,120,132, 68,180,116,139, 75,186,123,135, 71,182,119},
+ { 41,231, 25,216, 37,227, 21,212, 44,234, 28,218, 40,230, 24,215, 40,231, 25,215, 37,227, 21,211, 43,234, 28,218, 39,230, 24,214},
+ {168,104,152, 88,164,100,148, 84,171,107,155, 91,167,103,151, 87,168,104,152, 88,164,100,148, 84,171,107,155, 91,167,103,151, 87},
+ { 3,194, 51,241, 15,206, 63,253, 2,193, 50,240, 14,205, 62,252, 3,193, 50,241, 15,205, 62,253, 2,192, 49,240, 14,204, 61,252},
+ {130, 67,178,114,142, 78,190,126,129, 66,177,113,141, 77,189,125,130, 66,178,114,142, 78,189,126,129, 65,177,113,141, 77,188,125},
+ { 35,225, 19,210, 47,237, 31,221, 34,224, 18,209, 46,236, 30,220, 35,225, 19,209, 46,237, 31,221, 34,224, 18,208, 45,236, 30,220},
+ {162, 98,146, 82,174,110,158, 94,161, 97,145, 81,173,109,157, 93,162, 98,146, 82,174,110,158, 94,161, 97,145, 81,173,109,157, 93},
+ { 11,202, 59,249, 7,198, 55,245, 10,201, 58,248, 6,197, 54,244, 11,201, 58,249, 7,197, 54,245, 10,200, 57,248, 6,196, 53,244},
+ {138, 74,186,122,134, 71,182,118,137, 73,185,121,133, 70,181,117,138, 74,185,122,134, 70,182,118,137, 73,184,121,133, 69,181,117},
+ { 43,233, 27,218, 39,229, 23,214, 42,232, 26,217, 38,228, 22,213, 42,233, 27,217, 38,229, 23,213, 41,232, 26,216, 37,228, 22,212},
+ {170,106,154, 90,166,102,150, 86,169,105,153, 89,165,101,149, 85,170,106,154, 90,166,102,150, 86,169,105,153, 89,165,101,149, 85}
+};
+
+#endif /* __GIMP_IMAGE_CONVERT_DATA_H__ */
diff --git a/app/core/gimpimage-convert-fsdither.h b/app/core/gimpimage-convert-fsdither.h
new file mode 100644
index 0000000..ebd9800
--- /dev/null
+++ b/app/core/gimpimage-convert-fsdither.h
@@ -0,0 +1,559 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_IMAGE_CONVERT_FSDITHER_H__
+#define __GIMP_IMAGE_CONVERT_FSDITHER_H__
+
+/* The following 5 arrays are used in performing floyd-steinberg
+ * error diffusion dithering. The range array allows the quick
+ * bounds checking of pixel values. The 4 error arrays contain
+ * the error computations for the east, south-east, south and
+ * south-west pixels surrounding the current pixel respectively.
+ */
+
+
+static const guchar range_array[] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 1, 2, 3,
+ 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,
+ 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
+ 24, 25, 26, 27, 28, 29, 30, 31, 32, 33,
+ 34, 35, 36, 37, 38, 39, 40, 41, 42, 43,
+ 44, 45, 46, 47, 48, 49, 50, 51, 52, 53,
+ 54, 55, 56, 57, 58, 59, 60, 61, 62, 63,
+ 64, 65, 66, 67, 68, 69, 70, 71, 72, 73,
+ 74, 75, 76, 77, 78, 79, 80, 81, 82, 83,
+ 84, 85, 86, 87, 88, 89, 90, 91, 92, 93,
+ 94, 95, 96, 97, 98, 99, 100, 101, 102, 103,
+ 104, 105, 106, 107, 108, 109, 110, 111, 112, 113,
+ 114, 115, 116, 117, 118, 119, 120, 121, 122, 123,
+ 124, 125, 126, 127, 128, 129, 130, 131, 132, 133,
+ 134, 135, 136, 137, 138, 139, 140, 141, 142, 143,
+ 144, 145, 146, 147, 148, 149, 150, 151, 152, 153,
+ 154, 155, 156, 157, 158, 159, 160, 161, 162, 163,
+ 164, 165, 166, 167, 168, 169, 170, 171, 172, 173,
+ 174, 175, 176, 177, 178, 179, 180, 181, 182, 183,
+ 184, 185, 186, 187, 188, 189, 190, 191, 192, 193,
+ 194, 195, 196, 197, 198, 199, 200, 201, 202, 203,
+ 204, 205, 206, 207, 208, 209, 210, 211, 212, 213,
+ 214, 215, 216, 217, 218, 219, 220, 221, 222, 223,
+ 224, 225, 226, 227, 228, 229, 230, 231, 232, 233,
+ 234, 235, 236, 237, 238, 239, 240, 241, 242, 243,
+ 244, 245, 246, 247, 248, 249, 250, 251, 252, 253,
+ 254, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255,
+};
+
+static const gshort floyd_steinberg_error1[] = {
+ -223, -223, -222, -222, -221, -221, -220, -220, -220, -219,
+ -219, -218, -218, -217, -217, -217, -216, -216, -215, -215,
+ -214, -214, -213, -213, -213, -212, -212, -211, -211, -210,
+ -210, -210, -209, -209, -208, -208, -207, -207, -206, -206,
+ -206, -205, -205, -204, -204, -203, -203, -203, -202, -202,
+ -201, -201, -200, -200, -199, -199, -199, -198, -198, -197,
+ -197, -196, -196, -196, -195, -195, -194, -194, -193, -193,
+ -192, -192, -192, -191, -191, -190, -190, -189, -189, -189,
+ -188, -188, -187, -187, -186, -186, -185, -185, -185, -184,
+ -184, -183, -183, -182, -182, -182, -181, -181, -180, -180,
+ -179, -179, -178, -178, -178, -177, -177, -176, -176, -175,
+ -175, -175, -174, -174, -173, -173, -172, -172, -171, -171,
+ -171, -170, -170, -169, -169, -168, -168, -168, -167, -167,
+ -166, -166, -165, -165, -164, -164, -164, -163, -163, -162,
+ -162, -161, -161, -161, -160, -160, -159, -159, -158, -158,
+ -157, -157, -157, -156, -156, -155, -155, -154, -154, -154,
+ -153, -153, -152, -152, -151, -151, -150, -150, -150, -149,
+ -149, -148, -148, -147, -147, -147, -146, -146, -145, -145,
+ -144, -144, -143, -143, -143, -142, -142, -141, -141, -140,
+ -140, -140, -139, -139, -138, -138, -137, -137, -136, -136,
+ -136, -135, -135, -134, -134, -133, -133, -133, -132, -132,
+ -131, -131, -130, -130, -129, -129, -129, -128, -128, -127,
+ -127, -126, -126, -126, -125, -125, -124, -124, -123, -123,
+ -122, -122, -122, -121, -121, -120, -120, -119, -119, -119,
+ -118, -118, -117, -117, -116, -116, -115, -115, -115, -114,
+ -114, -113, -113, -112, -112, -112, -111, -111, -110, -110,
+ -109, -109, -108, -108, -108, -107, -107, -106, -106, -105,
+ -105, -105, -104, -104, -103, -103, -102, -102, -101, -101,
+ -101, -100, -100, -99, -99, -98, -98, -98, -97, -97,
+ -96, -96, -95, -95, -94, -94, -94, -93, -93, -92,
+ -92, -91, -91, -91, -90, -90, -89, -89, -88, -88,
+ -87, -87, -87, -86, -86, -85, -85, -84, -84, -84,
+ -83, -83, -82, -82, -81, -81, -80, -80, -80, -79,
+ -79, -78, -78, -77, -77, -77, -76, -76, -75, -75,
+ -74, -74, -73, -73, -73, -72, -72, -71, -71, -70,
+ -70, -70, -69, -69, -68, -68, -67, -67, -66, -66,
+ -66, -65, -65, -64, -64, -63, -63, -63, -62, -62,
+ -61, -61, -60, -60, -59, -59, -59, -58, -58, -57,
+ -57, -56, -56, -56, -55, -55, -54, -54, -53, -53,
+ -52, -52, -52, -51, -51, -50, -50, -49, -49, -49,
+ -48, -48, -47, -47, -46, -46, -45, -45, -45, -44,
+ -44, -43, -43, -42, -42, -42, -41, -41, -40, -40,
+ -39, -39, -38, -38, -38, -37, -37, -36, -36, -35,
+ -35, -35, -34, -34, -33, -33, -32, -32, -31, -31,
+ -31, -30, -30, -29, -29, -28, -28, -28, -27, -27,
+ -26, -26, -25, -25, -24, -24, -24, -23, -23, -22,
+ -22, -21, -21, -21, -20, -20, -19, -19, -18, -18,
+ -17, -17, -17, -16, -16, -15, -15, -14, -14, -14,
+ -13, -13, -12, -12, -11, -11, -10, -10, -10, -9,
+ -9, -8, -8, -7, -7, -7, -6, -6, -5, -5,
+ -4, -4, -3, -3, -3, -2, -2, -1, -1, 0,
+ 0, 0, 0, 0, 1, 1, 2, 2, 3, 3,
+ 3, 4, 4, 5, 5, 6, 6, 7, 7, 7,
+ 8, 8, 9, 9, 10, 10, 10, 11, 11, 12,
+ 12, 13, 13, 14, 14, 14, 15, 15, 16, 16,
+ 17, 17, 17, 18, 18, 19, 19, 20, 20, 21,
+ 21, 21, 22, 22, 23, 23, 24, 24, 24, 25,
+ 25, 26, 26, 27, 27, 28, 28, 28, 29, 29,
+ 30, 30, 31, 31, 31, 32, 32, 33, 33, 34,
+ 34, 35, 35, 35, 36, 36, 37, 37, 38, 38,
+ 38, 39, 39, 40, 40, 41, 41, 42, 42, 42,
+ 43, 43, 44, 44, 45, 45, 45, 46, 46, 47,
+ 47, 48, 48, 49, 49, 49, 50, 50, 51, 51,
+ 52, 52, 52, 53, 53, 54, 54, 55, 55, 56,
+ 56, 56, 57, 57, 58, 58, 59, 59, 59, 60,
+ 60, 61, 61, 62, 62, 63, 63, 63, 64, 64,
+ 65, 65, 66, 66, 66, 67, 67, 68, 68, 69,
+ 69, 70, 70, 70, 71, 71, 72, 72, 73, 73,
+ 73, 74, 74, 75, 75, 76, 76, 77, 77, 77,
+ 78, 78, 79, 79, 80, 80, 80, 81, 81, 82,
+ 82, 83, 83, 84, 84, 84, 85, 85, 86, 86,
+ 87, 87, 87, 88, 88, 89, 89, 90, 90, 91,
+ 91, 91, 92, 92, 93, 93, 94, 94, 94, 95,
+ 95, 96, 96, 97, 97, 98, 98, 98, 99, 99,
+ 100, 100, 101, 101, 101, 102, 102, 103, 103, 104,
+ 104, 105, 105, 105, 106, 106, 107, 107, 108, 108,
+ 108, 109, 109, 110, 110, 111, 111, 112, 112, 112,
+ 113, 113, 114, 114, 115, 115, 115, 116, 116, 117,
+ 117, 118, 118, 119, 119, 119, 120, 120, 121, 121,
+ 122, 122, 122, 123, 123, 124, 124, 125, 125, 126,
+ 126, 126, 127, 127, 128, 128, 129, 129, 129, 130,
+ 130, 131, 131, 132, 132, 133, 133, 133, 134, 134,
+ 135, 135, 136, 136, 136, 137, 137, 138, 138, 139,
+ 139, 140, 140, 140, 141, 141, 142, 142, 143, 143,
+ 143, 144, 144, 145, 145, 146, 146, 147, 147, 147,
+ 148, 148, 149, 149, 150, 150, 150, 151, 151, 152,
+ 152, 153, 153, 154, 154, 154, 155, 155, 156, 156,
+ 157, 157, 157, 158, 158, 159, 159, 160, 160, 161,
+ 161, 161, 162, 162, 163, 163, 164, 164, 164, 165,
+ 165, 166, 166, 167, 167, 168, 168, 168, 169, 169,
+ 170, 170, 171, 171, 171, 172, 172, 173, 173, 174,
+ 174, 175, 175, 175, 176, 176, 177, 177, 178, 178,
+ 178, 179, 179, 180, 180, 181, 181, 182, 182, 182,
+ 183, 183, 184, 184, 185, 185, 185, 186, 186, 187,
+ 187, 188, 188, 189, 189, 189, 190, 190, 191, 191,
+ 192, 192, 192, 193, 193, 194, 194, 195, 195, 196,
+ 196, 196, 197, 197, 198, 198, 199, 199, 199, 200,
+ 200, 201, 201, 202, 202, 203, 203, 203, 204, 204,
+ 205, 205, 206, 206, 206, 207, 207, 208, 208, 209,
+ 209, 210, 210, 210, 211, 211, 212, 212, 213, 213,
+ 213, 214, 214, 215, 215, 216, 216, 217, 217, 217,
+ 218, 218, 219, 219, 220, 220, 220, 221, 221, 222,
+ 222, 223, 223, 224, 224,
+};
+
+static const gshort floyd_steinberg_error2[] = {
+ -95, -95, -95, -95, -95, -94, -94, -94, -94, -94,
+ -93, -93, -93, -93, -93, -93, -92, -92, -92, -92,
+ -92, -91, -91, -91, -91, -91, -90, -90, -90, -90,
+ -90, -90, -89, -89, -89, -89, -89, -88, -88, -88,
+ -88, -88, -87, -87, -87, -87, -87, -87, -86, -86,
+ -86, -86, -86, -85, -85, -85, -85, -85, -84, -84,
+ -84, -84, -84, -84, -83, -83, -83, -83, -83, -82,
+ -82, -82, -82, -82, -81, -81, -81, -81, -81, -81,
+ -80, -80, -80, -80, -80, -79, -79, -79, -79, -79,
+ -78, -78, -78, -78, -78, -78, -77, -77, -77, -77,
+ -77, -76, -76, -76, -76, -76, -75, -75, -75, -75,
+ -75, -75, -74, -74, -74, -74, -74, -73, -73, -73,
+ -73, -73, -72, -72, -72, -72, -72, -72, -71, -71,
+ -71, -71, -71, -70, -70, -70, -70, -70, -69, -69,
+ -69, -69, -69, -69, -68, -68, -68, -68, -68, -67,
+ -67, -67, -67, -67, -66, -66, -66, -66, -66, -66,
+ -65, -65, -65, -65, -65, -64, -64, -64, -64, -64,
+ -63, -63, -63, -63, -63, -63, -62, -62, -62, -62,
+ -62, -61, -61, -61, -61, -61, -60, -60, -60, -60,
+ -60, -60, -59, -59, -59, -59, -59, -58, -58, -58,
+ -58, -58, -57, -57, -57, -57, -57, -57, -56, -56,
+ -56, -56, -56, -55, -55, -55, -55, -55, -54, -54,
+ -54, -54, -54, -54, -53, -53, -53, -53, -53, -52,
+ -52, -52, -52, -52, -51, -51, -51, -51, -51, -51,
+ -50, -50, -50, -50, -50, -49, -49, -49, -49, -49,
+ -48, -48, -48, -48, -48, -48, -47, -47, -47, -47,
+ -47, -46, -46, -46, -46, -46, -45, -45, -45, -45,
+ -45, -45, -44, -44, -44, -44, -44, -43, -43, -43,
+ -43, -43, -42, -42, -42, -42, -42, -42, -41, -41,
+ -41, -41, -41, -40, -40, -40, -40, -40, -39, -39,
+ -39, -39, -39, -39, -38, -38, -38, -38, -38, -37,
+ -37, -37, -37, -37, -36, -36, -36, -36, -36, -36,
+ -35, -35, -35, -35, -35, -34, -34, -34, -34, -34,
+ -33, -33, -33, -33, -33, -33, -32, -32, -32, -32,
+ -32, -31, -31, -31, -31, -31, -30, -30, -30, -30,
+ -30, -30, -29, -29, -29, -29, -29, -28, -28, -28,
+ -28, -28, -27, -27, -27, -27, -27, -27, -26, -26,
+ -26, -26, -26, -25, -25, -25, -25, -25, -24, -24,
+ -24, -24, -24, -24, -23, -23, -23, -23, -23, -22,
+ -22, -22, -22, -22, -21, -21, -21, -21, -21, -21,
+ -20, -20, -20, -20, -20, -19, -19, -19, -19, -19,
+ -18, -18, -18, -18, -18, -18, -17, -17, -17, -17,
+ -17, -16, -16, -16, -16, -16, -15, -15, -15, -15,
+ -15, -15, -14, -14, -14, -14, -14, -13, -13, -13,
+ -13, -13, -12, -12, -12, -12, -12, -12, -11, -11,
+ -11, -11, -11, -10, -10, -10, -10, -10, -9, -9,
+ -9, -9, -9, -9, -8, -8, -8, -8, -8, -7,
+ -7, -7, -7, -7, -6, -6, -6, -6, -6, -6,
+ -5, -5, -5, -5, -5, -4, -4, -4, -4, -4,
+ -3, -3, -3, -3, -3, -3, -2, -2, -2, -2,
+ -2, -1, -1, -1, -1, -1, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 1, 1, 1,
+ 1, 1, 2, 2, 2, 2, 2, 3, 3, 3,
+ 3, 3, 3, 4, 4, 4, 4, 4, 5, 5,
+ 5, 5, 5, 6, 6, 6, 6, 6, 6, 7,
+ 7, 7, 7, 7, 8, 8, 8, 8, 8, 9,
+ 9, 9, 9, 9, 9, 10, 10, 10, 10, 10,
+ 11, 11, 11, 11, 11, 12, 12, 12, 12, 12,
+ 12, 13, 13, 13, 13, 13, 14, 14, 14, 14,
+ 14, 15, 15, 15, 15, 15, 15, 16, 16, 16,
+ 16, 16, 17, 17, 17, 17, 17, 18, 18, 18,
+ 18, 18, 18, 19, 19, 19, 19, 19, 20, 20,
+ 20, 20, 20, 21, 21, 21, 21, 21, 21, 22,
+ 22, 22, 22, 22, 23, 23, 23, 23, 23, 24,
+ 24, 24, 24, 24, 24, 25, 25, 25, 25, 25,
+ 26, 26, 26, 26, 26, 27, 27, 27, 27, 27,
+ 27, 28, 28, 28, 28, 28, 29, 29, 29, 29,
+ 29, 30, 30, 30, 30, 30, 30, 31, 31, 31,
+ 31, 31, 32, 32, 32, 32, 32, 33, 33, 33,
+ 33, 33, 33, 34, 34, 34, 34, 34, 35, 35,
+ 35, 35, 35, 36, 36, 36, 36, 36, 36, 37,
+ 37, 37, 37, 37, 38, 38, 38, 38, 38, 39,
+ 39, 39, 39, 39, 39, 40, 40, 40, 40, 40,
+ 41, 41, 41, 41, 41, 42, 42, 42, 42, 42,
+ 42, 43, 43, 43, 43, 43, 44, 44, 44, 44,
+ 44, 45, 45, 45, 45, 45, 45, 46, 46, 46,
+ 46, 46, 47, 47, 47, 47, 47, 48, 48, 48,
+ 48, 48, 48, 49, 49, 49, 49, 49, 50, 50,
+ 50, 50, 50, 51, 51, 51, 51, 51, 51, 52,
+ 52, 52, 52, 52, 53, 53, 53, 53, 53, 54,
+ 54, 54, 54, 54, 54, 55, 55, 55, 55, 55,
+ 56, 56, 56, 56, 56, 57, 57, 57, 57, 57,
+ 57, 58, 58, 58, 58, 58, 59, 59, 59, 59,
+ 59, 60, 60, 60, 60, 60, 60, 61, 61, 61,
+ 61, 61, 62, 62, 62, 62, 62, 63, 63, 63,
+ 63, 63, 63, 64, 64, 64, 64, 64, 65, 65,
+ 65, 65, 65, 66, 66, 66, 66, 66, 66, 67,
+ 67, 67, 67, 67, 68, 68, 68, 68, 68, 69,
+ 69, 69, 69, 69, 69, 70, 70, 70, 70, 70,
+ 71, 71, 71, 71, 71, 72, 72, 72, 72, 72,
+ 72, 73, 73, 73, 73, 73, 74, 74, 74, 74,
+ 74, 75, 75, 75, 75, 75, 75, 76, 76, 76,
+ 76, 76, 77, 77, 77, 77, 77, 78, 78, 78,
+ 78, 78, 78, 79, 79, 79, 79, 79, 80, 80,
+ 80, 80, 80, 81, 81, 81, 81, 81, 81, 82,
+ 82, 82, 82, 82, 83, 83, 83, 83, 83, 84,
+ 84, 84, 84, 84, 84, 85, 85, 85, 85, 85,
+ 86, 86, 86, 86, 86, 87, 87, 87, 87, 87,
+ 87, 88, 88, 88, 88, 88, 89, 89, 89, 89,
+ 89, 90, 90, 90, 90, 90, 90, 91, 91, 91,
+ 91, 91, 92, 92, 92, 92, 92, 93, 93, 93,
+ 93, 93, 93, 94, 94, 94, 94, 94, 95, 95,
+ 95, 95, 95, 96, 96,
+};
+
+static const gshort floyd_steinberg_error3[] = {
+ -159, -159, -159, -158, -158, -158, -157, -157, -157, -156,
+ -156, -156, -155, -155, -155, -155, -154, -154, -154, -153,
+ -153, -153, -152, -152, -152, -151, -151, -151, -150, -150,
+ -150, -150, -149, -149, -149, -148, -148, -148, -147, -147,
+ -147, -146, -146, -146, -145, -145, -145, -145, -144, -144,
+ -144, -143, -143, -143, -142, -142, -142, -141, -141, -141,
+ -140, -140, -140, -140, -139, -139, -139, -138, -138, -138,
+ -137, -137, -137, -136, -136, -136, -135, -135, -135, -135,
+ -134, -134, -134, -133, -133, -133, -132, -132, -132, -131,
+ -131, -131, -130, -130, -130, -130, -129, -129, -129, -128,
+ -128, -128, -127, -127, -127, -126, -126, -126, -125, -125,
+ -125, -125, -124, -124, -124, -123, -123, -123, -122, -122,
+ -122, -121, -121, -121, -120, -120, -120, -120, -119, -119,
+ -119, -118, -118, -118, -117, -117, -117, -116, -116, -116,
+ -115, -115, -115, -115, -114, -114, -114, -113, -113, -113,
+ -112, -112, -112, -111, -111, -111, -110, -110, -110, -110,
+ -109, -109, -109, -108, -108, -108, -107, -107, -107, -106,
+ -106, -106, -105, -105, -105, -105, -104, -104, -104, -103,
+ -103, -103, -102, -102, -102, -101, -101, -101, -100, -100,
+ -100, -100, -99, -99, -99, -98, -98, -98, -97, -97,
+ -97, -96, -96, -96, -95, -95, -95, -95, -94, -94,
+ -94, -93, -93, -93, -92, -92, -92, -91, -91, -91,
+ -90, -90, -90, -90, -89, -89, -89, -88, -88, -88,
+ -87, -87, -87, -86, -86, -86, -85, -85, -85, -85,
+ -84, -84, -84, -83, -83, -83, -82, -82, -82, -81,
+ -81, -81, -80, -80, -80, -80, -79, -79, -79, -78,
+ -78, -78, -77, -77, -77, -76, -76, -76, -75, -75,
+ -75, -75, -74, -74, -74, -73, -73, -73, -72, -72,
+ -72, -71, -71, -71, -70, -70, -70, -70, -69, -69,
+ -69, -68, -68, -68, -67, -67, -67, -66, -66, -66,
+ -65, -65, -65, -65, -64, -64, -64, -63, -63, -63,
+ -62, -62, -62, -61, -61, -61, -60, -60, -60, -60,
+ -59, -59, -59, -58, -58, -58, -57, -57, -57, -56,
+ -56, -56, -55, -55, -55, -55, -54, -54, -54, -53,
+ -53, -53, -52, -52, -52, -51, -51, -51, -50, -50,
+ -50, -50, -49, -49, -49, -48, -48, -48, -47, -47,
+ -47, -46, -46, -46, -45, -45, -45, -45, -44, -44,
+ -44, -43, -43, -43, -42, -42, -42, -41, -41, -41,
+ -40, -40, -40, -40, -39, -39, -39, -38, -38, -38,
+ -37, -37, -37, -36, -36, -36, -35, -35, -35, -35,
+ -34, -34, -34, -33, -33, -33, -32, -32, -32, -31,
+ -31, -31, -30, -30, -30, -30, -29, -29, -29, -28,
+ -28, -28, -27, -27, -27, -26, -26, -26, -25, -25,
+ -25, -25, -24, -24, -24, -23, -23, -23, -22, -22,
+ -22, -21, -21, -21, -20, -20, -20, -20, -19, -19,
+ -19, -18, -18, -18, -17, -17, -17, -16, -16, -16,
+ -15, -15, -15, -15, -14, -14, -14, -13, -13, -13,
+ -12, -12, -12, -11, -11, -11, -10, -10, -10, -10,
+ -9, -9, -9, -8, -8, -8, -7, -7, -7, -6,
+ -6, -6, -5, -5, -5, -5, -4, -4, -4, -3,
+ -3, -3, -2, -2, -2, -1, -1, -1, 0, 0,
+ 0, 0, 0, 0, 0, 1, 1, 1, 2, 2,
+ 2, 3, 3, 3, 4, 4, 4, 5, 5, 5,
+ 5, 6, 6, 6, 7, 7, 7, 8, 8, 8,
+ 9, 9, 9, 10, 10, 10, 10, 11, 11, 11,
+ 12, 12, 12, 13, 13, 13, 14, 14, 14, 15,
+ 15, 15, 15, 16, 16, 16, 17, 17, 17, 18,
+ 18, 18, 19, 19, 19, 20, 20, 20, 20, 21,
+ 21, 21, 22, 22, 22, 23, 23, 23, 24, 24,
+ 24, 25, 25, 25, 25, 26, 26, 26, 27, 27,
+ 27, 28, 28, 28, 29, 29, 29, 30, 30, 30,
+ 30, 31, 31, 31, 32, 32, 32, 33, 33, 33,
+ 34, 34, 34, 35, 35, 35, 35, 36, 36, 36,
+ 37, 37, 37, 38, 38, 38, 39, 39, 39, 40,
+ 40, 40, 40, 41, 41, 41, 42, 42, 42, 43,
+ 43, 43, 44, 44, 44, 45, 45, 45, 45, 46,
+ 46, 46, 47, 47, 47, 48, 48, 48, 49, 49,
+ 49, 50, 50, 50, 50, 51, 51, 51, 52, 52,
+ 52, 53, 53, 53, 54, 54, 54, 55, 55, 55,
+ 55, 56, 56, 56, 57, 57, 57, 58, 58, 58,
+ 59, 59, 59, 60, 60, 60, 60, 61, 61, 61,
+ 62, 62, 62, 63, 63, 63, 64, 64, 64, 65,
+ 65, 65, 65, 66, 66, 66, 67, 67, 67, 68,
+ 68, 68, 69, 69, 69, 70, 70, 70, 70, 71,
+ 71, 71, 72, 72, 72, 73, 73, 73, 74, 74,
+ 74, 75, 75, 75, 75, 76, 76, 76, 77, 77,
+ 77, 78, 78, 78, 79, 79, 79, 80, 80, 80,
+ 80, 81, 81, 81, 82, 82, 82, 83, 83, 83,
+ 84, 84, 84, 85, 85, 85, 85, 86, 86, 86,
+ 87, 87, 87, 88, 88, 88, 89, 89, 89, 90,
+ 90, 90, 90, 91, 91, 91, 92, 92, 92, 93,
+ 93, 93, 94, 94, 94, 95, 95, 95, 95, 96,
+ 96, 96, 97, 97, 97, 98, 98, 98, 99, 99,
+ 99, 100, 100, 100, 100, 101, 101, 101, 102, 102,
+ 102, 103, 103, 103, 104, 104, 104, 105, 105, 105,
+ 105, 106, 106, 106, 107, 107, 107, 108, 108, 108,
+ 109, 109, 109, 110, 110, 110, 110, 111, 111, 111,
+ 112, 112, 112, 113, 113, 113, 114, 114, 114, 115,
+ 115, 115, 115, 116, 116, 116, 117, 117, 117, 118,
+ 118, 118, 119, 119, 119, 120, 120, 120, 120, 121,
+ 121, 121, 122, 122, 122, 123, 123, 123, 124, 124,
+ 124, 125, 125, 125, 125, 126, 126, 126, 127, 127,
+ 127, 128, 128, 128, 129, 129, 129, 130, 130, 130,
+ 130, 131, 131, 131, 132, 132, 132, 133, 133, 133,
+ 134, 134, 134, 135, 135, 135, 135, 136, 136, 136,
+ 137, 137, 137, 138, 138, 138, 139, 139, 139, 140,
+ 140, 140, 140, 141, 141, 141, 142, 142, 142, 143,
+ 143, 143, 144, 144, 144, 145, 145, 145, 145, 146,
+ 146, 146, 147, 147, 147, 148, 148, 148, 149, 149,
+ 149, 150, 150, 150, 150, 151, 151, 151, 152, 152,
+ 152, 153, 153, 153, 154, 154, 154, 155, 155, 155,
+ 155, 156, 156, 156, 157, 157, 157, 158, 158, 158,
+ 159, 159, 159, 160, 160,
+};
+
+static const gshort floyd_steinberg_error4[] = {
+ -34, -33, -33, -33, -33, -33, -34, -33, -32, -33,
+ -33, -33, -33, -33, -32, -31, -33, -32, -32, -32,
+ -32, -32, -33, -32, -31, -32, -32, -32, -32, -32,
+ -31, -30, -32, -31, -31, -31, -31, -31, -32, -31,
+ -30, -31, -31, -31, -31, -31, -30, -29, -31, -30,
+ -30, -30, -30, -30, -31, -30, -29, -30, -30, -30,
+ -30, -30, -29, -28, -30, -29, -29, -29, -29, -29,
+ -30, -29, -28, -29, -29, -29, -29, -29, -28, -27,
+ -29, -28, -28, -28, -28, -28, -29, -28, -27, -28,
+ -28, -28, -28, -28, -27, -26, -28, -27, -27, -27,
+ -27, -27, -28, -27, -26, -27, -27, -27, -27, -27,
+ -26, -25, -27, -26, -26, -26, -26, -26, -27, -26,
+ -25, -26, -26, -26, -26, -26, -25, -24, -26, -25,
+ -25, -25, -25, -25, -26, -25, -24, -25, -25, -25,
+ -25, -25, -24, -23, -25, -24, -24, -24, -24, -24,
+ -25, -24, -23, -24, -24, -24, -24, -24, -23, -22,
+ -24, -23, -23, -23, -23, -23, -24, -23, -22, -23,
+ -23, -23, -23, -23, -22, -21, -23, -22, -22, -22,
+ -22, -22, -23, -22, -21, -22, -22, -22, -22, -22,
+ -21, -20, -22, -21, -21, -21, -21, -21, -22, -21,
+ -20, -21, -21, -21, -21, -21, -20, -19, -21, -20,
+ -20, -20, -20, -20, -21, -20, -19, -20, -20, -20,
+ -20, -20, -19, -18, -20, -19, -19, -19, -19, -19,
+ -20, -19, -18, -19, -19, -19, -19, -19, -18, -17,
+ -19, -18, -18, -18, -18, -18, -19, -18, -17, -18,
+ -18, -18, -18, -18, -17, -16, -18, -17, -17, -17,
+ -17, -17, -18, -17, -16, -17, -17, -17, -17, -17,
+ -16, -15, -17, -16, -16, -16, -16, -16, -17, -16,
+ -15, -16, -16, -16, -16, -16, -15, -14, -16, -15,
+ -15, -15, -15, -15, -16, -15, -14, -15, -15, -15,
+ -15, -15, -14, -13, -15, -14, -14, -14, -14, -14,
+ -15, -14, -13, -14, -14, -14, -14, -14, -13, -12,
+ -14, -13, -13, -13, -13, -13, -14, -13, -12, -13,
+ -13, -13, -13, -13, -12, -11, -13, -12, -12, -12,
+ -12, -12, -13, -12, -11, -12, -12, -12, -12, -12,
+ -11, -10, -12, -11, -11, -11, -11, -11, -12, -11,
+ -10, -11, -11, -11, -11, -11, -10, -9, -11, -10,
+ -10, -10, -10, -10, -11, -10, -9, -10, -10, -10,
+ -10, -10, -9, -8, -10, -9, -9, -9, -9, -9,
+ -10, -9, -8, -9, -9, -9, -9, -9, -8, -7,
+ -9, -8, -8, -8, -8, -8, -9, -8, -7, -8,
+ -8, -8, -8, -8, -7, -6, -8, -7, -7, -7,
+ -7, -7, -8, -7, -6, -7, -7, -7, -7, -7,
+ -6, -5, -7, -6, -6, -6, -6, -6, -7, -6,
+ -5, -6, -6, -6, -6, -6, -5, -4, -6, -5,
+ -5, -5, -5, -5, -6, -5, -4, -5, -5, -5,
+ -5, -5, -4, -3, -5, -4, -4, -4, -4, -4,
+ -5, -4, -3, -4, -4, -4, -4, -4, -3, -2,
+ -4, -3, -3, -3, -3, -3, -4, -3, -2, -3,
+ -3, -3, -3, -3, -2, -1, -3, -2, -2, -2,
+ -2, -2, -3, -2, -1, -2, -2, -2, -2, -2,
+ -1, 0, 1, 2, 2, 2, 2, 2, 1, 2,
+ 3, 2, 2, 2, 2, 2, 3, 1, 2, 3,
+ 3, 3, 3, 3, 2, 3, 4, 3, 3, 3,
+ 3, 3, 4, 2, 3, 4, 4, 4, 4, 4,
+ 3, 4, 5, 4, 4, 4, 4, 4, 5, 3,
+ 4, 5, 5, 5, 5, 5, 4, 5, 6, 5,
+ 5, 5, 5, 5, 6, 4, 5, 6, 6, 6,
+ 6, 6, 5, 6, 7, 6, 6, 6, 6, 6,
+ 7, 5, 6, 7, 7, 7, 7, 7, 6, 7,
+ 8, 7, 7, 7, 7, 7, 8, 6, 7, 8,
+ 8, 8, 8, 8, 7, 8, 9, 8, 8, 8,
+ 8, 8, 9, 7, 8, 9, 9, 9, 9, 9,
+ 8, 9, 10, 9, 9, 9, 9, 9, 10, 8,
+ 9, 10, 10, 10, 10, 10, 9, 10, 11, 10,
+ 10, 10, 10, 10, 11, 9, 10, 11, 11, 11,
+ 11, 11, 10, 11, 12, 11, 11, 11, 11, 11,
+ 12, 10, 11, 12, 12, 12, 12, 12, 11, 12,
+ 13, 12, 12, 12, 12, 12, 13, 11, 12, 13,
+ 13, 13, 13, 13, 12, 13, 14, 13, 13, 13,
+ 13, 13, 14, 12, 13, 14, 14, 14, 14, 14,
+ 13, 14, 15, 14, 14, 14, 14, 14, 15, 13,
+ 14, 15, 15, 15, 15, 15, 14, 15, 16, 15,
+ 15, 15, 15, 15, 16, 14, 15, 16, 16, 16,
+ 16, 16, 15, 16, 17, 16, 16, 16, 16, 16,
+ 17, 15, 16, 17, 17, 17, 17, 17, 16, 17,
+ 18, 17, 17, 17, 17, 17, 18, 16, 17, 18,
+ 18, 18, 18, 18, 17, 18, 19, 18, 18, 18,
+ 18, 18, 19, 17, 18, 19, 19, 19, 19, 19,
+ 18, 19, 20, 19, 19, 19, 19, 19, 20, 18,
+ 19, 20, 20, 20, 20, 20, 19, 20, 21, 20,
+ 20, 20, 20, 20, 21, 19, 20, 21, 21, 21,
+ 21, 21, 20, 21, 22, 21, 21, 21, 21, 21,
+ 22, 20, 21, 22, 22, 22, 22, 22, 21, 22,
+ 23, 22, 22, 22, 22, 22, 23, 21, 22, 23,
+ 23, 23, 23, 23, 22, 23, 24, 23, 23, 23,
+ 23, 23, 24, 22, 23, 24, 24, 24, 24, 24,
+ 23, 24, 25, 24, 24, 24, 24, 24, 25, 23,
+ 24, 25, 25, 25, 25, 25, 24, 25, 26, 25,
+ 25, 25, 25, 25, 26, 24, 25, 26, 26, 26,
+ 26, 26, 25, 26, 27, 26, 26, 26, 26, 26,
+ 27, 25, 26, 27, 27, 27, 27, 27, 26, 27,
+ 28, 27, 27, 27, 27, 27, 28, 26, 27, 28,
+ 28, 28, 28, 28, 27, 28, 29, 28, 28, 28,
+ 28, 28, 29, 27, 28, 29, 29, 29, 29, 29,
+ 28, 29, 30, 29, 29, 29, 29, 29, 30, 28,
+ 29, 30, 30, 30, 30, 30, 29, 30, 31, 30,
+ 30, 30, 30, 30, 31, 29, 30, 31, 31, 31,
+ 31, 31, 30, 31, 32, 31, 31, 31, 31, 31,
+ 32, 30, 31, 32, 32, 32, 32, 32, 31, 32,
+ 33, 32, 32, 32, 32, 32, 33, 31, 32, 33,
+ 33, 33, 33, 33, 32, 33, 34, 33, 33, 33,
+ 33, 33, 34, 32, 33,
+};
+
+#endif /* __GIMP_IMAGE_CONVERT_FSDITHER_H__ */
diff --git a/app/core/gimpimage-convert-indexed.c b/app/core/gimpimage-convert-indexed.c
new file mode 100644
index 0000000..6a3eae3
--- /dev/null
+++ b/app/core/gimpimage-convert-indexed.c
@@ -0,0 +1,4567 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpimage-convert-indexed.c
+ * Copyright (C) 1997-2004 Adam D. Moss <adam@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+/*
+ * 2005-09-04 - Switch 'positional' dither matrix to a 32x32 Bayer,
+ * which generates results that compress somewhat better (and may look
+ * worse or better depending on what you enjoy...). [adam@gimp.org]
+ *
+ * 2004-12-12 - Use a slower but much nicer technique for finding the
+ * two best colors to dither between when using fixed/positional
+ * dither methods. Makes positional dither much less lame. [adam@gimp.org]
+ *
+ * 2002-02-10 - Quantizer version 3.0 (the rest of the commit started
+ * a year ago -- whoops). Divide colors within CIE L*a*b* space using
+ * CPercep module (cpercep.[ch]), color-match and dither likewise,
+ * change the underlying box selection criteria and division point
+ * logic, bump luminance precision upwards, etc.etc. Generally
+ * chooses a much richer color set, especially for low numbers of
+ * colors. n.b.: Less luminance-sloppy in straight remapping which is
+ * good for color but a bit worse for high-frequency detail (that's
+ * partly what fs-dithering is for -- use it). [adam@gimp.org]
+ *
+ * 2001-03-25 - Define accessor function/macro for histogram reads and
+ * writes. This slows us down a little because we avoid some of the
+ * dirty tricks we used when we knew that the histogram was a straight
+ * 3d array, so I've recovered some of the speed loss by implementing
+ * a 5d accessor function with good locality of reference. This change
+ * is the first step towards quantizing in a more interesting colorspace
+ * than frumpy old RGB. [Adam]
+ *
+ * 2000/01/30 - Use palette_selector instead of option_menu for custom
+ * palette. Use libgimp callback functions. [Sven]
+ *
+ * 99/09/01 - Created a low-bleed FS-dither option. [Adam]
+ *
+ * 99/08/29 - Deterministic color dithering to arbitrary palettes.
+ * Ideal for animations that are going to be delta-optimized or simply
+ * don't want to look 'busy' in static areas. Also a bunch of bugfixes
+ * and tweaks. [Adam]
+ *
+ * 99/08/28 - Deterministic alpha dithering over layers, reduced bleeding
+ * of transparent values into opaque values, added optional stage to
+ * remove duplicate or unused color entries from final colormap. [Adam]
+ *
+ * 99/02/24 - Many revisions to the box-cut quantizer used in RGB->INDEXED
+ * conversion. Box to be cut is chosen on the basis of possessing an axis
+ * with the largest sum of weighted perceptible error, rather than based on
+ * volume or population. The box is split along this axis rather than its
+ * longest axis, at the point of error mean rather than simply at its centre.
+ * Error-limiting in the F-S dither has been disabled - it may become optional
+ * again later. If you're convinced that you have an image where the old
+ * dither looks better, let me know. [Adam]
+ *
+ * 99/01/10 - Hourglass... [Adam]
+ *
+ * 98/07/25 - Convert-to-indexed now remembers the last invocation's
+ * settings. Also, GRAY->INDEXED is more flexible. [Adam]
+ *
+ * 98/07/05 - Sucked the warning about quantizing to too many colors into
+ * a text widget embedded in the dialog, improved intelligence of dialog
+ * to default 'custom palette' selection to 'Web' if available, and
+ * in this case not bother to present the native WWW-palette radio
+ * button. [Adam]
+ *
+ * 98/04/13 - avoid a division by zero when converting an empty gray-scale
+ * image (who would like to do such a thing anyway??) [Sven ]
+ *
+ * 98/03/23 - fixed a longstanding fencepost - hopefully the *right*
+ * way, *again*. [Adam]
+ *
+ * 97/11/14 - added a proper pdb interface and support for dithering
+ * to custom palettes (based on a patch by Eric Hernes) [Yosh]
+ *
+ * 97/11/04 - fixed the accidental use of the color-counting case
+ * when palette_type is WEB or MONO. [Adam]
+ *
+ * 97/10/25 - color-counting implemented (could use some hashing, but
+ * performance actually seems okay) - now RGB->INDEXED conversion isn't
+ * destructive if it doesn't have to be. [Adam]
+ *
+ * 97/10/14 - fixed divide-by-zero when converting a completely transparent
+ * RGB image to indexed. [Adam]
+ *
+ * 97/07/01 - started todo/revision log. Put code back in to
+ * eliminate full-alpha pixels from RGB histogram.
+ * [Adam D. Moss - adam@gimp.org]
+ */
+
+ /* TODO for Convert:
+ *
+ * . Tweak, tweak, tweak. Old RGB code was tuned muchly.
+ *
+ * . Re-enable Heckbert locality for matching, benchmark it
+ *
+ * . Try faster fixed-point sRGB<->L*a*b* pixel conversion (see cpercep.c)
+ *
+ * . Use palette of another open INDEXED image?
+ *
+ * . Do error-splitting trick for GREY->INDEXED (hardly worth it)
+ */
+
+ /* CODE READABILITY BUGS:
+ *
+ * . Most uses of variants of the R,G,B variable naming convention
+ * are referring to L*a*b* co-ordinates, not RGB co-ordinates!
+ *
+ * . Each said variable is usually one of the following, but it is
+ * rarely clear which one:
+ * - (assumed sRGB) raw non-linear 8-bit RGB co-ordinates
+ * - 'full'-precision (unshifted) 8-bit L*a*b* co-ordinates
+ * - box-space (reduced-precision shifted L*a*b*) co-ordinates
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <cairo.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpcolor/gimpcolor.h"
+#include "libgimpmath/gimpmath.h"
+
+#include "core-types.h"
+
+#include "gegl/gimp-babl.h"
+#include "gegl/gimp-gegl-utils.h"
+
+#include "gimp.h"
+#include "gimpcontainer.h"
+#include "gimpdrawable.h"
+#include "gimperror.h"
+#include "gimpimage.h"
+#include "gimpimage-color-profile.h"
+#include "gimpimage-colormap.h"
+#include "gimpimage-undo.h"
+#include "gimpimage-undo-push.h"
+#include "gimplayer.h"
+#include "gimpobjectqueue.h"
+#include "gimppalette.h"
+#include "gimpprogress.h"
+
+#include "text/gimptextlayer.h"
+
+#include "gimpimage-convert-fsdither.h"
+#include "gimpimage-convert-data.h"
+#include "gimpimage-convert-indexed.h"
+
+#include "gimp-intl.h"
+
+
+/* basic memory/quality tradeoff */
+#define PRECISION_R 8
+#define PRECISION_G 6
+#define PRECISION_B 6
+
+#define HIST_R_ELEMS (1<<PRECISION_R)
+#define HIST_G_ELEMS (1<<PRECISION_G)
+#define HIST_B_ELEMS (1<<PRECISION_B)
+
+#define BITS_IN_SAMPLE 8
+
+#define R_SHIFT (BITS_IN_SAMPLE-PRECISION_R)
+#define G_SHIFT (BITS_IN_SAMPLE-PRECISION_G)
+#define B_SHIFT (BITS_IN_SAMPLE-PRECISION_B)
+
+/* we've stretched our non-cubic L*a*b* volume to touch the
+ * faces of the logical cube we've allocated for it, so re-scale
+ * again in inverse proportion to get back to linear proportions.
+ */
+#define R_SCALE 13 /* scale R (L*) distances by this much */
+#define G_SCALE 24 /* scale G (a*) distances by this much */
+#define B_SCALE 26 /* and B (b*) by this much */
+
+
+typedef struct _Color Color;
+typedef struct _QuantizeObj QuantizeObj;
+
+typedef void (* Pass1Func) (QuantizeObj *quantize_obj);
+typedef void (* Pass2InitFunc) (QuantizeObj *quantize_obj);
+typedef void (* Pass2Func) (QuantizeObj *quantize_obj,
+ GimpLayer *layer,
+ GeglBuffer *new_buffer);
+typedef void (* CleanupFunc) (QuantizeObj *quantize_obj);
+
+typedef guint64 ColorFreq;
+typedef ColorFreq * CFHistogram;
+
+typedef enum { AXIS_UNDEF, AXIS_RED, AXIS_BLUE, AXIS_GREEN } AxisType;
+
+typedef double etype;
+
+
+/*
+ We provide two different histogram access interfaces. HIST_LIN()
+ accesses the histogram in histogram-native space, taking absolute
+ histogram co-ordinates. HIST_RGB() accesses the histogram in RGB
+ space. This latter takes unsigned 8-bit co-ordinates, internally
+ converts those co-ordinates to histogram-native space and returns
+ the access pointer to the corresponding histogram cell.
+
+ Using these two interfaces we can import RGB data into a more
+ interesting space and efficiently work in the latter space until
+ it is time to output the quantized values in RGB again. For
+ this final conversion we implement the function lin_to_rgb().
+
+ We effectively pull our three-dimensional space into five dimensions
+ such that the most-entropic bits lay in the lowest bits of the resulting
+ array index. This gives significantly better locality of reference
+ and hence a small speedup despite the extra work involved in calculating
+ the index.
+
+ Why not six dimensions? The expansion of dimensionality is good for random
+ access such as histogram population and the query pattern typical of
+ dithering but we have some code which iterates in a scanning manner, for
+ which the expansion is suboptimal. The compromise is to leave the B
+ dimension unmolested in the lower-order bits of the index, since this is
+ the dimension most commonly iterated through in the inner loop of the
+ scans.
+
+ --adam
+
+ RhGhRlGlB
+*/
+#define VOL_GBITS (PRECISION_G)
+#define VOL_BBITS (PRECISION_B)
+#define VOL_RBITS (PRECISION_R)
+#define VOL_GBITSh (VOL_GBITS - 3)
+#define VOL_GBITSl (VOL_GBITS - VOL_GBITSh)
+#define VOL_BBITSh (VOL_BBITS - 4)
+#define VOL_BBITSl (VOL_BBITS - VOL_BBITSh)
+#define VOL_RBITSh (VOL_RBITS - 3)
+#define VOL_RBITSl (VOL_RBITS - VOL_RBITSh)
+#define VOL_GMASKh (((1<<VOL_GBITSh)-1) << VOL_GBITSl)
+#define VOL_GMASKl ((1<<VOL_GBITSl)-1)
+#define VOL_BMASKh (((1<<VOL_BBITSh)-1) << VOL_BBITSl)
+#define VOL_BMASKl ((1<<VOL_BBITSl)-1)
+#define VOL_RMASKh (((1<<VOL_RBITSh)-1) << VOL_RBITSl)
+#define VOL_RMASKl ((1<<VOL_RBITSl)-1)
+/* The 5d compromise thing. */
+#define REF_FUNC(r,g,b) \
+( \
+ (((r) & VOL_RMASKh) << (VOL_BBITS + VOL_GBITS)) | \
+ (((r) & VOL_RMASKl) << (VOL_GBITSl + VOL_BBITS)) | \
+ (((g) & VOL_GMASKh) << (VOL_RBITSl + VOL_BBITS)) | \
+ (((g) & VOL_GMASKl) << (VOL_BBITS)) | \
+ (b) \
+)
+/* The full-on 6d thing. */
+/*
+#define REF_FUNC(r,g,b) \
+( \
+ (((r) & VOL_RMASKh) << (VOL_BBITS + VOL_GBITS)) | \
+ (((r) & VOL_RMASKl) << (VOL_GBITSl + VOL_BBITSl)) | \
+ (((g) & VOL_GMASKh) << (VOL_RBITSl + VOL_BBITS)) | \
+ (((g) & VOL_GMASKl) << (VOL_BBITSl)) | \
+ (((b) & VOL_BMASKh) << (VOL_RBITSl + VOL_GBITSl)) | \
+ ((b) & VOL_BMASKl) \
+)
+*/
+/* The boring old 3d thing. */
+/*
+#define REF_FUNC(r,g,b) (((r)<<(PRECISION_G+PRECISION_B)) | ((g)<<(PRECISION_B)) | (b))
+*/
+
+/* You even get to choose whether you want the accessor function
+ implemented as a macro or an inline function. Don't say I never
+ give you anything. */
+/*
+#define HIST_LIN(hist_ptr,r,g,b) (&(hist_ptr)[REF_FUNC((r),(g),(b))])
+*/
+static inline ColorFreq *
+HIST_LIN (ColorFreq *hist_ptr,
+ const gint r,
+ const gint g,
+ const gint b)
+{
+ return (&(hist_ptr) [REF_FUNC (r, g, b)]);
+}
+
+
+#define LOWA (-86.181F)
+#define LOWB (-107.858F)
+#define HIGHA (98.237F)
+#define HIGHB (94.480F)
+
+#if 1
+#define LRAT (2.55F)
+#define ARAT (255.0F / (HIGHA - LOWA))
+#define BRAT (255.0F / (HIGHB - LOWB))
+#else
+#define LRAT (1.0F)
+#define ARAT (1.0F)
+#define BRAT (1.0F)
+#endif
+
+static const Babl *rgb_to_lab_fish = NULL;
+static const Babl *lab_to_rgb_fish = NULL;
+
+static inline void
+rgb_to_unshifted_lin (const guchar r,
+ const guchar g,
+ const guchar b,
+ gint *hr,
+ gint *hg,
+ gint *hb)
+{
+ gint or, og, ob;
+ gfloat rgb[3] = { r / 255.0, g / 255.0, b / 255.0 };
+ gfloat lab[3];
+
+ babl_process (rgb_to_lab_fish, rgb, lab, 1);
+
+ /* fprintf(stderr, " %d-%d-%d -> %0.3f,%0.3f,%0.3f ", r, g, b, sL, sa, sb);*/
+
+ or = RINT(lab[0] * LRAT);
+ og = RINT((lab[1] - LOWA) * ARAT);
+ ob = RINT((lab[2] - LOWB) * BRAT);
+
+ *hr = CLAMP(or, 0, 255);
+ *hg = CLAMP(og, 0, 255);
+ *hb = CLAMP(ob, 0, 255);
+
+ /* fprintf(stderr, " %d:%d:%d ", *hr, *hg, *hb); */
+}
+
+
+static inline void
+rgb_to_lin (const guchar r,
+ const guchar g,
+ const guchar b,
+ gint *hr,
+ gint *hg,
+ gint *hb)
+{
+ gint or, og, ob;
+
+ /*
+ double sL, sa, sb;
+ {
+ double low_l = 999.0, low_a = 999.9, low_b = 999.0;
+ double high_l = -999.0, high_a = -999.0, high_b = -999.0;
+
+ int r,g,b;
+
+ for (r=0; r<256; r++)
+ for (g=0; g<256; g++)
+ for (b=0; b<256; b++)
+ {
+ cpercep_rgb_to_space(r,g,b, &sL, &sa, &sb);
+
+ if (sL > high_l)
+ high_l = sL;
+ if (sL < low_l)
+ low_l = sL;
+ if (sa > high_a)
+ high_a = sa;
+ if (sa < low_a)
+ low_a = sa;
+ if (sb > high_b)
+ high_b = sb;
+ if (sb < low_b)
+ low_b = sb;
+ }
+
+ fprintf(stderr, " [L: %0.3f -> %0.3f / a: %0.3f -> %0.3f / b: %0.3f -> %0.3f]\t", low_l, high_l, low_a, high_a, low_b, high_b);
+
+ exit(-1);
+ }
+ */
+
+ rgb_to_unshifted_lin (r, g, b, &or, &og, &ob);
+
+#if 0
+#define RSDF(r) ((r) >= ((HIST_R_ELEMS-1) << R_SHIFT) ? HIST_R_ELEMS-1 : \
+ ((r) + ((1<<R_SHIFT)>>1) ) >> R_SHIFT)
+#define GSDF(g) ((g) >= ((HIST_G_ELEMS-1) << G_SHIFT) ? HIST_G_ELEMS-1 : \
+ ((g) + ((1<<G_SHIFT)>>1) ) >> G_SHIFT)
+#define BSDF(b) ((b) >= ((HIST_B_ELEMS-1) << B_SHIFT) ? HIST_B_ELEMS-1 : \
+ ((b) + ((1<<B_SHIFT)>>1) ) >> B_SHIFT)
+#else
+#define RSDF(r) ((r) >> R_SHIFT)
+#define GSDF(g) ((g) >> G_SHIFT)
+#define BSDF(b) ((b) >> B_SHIFT)
+#endif
+
+ or = RSDF (or);
+ og = GSDF (og);
+ ob = BSDF (ob);
+
+ *hr = or;
+ *hg = og;
+ *hb = ob;
+}
+
+
+static inline ColorFreq *
+HIST_RGB (ColorFreq *hist_ptr,
+ const gint r,
+ const gint g,
+ const gint b)
+{
+ gint hr, hg, hb;
+
+ rgb_to_lin (r, g, b, &hr, &hg, &hb);
+
+ return HIST_LIN (hist_ptr, hr, hg, hb);
+}
+
+
+static inline void
+lin_to_rgb (const gdouble hr,
+ const gdouble hg,
+ const gdouble hb,
+ guchar *r,
+ guchar *g,
+ guchar *b)
+{
+ gfloat rgb[3];
+ gfloat lab[3];
+ gdouble ir, ig, ib;
+
+ ir = ((gdouble) (hr)) * 255.0F / (gdouble) (HIST_R_ELEMS - 1);
+ ig = ((gdouble)( hg)) * 255.0F / (gdouble) (HIST_G_ELEMS - 1);
+ ib = ((gdouble)( hb)) * 255.0F / (gdouble) (HIST_B_ELEMS - 1);
+
+ ir = ir / LRAT;
+ ig = (ig / ARAT) + LOWA;
+ ib = (ib / BRAT) + LOWB;
+
+ lab[0] = ir;
+ lab[1] = ig;
+ lab[2] = ib;
+
+ babl_process (lab_to_rgb_fish, lab, rgb, 1);
+
+ *r = RINT (CLAMP (rgb[0] * 255, 0.0F, 255.0F));
+ *g = RINT (CLAMP (rgb[1] * 255, 0.0F, 255.0F));
+ *b = RINT (CLAMP (rgb[2] * 255, 0.0F, 255.0F));
+}
+
+
+
+struct _Color
+{
+ gint red;
+ gint green;
+ gint blue;
+};
+
+struct _QuantizeObj
+{
+ Pass1Func first_pass; /* first pass over image data creates colormap */
+ Pass2InitFunc second_pass_init; /* Initialize data which persists over invocations */
+ Pass2Func second_pass; /* second pass maps from image data to colormap */
+ CleanupFunc delete_func; /* function to clean up data associated with private */
+
+ GimpPalette *custom_palette; /* The custom palette, if any */
+
+ gint desired_number_of_colors; /* Number of colors we will allow */
+ gint actual_number_of_colors; /* Number of colors actually needed */
+ Color cmap[256]; /* colormap created by quantization */
+ Color clin[256]; /* .. converted back to linear space */
+ guint64 index_used_count[256]; /* how many times an index was used */
+ CFHistogram histogram; /* holds the histogram */
+
+ gboolean want_dither_alpha;
+ gint error_freedom; /* 0=much bleed, 1=controlled bleed */
+
+ GimpProgress *progress;
+};
+
+typedef struct
+{
+ /* The bounds of the box (inclusive); expressed as histogram indexes */
+ gint Rmin, Rmax;
+ gint Rhalferror;
+ gint Gmin, Gmax;
+ gint Ghalferror;
+ gint Bmin, Bmax;
+ gint Bhalferror;
+
+ /* The volume (actually 2-norm) of the box */
+ gint volume;
+
+ /* The number of nonzero histogram cells within this box */
+ gint64 colorcount;
+
+ /* The sum of the weighted error within this box */
+ guint64 error;
+ /* The sum of the unweighted error within this box */
+ guint64 rerror;
+ guint64 gerror;
+ guint64 berror;
+
+} box, *boxptr;
+
+
+static void zero_histogram_gray (CFHistogram histogram);
+static void zero_histogram_rgb (CFHistogram histogram);
+static void generate_histogram_gray (CFHistogram hostogram,
+ GimpLayer *layer,
+ gboolean dither_alpha);
+static void generate_histogram_rgb (CFHistogram histogram,
+ GimpLayer *layer,
+ gint col_limit,
+ gboolean dither_alpha,
+ GimpProgress *progress);
+
+static QuantizeObj * initialize_median_cut (GimpImageBaseType old_type,
+ gint max_colors,
+ GimpConvertDitherType dither_type,
+ GimpConvertPaletteType palette_type,
+ GimpPalette *custom_palette,
+ gboolean dither_alpha,
+ GimpProgress *progress);
+
+static void compute_color_lin8 (QuantizeObj *quantobj,
+ CFHistogram histogram,
+ boxptr boxp,
+ const int icolor);
+
+
+static guchar found_cols[MAXNUMCOLORS][3];
+static gint num_found_cols;
+static gboolean needs_quantize;
+static gboolean had_white;
+static gboolean had_black;
+
+
+/**********************************************************/
+typedef struct
+{
+ gint64 used_count;
+ guchar initial_index;
+} PalEntry;
+
+static int
+mapping_compare (const void *a,
+ const void *b)
+{
+ PalEntry *m1 = (PalEntry *) a;
+ PalEntry *m2 = (PalEntry *) b;
+
+ return (m2->used_count - m1->used_count);
+}
+
+/* FWIW, the make_remap_table() and mapping_compare() function source
+ * and PalEntry may be re-used under the XFree86-style license.
+ * <adam@gimp.org>
+ */
+static void
+make_remap_table (const guchar old_palette[],
+ guchar new_palette[],
+ const guint64 index_used_count[],
+ guchar remap_table[],
+ gint *num_entries)
+{
+ gint i, j, k;
+ guchar temppal[256 * 3];
+ guint64 tempuse[256];
+ guint64 transmap[256];
+ PalEntry *palentries;
+ gint used = 0;
+
+ memset (temppal, 0, 256 * 3);
+ memset (tempuse, 0, 256 * sizeof (guint64));
+ memset (transmap, 255, 256 * sizeof (guint64));
+
+ /* First pass - only collect entries which are marked as being used
+ * at all in index_used_count.
+ */
+ for (i = 0; i < *num_entries; i++)
+ {
+ if (index_used_count[i])
+ {
+ temppal[used*3 + 0] = old_palette[i*3 + 0];
+ temppal[used*3 + 1] = old_palette[i*3 + 1];
+ temppal[used*3 + 2] = old_palette[i*3 + 2];
+
+ tempuse[used] = index_used_count[i];
+ transmap[i] = used;
+
+ used++;
+ }
+ }
+
+ /* Second pass - remove duplicates. (O(n^3), could do better!) */
+ for (i = 0; i < used; i++)
+ {
+ for (j = 0; j < i; j++)
+ {
+ if ((temppal[i*3 + 1] == temppal[j*3 + 1]) &&
+ (temppal[i*3 + 0] == temppal[j*3 + 0]) &&
+ (temppal[i*3 + 2] == temppal[j*3 + 2]) &&
+ tempuse[j] &&
+ tempuse[i])
+ {
+ /* Move the 'used' tally from one to the other. */
+ tempuse[i] += tempuse[j];
+ /* zero one of them, deactivating its entry. */
+ tempuse[j] = 0;
+
+ /* change all mappings from this dead index to the live
+ * one.
+ */
+ for (k = 0; k < *num_entries; k++)
+ {
+ if (index_used_count[k] && (transmap[k] == j))
+ transmap[k] = i;
+ }
+ }
+ }
+ }
+
+ /* Third pass - rank all used indices to the beginning of the
+ * palette.
+ */
+ palentries = g_new (PalEntry, used);
+
+ for (i = 0; i < used; i++)
+ {
+ palentries[i].initial_index = i;
+ palentries[i].used_count = tempuse[i];
+ }
+
+ qsort (palentries, used, sizeof (PalEntry), &mapping_compare);
+
+ for (i = 0; i < *num_entries; i++)
+ {
+ if (index_used_count[i])
+ {
+ for (j = 0; j < used; j++)
+ {
+ if ((transmap[i] == palentries[j].initial_index)
+ && (palentries[j].used_count))
+ {
+ remap_table[i] = j;
+ break;
+ }
+ }
+ }
+ }
+ for (i = 0; i < *num_entries; i++)
+ {
+ if (index_used_count[i])
+ {
+ new_palette[remap_table[i] * 3 + 0] = old_palette[i * 3 + 0];
+ new_palette[remap_table[i] * 3 + 1] = old_palette[i * 3 + 1];
+ new_palette[remap_table[i] * 3 + 2] = old_palette[i * 3 + 2];
+ }
+ }
+
+ *num_entries = 0;
+
+ for (j = 0; j < used; j++)
+ {
+ if (palentries[j].used_count)
+ {
+ (*num_entries)++;
+ }
+ }
+
+ g_free (palentries);
+}
+
+static void
+remap_indexed_layer (GimpLayer *layer,
+ const guchar *remap_table,
+ gint num_entries)
+{
+ GeglBufferIterator *iter;
+ const Babl *format;
+ gint bpp;
+ gboolean has_alpha;
+
+ format = gimp_drawable_get_format (GIMP_DRAWABLE (layer));
+
+ bpp = babl_format_get_bytes_per_pixel (format);
+ has_alpha = babl_format_has_alpha (format);
+
+ iter = gegl_buffer_iterator_new (gimp_drawable_get_buffer (GIMP_DRAWABLE (layer)),
+ NULL, 0, NULL,
+ GEGL_ACCESS_READWRITE, GEGL_ABYSS_NONE, 1);
+
+ while (gegl_buffer_iterator_next (iter))
+ {
+ guchar *data = iter->items[0].data;
+ gint length = iter->length;
+
+ if (has_alpha)
+ {
+ while (length--)
+ {
+ if (data[ALPHA_I])
+ data[INDEXED] = remap_table[data[INDEXED]];
+ else
+ data[INDEXED] = 0;
+
+ data += bpp;
+ }
+ }
+ else
+ {
+ while (length--)
+ {
+ data[INDEXED] = remap_table[data[INDEXED]];
+
+ data += bpp;
+ }
+ }
+ }
+}
+
+static gint
+color_quicksort (const void *c1,
+ const void *c2)
+{
+ Color *color1 = (Color *) c1;
+ Color *color2 = (Color *) c2;
+
+ gdouble v1 = GIMP_RGB_LUMINANCE (color1->red, color1->green, color1->blue);
+ gdouble v2 = GIMP_RGB_LUMINANCE (color2->red, color2->green, color2->blue);
+
+ if (v1 < v2)
+ return -1;
+ else if (v1 > v2)
+ return 1;
+ else
+ return 0;
+}
+
+gboolean
+gimp_image_convert_indexed (GimpImage *image,
+ GimpConvertPaletteType palette_type,
+ gint max_colors,
+ gboolean remove_duplicates,
+ GimpConvertDitherType dither_type,
+ gboolean dither_alpha,
+ gboolean dither_text_layers,
+ GimpPalette *custom_palette,
+ GimpProgress *progress,
+ GError **error)
+{
+ QuantizeObj *quantobj = NULL;
+ GimpObjectQueue *queue = NULL;
+ GimpProgress *sub_progress = NULL;
+ GimpImageBaseType old_type;
+ GList *all_layers;
+ GList *list;
+ GimpColorProfile *dest_profile = NULL;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);
+ g_return_val_if_fail (gimp_image_get_base_type (image) != GIMP_INDEXED, FALSE);
+ g_return_val_if_fail (gimp_babl_is_valid (GIMP_INDEXED,
+ gimp_image_get_precision (image)),
+ FALSE);
+ g_return_val_if_fail (custom_palette == NULL ||
+ GIMP_IS_PALETTE (custom_palette), FALSE);
+ g_return_val_if_fail (custom_palette == NULL ||
+ gimp_palette_get_n_colors (custom_palette) <= 256,
+ FALSE);
+ g_return_val_if_fail (progress == NULL || GIMP_IS_PROGRESS (progress), FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ if (palette_type == GIMP_CONVERT_PALETTE_CUSTOM)
+ {
+ if (! custom_palette)
+ palette_type = GIMP_CONVERT_PALETTE_MONO;
+
+ if (gimp_palette_get_n_colors (custom_palette) == 0)
+ {
+ g_set_error_literal (error, GIMP_ERROR, GIMP_FAILED,
+ _("Cannot convert image: palette is empty."));
+ return FALSE;
+ }
+ }
+
+ gimp_set_busy (image->gimp);
+
+ all_layers = gimp_image_get_layer_list (image);
+
+ g_object_freeze_notify (G_OBJECT (image));
+
+ gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_IMAGE_CONVERT,
+ C_("undo-type", "Convert Image to Indexed"));
+
+ /* Push the image type to the stack */
+ gimp_image_undo_push_image_type (image, NULL);
+
+ /* Set the new base type */
+ old_type = gimp_image_get_base_type (image);
+
+ g_object_set (image, "base-type", GIMP_INDEXED, NULL);
+
+ /* when converting from GRAY, convert to the new type's builtin
+ * profile.
+ */
+ if (old_type == GIMP_GRAY)
+ {
+ if (gimp_image_get_color_profile (image))
+ dest_profile = gimp_image_get_builtin_color_profile (image);
+ }
+
+ /* Build histogram if necessary. */
+ rgb_to_lab_fish = babl_fish (babl_format ("R'G'B' float"),
+ babl_format ("CIE Lab float"));
+ lab_to_rgb_fish = babl_fish (babl_format ("CIE Lab float"),
+ babl_format ("R'G'B' float"));
+
+ /* don't dither if the input is grayscale and we are simply mapping
+ * every color
+ */
+ if (old_type == GIMP_GRAY &&
+ max_colors == 256 &&
+ palette_type == GIMP_CONVERT_PALETTE_GENERATE)
+ {
+ dither_type = GIMP_CONVERT_DITHER_NONE;
+ }
+
+ if (progress)
+ {
+ queue = gimp_object_queue_new (progress);
+ sub_progress = GIMP_PROGRESS (queue);
+
+ gimp_object_queue_push_list (queue, all_layers);
+ }
+
+ quantobj = initialize_median_cut (old_type, max_colors, dither_type,
+ palette_type, custom_palette,
+ dither_alpha,
+ sub_progress);
+
+ if (palette_type == GIMP_CONVERT_PALETTE_GENERATE)
+ {
+ if (old_type == GIMP_GRAY)
+ zero_histogram_gray (quantobj->histogram);
+ else
+ zero_histogram_rgb (quantobj->histogram);
+
+ /* To begin, assume that there are fewer colors in the image
+ * than the user actually asked for. In that case, we don't
+ * need to quantize or color-dither.
+ */
+ needs_quantize = FALSE;
+ had_black = FALSE;
+ had_white = FALSE;
+ num_found_cols = 0;
+
+ /* Build the histogram */
+ for (list = all_layers;
+ list;
+ list = g_list_next (list))
+ {
+ GimpLayer *layer = list->data;
+
+ if (queue)
+ gimp_object_queue_pop (queue);
+
+ if (old_type == GIMP_GRAY)
+ {
+ generate_histogram_gray (quantobj->histogram,
+ layer, dither_alpha);
+ }
+ else
+ {
+ /* Note: generate_histogram_rgb may set needs_quantize
+ * if the image contains more colors than the limit
+ * specified by the user.
+ */
+ generate_histogram_rgb (quantobj->histogram,
+ layer, max_colors, dither_alpha,
+ sub_progress);
+ }
+ }
+ }
+
+ if (progress)
+ gimp_progress_set_text_literal (progress,
+ _("Converting to indexed colors (stage 2)"));
+
+ if (old_type == GIMP_RGB &&
+ ! needs_quantize &&
+ palette_type == GIMP_CONVERT_PALETTE_GENERATE)
+ {
+ gint i;
+
+ /* If this is an RGB image, and the user wanted a custom-built
+ * generated palette, and this image has no more colors than
+ * the user asked for, we don't need the first pass
+ * (quantization).
+ *
+ * There's also no point in dithering, since there's no error
+ * to spread. So we destroy the old quantobj and make a new
+ * one with the remapping function set to a special LUT-based
+ * no-dither remapper.
+ */
+
+ quantobj->delete_func (quantobj);
+ quantobj = initialize_median_cut (old_type, max_colors,
+ GIMP_CONVERT_DITHER_NODESTRUCT,
+ palette_type,
+ custom_palette,
+ dither_alpha,
+ sub_progress);
+ /* We can skip the first pass (palette creation) */
+
+ quantobj->actual_number_of_colors = num_found_cols;
+ for (i = 0; i < num_found_cols; i++)
+ {
+ quantobj->cmap[i].red = found_cols[i][0];
+ quantobj->cmap[i].green = found_cols[i][1];
+ quantobj->cmap[i].blue = found_cols[i][2];
+ }
+ }
+ else
+ {
+ quantobj->first_pass (quantobj);
+ }
+
+ if (palette_type == GIMP_CONVERT_PALETTE_GENERATE)
+ qsort (quantobj->cmap,
+ quantobj->actual_number_of_colors, sizeof (Color),
+ color_quicksort);
+
+ if (progress)
+ {
+ gimp_progress_set_text_literal (progress,
+ _("Converting to indexed colors (stage 3)"));
+
+ gimp_object_queue_clear (queue);
+ gimp_object_queue_push_list (queue, all_layers);
+ }
+
+ /* Initialise data which must persist across indexed layer iterations */
+ if (quantobj->second_pass_init)
+ quantobj->second_pass_init (quantobj);
+
+ /* Set the generated palette on the image, we need it to
+ * convert the layers. We optionally remove duplicate entries
+ * after the layer conversion.
+ */
+ {
+ guchar colormap[GIMP_IMAGE_COLORMAP_SIZE];
+ gint i, j;
+
+ for (i = 0, j = 0; i < quantobj->actual_number_of_colors; i++)
+ {
+ colormap[j++] = quantobj->cmap[i].red;
+ colormap[j++] = quantobj->cmap[i].green;
+ colormap[j++] = quantobj->cmap[i].blue;
+ }
+
+ gimp_image_set_colormap (image, colormap,
+ quantobj->actual_number_of_colors, TRUE);
+ }
+
+ /* Convert all layers */
+ for (list = all_layers;
+ list;
+ list = g_list_next (list))
+ {
+ GimpLayer *layer = list->data;
+ gboolean quantize;
+
+ if (queue)
+ gimp_object_queue_pop (queue);
+
+ if (gimp_item_is_text_layer (GIMP_ITEM (layer)))
+ quantize = dither_text_layers;
+ else
+ quantize = TRUE;
+
+ if (quantize)
+ {
+ GeglBuffer *new_buffer;
+ gboolean has_alpha;
+
+ has_alpha = gimp_drawable_has_alpha (GIMP_DRAWABLE (layer));
+
+ new_buffer =
+ gegl_buffer_new (GEGL_RECTANGLE (0, 0,
+ gimp_item_get_width (GIMP_ITEM (layer)),
+ gimp_item_get_height (GIMP_ITEM (layer))),
+ gimp_image_get_layer_format (image,
+ has_alpha));
+
+ quantobj->second_pass (quantobj, layer, new_buffer);
+
+ gimp_drawable_set_buffer (GIMP_DRAWABLE (layer), TRUE, NULL,
+ new_buffer);
+ g_object_unref (new_buffer);
+ }
+ else
+ {
+ gimp_drawable_convert_type (GIMP_DRAWABLE (layer), image,
+ GIMP_INDEXED,
+ gimp_drawable_get_precision (GIMP_DRAWABLE (layer)),
+ gimp_drawable_has_alpha (GIMP_DRAWABLE (layer)),
+ dest_profile,
+ GEGL_DITHER_NONE, GEGL_DITHER_NONE,
+ TRUE, sub_progress);
+ }
+ }
+
+ /* Set the final palette on the image */
+ if (remove_duplicates &&
+ (palette_type != GIMP_CONVERT_PALETTE_GENERATE) &&
+ (palette_type != GIMP_CONVERT_PALETTE_MONO))
+ {
+ guchar colormap[GIMP_IMAGE_COLORMAP_SIZE];
+ gint i, j;
+ guchar old_palette[256 * 3];
+ guchar new_palette[256 * 3];
+ guchar remap_table[256];
+ gint num_entries;
+
+ for (i = 0, j = 0; i < quantobj->actual_number_of_colors; i++)
+ {
+ old_palette[j++] = quantobj->cmap[i].red;
+ old_palette[j++] = quantobj->cmap[i].green;
+ old_palette[j++] = quantobj->cmap[i].blue;
+ }
+
+ num_entries = quantobj->actual_number_of_colors;
+
+ /* Generate a remapping table */
+ make_remap_table (old_palette, new_palette,
+ quantobj->index_used_count,
+ remap_table, &num_entries);
+
+ /* Convert all layers */
+ for (list = all_layers; list; list = g_list_next (list))
+ {
+ remap_indexed_layer (list->data, remap_table, num_entries);
+ }
+
+ for (i = 0, j = 0; i < num_entries; i++)
+ {
+ colormap[j] = new_palette[j]; j++;
+ colormap[j] = new_palette[j]; j++;
+ colormap[j] = new_palette[j]; j++;
+ }
+
+ gimp_image_set_colormap (image, colormap, num_entries, TRUE);
+ }
+
+ /* When converting from GRAY, set the new profile.
+ */
+ if (old_type == GIMP_GRAY)
+ {
+ if (gimp_image_get_color_profile (image))
+ gimp_image_set_color_profile (image, dest_profile, NULL);
+ else
+ gimp_color_managed_profile_changed (GIMP_COLOR_MANAGED (image));
+ }
+
+ /* Delete the quantizer object, if there is one */
+ if (quantobj)
+ quantobj->delete_func (quantobj);
+
+ gimp_image_undo_group_end (image);
+
+ gimp_image_mode_changed (image);
+ g_object_thaw_notify (G_OBJECT (image));
+
+ g_clear_object (&queue);
+
+ g_list_free (all_layers);
+
+ gimp_unset_busy (image->gimp);
+
+ return TRUE;
+}
+
+/*
+ * Indexed color conversion machinery
+ */
+
+static void
+zero_histogram_gray (CFHistogram histogram)
+{
+ gint i;
+
+ for (i = 0; i < 256; i++)
+ histogram[i] = 0;
+}
+
+
+static void
+zero_histogram_rgb (CFHistogram histogram)
+{
+ memset (histogram, 0,
+ HIST_R_ELEMS * HIST_G_ELEMS * HIST_B_ELEMS * sizeof (ColorFreq));
+}
+
+
+static void
+generate_histogram_gray (CFHistogram histogram,
+ GimpLayer *layer,
+ gboolean dither_alpha)
+{
+ GeglBufferIterator *iter;
+ const Babl *format;
+ gint bpp;
+ gboolean has_alpha;
+
+ format = gimp_drawable_get_format (GIMP_DRAWABLE (layer));
+
+ g_return_if_fail (format == babl_format_with_space ("Y' u8", format) ||
+ format == babl_format_with_space ("Y'A u8", format));
+
+ bpp = babl_format_get_bytes_per_pixel (format);
+ has_alpha = babl_format_has_alpha (format);
+
+ iter = gegl_buffer_iterator_new (gimp_drawable_get_buffer (GIMP_DRAWABLE (layer)),
+ NULL, 0, format,
+ GEGL_ACCESS_READ, GEGL_ABYSS_NONE, 1);
+
+ while (gegl_buffer_iterator_next (iter))
+ {
+ const guchar *data = iter->items[0].data;
+ gint length = iter->length;
+
+ if (has_alpha)
+ {
+ while (length--)
+ {
+ if (data[ALPHA_G] > 127)
+ histogram[*data]++;
+
+ data += bpp;
+ }
+ }
+ else
+ {
+ while (length--)
+ {
+ histogram[*data]++;
+
+ data += bpp;
+ }
+ }
+ }
+}
+
+static void
+check_white_or_black (const guchar *data)
+{
+ if (data[RED] == 255 &&
+ data[GREEN] == 255 &&
+ data[BLUE] == 255)
+ had_white = TRUE;
+ if (data[RED] ==0 &&
+ data[GREEN]==0 &&
+ data[BLUE] ==0)
+ had_black = TRUE;
+}
+
+static void
+generate_histogram_rgb (CFHistogram histogram,
+ GimpLayer *layer,
+ gint col_limit,
+ gboolean dither_alpha,
+ GimpProgress *progress)
+{
+ GeglBufferIterator *iter;
+ const Babl *format;
+ GeglRectangle *roi;
+ ColorFreq *colfreq;
+ gint nfc_iter;
+ gint row, col, coledge;
+ gint offsetx, offsety;
+ gint64 layer_size;
+ gint64 total_size = 0;
+ gint count = 0;
+ gint bpp;
+ gboolean has_alpha;
+
+ format = gimp_drawable_get_format (GIMP_DRAWABLE (layer));
+
+ g_return_if_fail (format == babl_format ("R'G'B' u8") ||
+ format == babl_format ("R'G'B'A u8"));
+
+ bpp = babl_format_get_bytes_per_pixel (format);
+ has_alpha = babl_format_has_alpha (format);
+
+ gimp_item_get_offset (GIMP_ITEM (layer), &offsetx, &offsety);
+
+ layer_size = (gimp_item_get_width (GIMP_ITEM (layer)) *
+ gimp_item_get_height (GIMP_ITEM (layer)));
+
+ /* g_printerr ("col_limit = %d, nfc = %d\n", col_limit, num_found_cols); */
+
+ iter = gegl_buffer_iterator_new (gimp_drawable_get_buffer (GIMP_DRAWABLE (layer)),
+ NULL, 0, format,
+ GEGL_ACCESS_READ, GEGL_ABYSS_NONE, 1);
+ roi = &iter->items[0].roi;
+
+ if (progress)
+ gimp_progress_set_value (progress, 0.0);
+
+ while (gegl_buffer_iterator_next (iter))
+ {
+ const guchar *data = iter->items[0].data;
+ gint length = iter->length;
+
+ total_size += length;
+
+ /* g_printerr (" [%d,%d - %d,%d]", srcPR.x, src_roi->y, offsetx, offsety); */
+
+ if (needs_quantize)
+ {
+ if (dither_alpha)
+ {
+ /* if alpha-dithering,
+ we need to be deterministic w.r.t. offsets */
+
+ col = roi->x + offsetx;
+ coledge = col + roi->width;
+ row = roi->y + offsety;
+
+ while (length--)
+ {
+ gboolean transparent = FALSE;
+
+ if (has_alpha &&
+ data[ALPHA] <
+ DM[col & DM_WIDTHMASK][row & DM_HEIGHTMASK])
+ transparent = TRUE;
+
+ if (! transparent)
+ {
+ colfreq = HIST_RGB (histogram,
+ data[RED],
+ data[GREEN],
+ data[BLUE]);
+ check_white_or_black (data);
+ (*colfreq)++;
+ }
+
+ col++;
+ if (col == coledge)
+ {
+ col = roi->x + offsetx;
+ row++;
+ }
+
+ data += bpp;
+ }
+ }
+ else
+ {
+ while (length--)
+ {
+ if ((has_alpha && ((data[ALPHA] > 127)))
+ || (!has_alpha))
+ {
+ colfreq = HIST_RGB (histogram,
+ data[RED],
+ data[GREEN],
+ data[BLUE]);
+ check_white_or_black (data);
+ (*colfreq)++;
+ }
+
+ data += bpp;
+ }
+ }
+ }
+ else
+ {
+ /* if alpha-dithering, we need to be deterministic w.r.t. offsets */
+ col = roi->x + offsetx;
+ coledge = col + roi->width;
+ row = roi->y + offsety;
+
+ while (length--)
+ {
+ gboolean transparent = FALSE;
+
+ if (has_alpha)
+ {
+ if (dither_alpha)
+ {
+ if (data[ALPHA] <
+ DM[col & DM_WIDTHMASK][row & DM_HEIGHTMASK])
+ transparent = TRUE;
+ }
+ else
+ {
+ if (data[ALPHA] <= 127)
+ transparent = TRUE;
+ }
+ }
+
+ if (! transparent)
+ {
+ colfreq = HIST_RGB (histogram,
+ data[RED],
+ data[GREEN],
+ data[BLUE]);
+ (*colfreq)++;
+
+ if (!needs_quantize)
+ {
+ for (nfc_iter = 0;
+ nfc_iter < num_found_cols;
+ nfc_iter++)
+ {
+ if ((data[RED] == found_cols[nfc_iter][0]) &&
+ (data[GREEN] == found_cols[nfc_iter][1]) &&
+ (data[BLUE] == found_cols[nfc_iter][2]))
+ goto already_found;
+ }
+
+ /* Color was not in the table of
+ * existing colors
+ */
+
+ num_found_cols++;
+
+ if (num_found_cols > col_limit)
+ {
+ /* There are more colors in the image than
+ * were allowed. We switch to plain
+ * histogram calculation with a view to
+ * quantizing at a later stage.
+ */
+ needs_quantize = TRUE;
+ /* g_print ("\nmax colors exceeded - needs quantize.\n");*/
+ goto already_found;
+ }
+ else
+ {
+ /* Remember the new color we just found.
+ */
+ found_cols[num_found_cols-1][0] = data[RED];
+ found_cols[num_found_cols-1][1] = data[GREEN];
+ found_cols[num_found_cols-1][2] = data[BLUE];
+
+ check_white_or_black (data);
+ }
+ }
+ }
+ already_found:
+
+ col++;
+ if (col == coledge)
+ {
+ col = roi->x + offsetx;
+ row++;
+ }
+
+ data += bpp;
+ }
+ }
+
+ if (progress && (count % 16 == 0))
+ gimp_progress_set_value (progress,
+ (gdouble) total_size / (gdouble) layer_size);
+ }
+
+/* g_print ("O: col_limit = %d, nfc = %d\n", col_limit, num_found_cols);*/
+}
+
+
+
+static boxptr
+find_split_candidate (const boxptr boxlist,
+ const gint numboxes,
+ AxisType *which_axis,
+ const gint desired_colors)
+{
+ boxptr boxp;
+ gint i;
+ etype maxc = 0;
+ boxptr which = NULL;
+ gdouble Lbias;
+
+ *which_axis = AXIS_UNDEF;
+
+ /* we only perform the initial L-split bias /at all/ if the final
+ number of desired colors is quite low, otherwise it all comes
+ out in the wash anyway and this initial bias generally only hurts
+ us in the long run. */
+ if (desired_colors <= 16)
+ {
+#define BIAS_FACTOR 2.66F
+#define BIAS_NUMBER 2 /* 0 */
+
+ /* we bias towards splitting across L* for first few colors */
+ Lbias = (numboxes > BIAS_NUMBER) ? 1.0F : ((gdouble) (BIAS_NUMBER + 1) -
+ ((gdouble) numboxes)) /
+ ((gdouble) BIAS_NUMBER / BIAS_FACTOR);
+ /*Lbias = 1.0;
+ fprintf(stderr, " [[%d]] ", numboxes);
+ fprintf(stderr, "Using ramped L-split bias.\n");
+ fprintf(stderr, "R\n");
+ */
+ }
+ else
+ Lbias = 1.0F;
+
+ for (i = 0, boxp = boxlist; i < numboxes; i++, boxp++)
+ {
+ if (boxp->volume > 0)
+ {
+#ifndef _MSC_VER
+ etype rpe = (double)((boxp->rerror) * R_SCALE * R_SCALE);
+ etype gpe = (double)((boxp->gerror) * G_SCALE * G_SCALE);
+ etype bpe = (double)((boxp->berror) * B_SCALE * B_SCALE);
+#else
+ /*
+ * Sorry about the mess, otherwise would get :
+ * error C2520: conversion from unsigned __int64 to double
+ * not implemented, use signed __int64
+ */
+ etype rpe = (double)(((__int64)boxp->rerror) * R_SCALE * R_SCALE);
+ etype gpe = (double)(((__int64)boxp->gerror) * G_SCALE * G_SCALE);
+ etype bpe = (double)(((__int64)boxp->berror) * B_SCALE * B_SCALE);
+#endif
+
+ if (Lbias * rpe > maxc &&
+ boxp->Rmin < boxp->Rmax)
+ {
+ which = boxp;
+ maxc = Lbias * rpe;
+ *which_axis = AXIS_RED;
+ }
+
+ if (gpe > maxc &&
+ boxp->Gmin < boxp->Gmax)
+ {
+ which = boxp;
+ maxc = gpe;
+ *which_axis = AXIS_GREEN;
+ }
+
+ if (bpe > maxc &&
+ boxp->Bmin < boxp->Bmax)
+ {
+ which = boxp;
+ maxc = bpe;
+ *which_axis = AXIS_BLUE;
+ }
+ }
+ }
+
+ /* fprintf(stderr, " %f,%p ", maxc, which); */
+ /* fprintf(stderr, " %llu ", maxc); */
+
+ return which;
+}
+
+
+/* Find the splittable box with the largest (scaled) volume Returns
+ * NULL if no splittable boxes remain
+ */
+static boxptr
+find_biggest_volume (const boxptr boxlist,
+ const gint numboxes)
+{
+ boxptr boxp;
+ gint i;
+ gint maxv = 0;
+ boxptr which = NULL;
+
+ for (i = 0, boxp = boxlist; i < numboxes; i++, boxp++)
+ {
+ if (boxp->volume > maxv)
+ {
+ which = boxp;
+ maxv = boxp->volume;
+ }
+ }
+
+ return which;
+}
+
+
+/* Shrink the min/max bounds of a box to enclose only nonzero
+ * elements, and recompute its volume and population
+ */
+static void
+update_box_gray (const CFHistogram histogram,
+ boxptr boxp)
+{
+ gint i, min, max, dist;
+ ColorFreq ccount;
+
+ min = boxp->Rmin;
+ max = boxp->Rmax;
+
+ if (max > min)
+ for (i = min; i <= max; i++)
+ {
+ if (histogram[i] != 0)
+ {
+ boxp->Rmin = min = i;
+ break;
+ }
+ }
+
+ if (max > min)
+ for (i = max; i >= min; i--)
+ {
+ if (histogram[i] != 0)
+ {
+ boxp->Rmax = max = i;
+ break;
+ }
+ }
+
+ /* Update box volume.
+ * We use 2-norm rather than real volume here; this biases the method
+ * against making long narrow boxes, and it has the side benefit that
+ * a box is splittable iff norm > 0.
+ * Since the differences are expressed in histogram-cell units,
+ * we have to shift back to JSAMPLE units to get consistent distances;
+ * after which, we scale according to the selected distance scale factors.
+ */
+ dist = max - min;
+ boxp->volume = dist * dist;
+
+ /* Now scan remaining volume of box and compute population */
+ ccount = 0;
+ for (i = min; i <= max; i++)
+ if (histogram[i] != 0)
+ ccount++;
+
+ boxp->colorcount = ccount;
+}
+
+
+/* Shrink the min/max bounds of a box to enclose only nonzero
+ * elements, and recompute its volume, population and error
+ */
+static void
+update_box_rgb (const CFHistogram histogram,
+ boxptr boxp,
+ const gint cells_remaining)
+{
+ gint R, G, B;
+ gint Rmin, Rmax, Gmin, Gmax, Bmin, Bmax;
+ gint dist0, dist1, dist2;
+ ColorFreq ccount;
+ /*
+ guint64 tempRerror;
+ guint64 tempGerror;
+ guint64 tempBerror;
+ */
+ QuantizeObj dummyqo;
+ box dummybox;
+
+ /* fprintf(stderr, "U"); */
+
+ Rmin = boxp->Rmin; Rmax = boxp->Rmax;
+ Gmin = boxp->Gmin; Gmax = boxp->Gmax;
+ Bmin = boxp->Bmin; Bmax = boxp->Bmax;
+
+ if (Rmax > Rmin)
+ for (R = Rmin; R <= Rmax; R++)
+ for (G = Gmin; G <= Gmax; G++)
+ {
+ for (B = Bmin; B <= Bmax; B++)
+ {
+ if (*HIST_LIN (histogram, R, G, B) != 0)
+ {
+ boxp->Rmin = Rmin = R;
+ goto have_Rmin;
+ }
+ }
+ }
+ have_Rmin:
+ if (Rmax > Rmin)
+ for (R = Rmax; R >= Rmin; R--)
+ for (G = Gmin; G <= Gmax; G++)
+ {
+ for (B = Bmin; B <= Bmax; B++)
+ {
+ if (*HIST_LIN (histogram, R, G, B) != 0)
+ {
+ boxp->Rmax = Rmax = R;
+ goto have_Rmax;
+ }
+ }
+ }
+ have_Rmax:
+ if (Gmax > Gmin)
+ for (G = Gmin; G <= Gmax; G++)
+ for (R = Rmin; R <= Rmax; R++)
+ {
+ for (B = Bmin; B <= Bmax; B++)
+ {
+ if (*HIST_LIN (histogram, R, G, B) != 0)
+ {
+ boxp->Gmin = Gmin = G;
+ goto have_Gmin;
+ }
+ }
+ }
+ have_Gmin:
+ if (Gmax > Gmin)
+ for (G = Gmax; G >= Gmin; G--)
+ for (R = Rmin; R <= Rmax; R++)
+ {
+ for (B = Bmin; B <= Bmax; B++)
+ {
+ if (*HIST_LIN (histogram, R, G, B) != 0)
+ {
+ boxp->Gmax = Gmax = G;
+ goto have_Gmax;
+ }
+ }
+ }
+ have_Gmax:
+ if (Bmax > Bmin)
+ for (B = Bmin; B <= Bmax; B++)
+ for (R = Rmin; R <= Rmax; R++)
+ {
+ for (G = Gmin; G <= Gmax; G++)
+ {
+ if (*HIST_LIN (histogram, R, G, B) != 0)
+ {
+ boxp->Bmin = Bmin = B;
+ goto have_Bmin;
+ }
+ }
+ }
+ have_Bmin:
+ if (Bmax > Bmin)
+ for (B = Bmax; B >= Bmin; B--)
+ for (R = Rmin; R <= Rmax; R++)
+ {
+ for (G = Gmin; G <= Gmax; G++)
+ {
+ if (*HIST_LIN (histogram, R, G, B) != 0)
+ {
+ boxp->Bmax = Bmax = B;
+ goto have_Bmax;
+ }
+ }
+ }
+ have_Bmax:
+
+ /* Update box volume.
+ * We use 2-norm rather than real volume here; this biases the method
+ * against making long narrow boxes, and it has the side benefit that
+ * a box is splittable iff norm > 0. (ADM: note: this isn't true.)
+ * Since the differences are expressed in histogram-cell units,
+ * we have to shift back to JSAMPLE units to get consistent distances;
+ * after which, we scale according to the selected distance scale factors.
+ */
+ dist0 = ((1 + Rmax - Rmin) << R_SHIFT) * R_SCALE;
+ dist1 = ((1 + Gmax - Gmin) << G_SHIFT) * G_SCALE;
+ dist2 = ((1 + Bmax - Bmin) << B_SHIFT) * B_SCALE;
+ boxp->volume = dist0*dist0 + dist1*dist1 + dist2*dist2;
+ /* boxp->volume = dist0 * dist1 * dist2; */
+
+ compute_color_lin8(&dummyqo, histogram, boxp, 0);
+
+ /*printf("(%d %d %d)\n", dummyqo.cmap[0].red,dummyqo.cmap[0].green,dummyqo.cmap[0].blue);
+ fflush(stdout);*/
+
+ /* Now scan remaining volume of box and compute population */
+ ccount = 0;
+ boxp->error = 0;
+ boxp->rerror = 0;
+ boxp->gerror = 0;
+ boxp->berror = 0;
+ for (R = Rmin; R <= Rmax; R++)
+ {
+ for (G = Gmin; G <= Gmax; G++)
+ {
+ for (B = Bmin; B <= Bmax; B++)
+ {
+ ColorFreq freq_here;
+
+ freq_here = *HIST_LIN (histogram, R, G, B);
+
+ if (freq_here != 0)
+ {
+ int ge, be, re;
+
+ dummybox.Rmin = dummybox.Rmax = R;
+ dummybox.Gmin = dummybox.Gmax = G;
+ dummybox.Bmin = dummybox.Bmax = B;
+ compute_color_lin8(&dummyqo, histogram, &dummybox, 1);
+
+ re = dummyqo.cmap[0].red - dummyqo.cmap[1].red;
+ ge = dummyqo.cmap[0].green - dummyqo.cmap[1].green;
+ be = dummyqo.cmap[0].blue - dummyqo.cmap[1].blue;
+
+ boxp->rerror += freq_here * (re) * (re);
+ boxp->gerror += freq_here * (ge) * (ge);
+ boxp->berror += freq_here * (be) * (be);
+
+ ccount += freq_here;
+ }
+ }
+ }
+ }
+
+#if 0
+ fg d;flg fd;kg fld;gflkfld
+ /* Scan again, taking note of halfway error point for red axis */
+ tempRerror = 0;
+ boxp->Rhalferror = Rmin;
+#warning r<=?
+ for (R = Rmin; R <= Rmax; R++)
+ {
+ for (G = Gmin; G <= Gmax; G++)
+ {
+ for (B = Bmin; B <= Bmax; B++)
+ {
+ ColorFreq freq_here;
+ freq_here = *HIST_LIN(histogram, R, G, B);
+ if (freq_here != 0)
+ {
+ int re;
+ int idist;
+ double dist;
+
+ dummybox.Rmin = dummybox.Rmax = R;
+ dummybox.Gmin = dummybox.Gmax = G;
+ dummybox.Bmin = dummybox.Bmax = B;
+ compute_color_lin8(&dummyqo, histogram, &dummybox, 1);
+
+ re = dummyqo.cmap[0].red - dummyqo.cmap[1].red;
+
+ tempRerror += freq_here * (re) * (re);
+
+ if (tempRerror*2 >= boxp->rerror)
+ goto green_axisscan;
+ else
+ boxp->Rhalferror = R;
+ }
+ }
+ }
+ }
+ fprintf(stderr, " D:");
+ green_axisscan:
+
+ fprintf(stderr, "<%d: %llu/%llu> ", R, tempRerror, boxp->rerror);
+ /* Scan again, taking note of halfway error point for green axis */
+ tempGerror = 0;
+ boxp->Ghalferror = Gmin;
+#warning G<=?
+ for (G = Gmin; G <= Gmax; G++)
+ {
+ for (R = Rmin; R <= Rmax; R++)
+ {
+ for (B = Bmin; B <= Bmax; B++)
+ {
+ ColorFreq freq_here;
+ freq_here = *HIST_LIN(histogram, R, G, B);
+ if (freq_here != 0)
+ {
+ int ge;
+ dummybox.Rmin = dummybox.Rmax = R;
+ dummybox.Gmin = dummybox.Gmax = G;
+ dummybox.Bmin = dummybox.Bmax = B;
+ compute_color_lin8(&dummyqo, histogram, &dummybox, 1);
+
+ ge = dummyqo.cmap[0].green - dummyqo.cmap[1].green;
+
+ tempGerror += freq_here * (ge) * (ge);
+
+ if (tempGerror*2 >= boxp->gerror)
+ goto blue_axisscan;
+ else
+ boxp->Ghalferror = G;
+ }
+ }
+ }
+ }
+
+ blue_axisscan:
+ /* Scan again, taking note of halfway error point for blue axis */
+ tempBerror = 0;
+ boxp->Bhalferror = Bmin;
+#warning B<=?
+ for (B = Bmin; B <= Bmax; B++)
+ {
+ for (R = Rmin; R <= Rmax; R++)
+ {
+ for (G = Gmin; G <= Gmax; G++)
+ {
+ ColorFreq freq_here;
+ freq_here = *HIST_LIN(histogram, R, G, B);
+ if (freq_here != 0)
+ {
+ int be;
+ dummybox.Rmin = dummybox.Rmax = R;
+ dummybox.Gmin = dummybox.Gmax = G;
+ dummybox.Bmin = dummybox.Bmax = B;
+ compute_color_lin8(&dummyqo, histogram, &dummybox, 1);
+
+ be = dummyqo.cmap[0].blue - dummyqo.cmap[1].blue;
+
+ tempBerror += freq_here * (be) * (be);
+
+ if (tempBerror*2 >= boxp->berror)
+ goto finished_axesscan;
+ else
+ boxp->Bhalferror = B;
+ }
+ }
+ }
+ }
+ finished_axesscan:
+#else
+
+ boxp->Rhalferror = Rmin + (Rmax - Rmin + 1) / 2;
+ boxp->Ghalferror = Gmin + (Gmax - Gmin + 1) / 2;
+ boxp->Bhalferror = Bmin + (Bmax - Bmin + 1) / 2;
+
+ if (dist0 && dist1 && dist2)
+ {
+ AxisType longest_ax = AXIS_UNDEF;
+ gint longest_length = 0;
+ gint longest_length2 = 0;
+ gint ratio;
+
+ /*
+ fprintf(stderr, "[%d,%d,%d=%d,%d,%d] ",
+ (Rmax - Rmin), (Gmax - Gmin), (Bmax - Bmin),
+ dist0, dist1, dist2);
+ */
+
+ if (dist0 >= longest_length)
+ {
+ longest_length2 = longest_length;
+ longest_length = dist0;
+ longest_ax = AXIS_RED;
+ }
+ else if (dist0 >= longest_length2)
+ {
+ longest_length2 = dist0;
+ }
+
+ if (dist1 >= longest_length)
+ {
+ longest_length2 = longest_length;
+ longest_length = dist1;
+ longest_ax = AXIS_GREEN;
+ }
+ else if (dist1 >= longest_length2)
+ {
+ longest_length2 = dist1;
+ }
+
+ if (dist2 >= longest_length)
+ {
+ longest_length2 = longest_length;
+ longest_length = dist2;
+ longest_ax = AXIS_BLUE;
+ }
+ else if (dist2 >= longest_length2)
+ {
+ longest_length2 = dist2;
+ }
+
+ if (longest_length2 == 0)
+ longest_length2 = 1;
+
+ ratio = (longest_length + longest_length2/2) / longest_length2;
+ /* fprintf(stderr, " ratio:(%d/%d)=%d ", longest_length, longest_length2, ratio);
+ fprintf(stderr, "C%d ", cells_remaining); */
+
+ if (ratio > cells_remaining + 1)
+ ratio = cells_remaining + 1;
+
+ if (ratio > 2)
+ {
+ switch (longest_ax)
+ {
+ case AXIS_RED:
+ if (Rmin + (Rmax - Rmin + ratio / 2) / ratio < Rmax)
+ {
+ /* fprintf(stderr, "FR%d \007\n",ratio);*/
+ boxp->Rhalferror = Rmin + (Rmax - Rmin + ratio / 2) / ratio;
+ }
+ break;
+ case AXIS_GREEN:
+ if (Gmin + (Gmax - Gmin + ratio / 2) / ratio < Gmax)
+ {
+ /* fprintf(stderr, "FG%d \007\n",ratio);*/
+ boxp->Ghalferror = Gmin + (Gmax - Gmin + ratio / 2) / ratio;
+ }
+ break;
+ case AXIS_BLUE:
+ if (Bmin + (Bmax - Bmin + ratio / 2) / ratio < Bmax)
+ {
+ /* fprintf(stderr, "FB%d \007\n",ratio);*/
+ boxp->Bhalferror = Bmin + (Bmax - Bmin + ratio / 2) / ratio;
+ }
+ break;
+ default:
+ g_warning ("GRR, UNDEF LONGEST AXIS\007\n");
+ }
+ }
+ }
+
+ if (boxp->Rhalferror == Rmax)
+ boxp->Rhalferror = Rmin;
+ if (boxp->Ghalferror == Gmax)
+ boxp->Ghalferror = Gmin;
+ if (boxp->Bhalferror == Bmax)
+ boxp->Bhalferror = Bmin;
+
+ /*
+ boxp->Rhalferror = RSDF(dummyqo.cmap[0].red);
+ boxp->Ghalferror = GSDF(dummyqo.cmap[0].green);
+ boxp->Bhalferror = BSDF(dummyqo.cmap[0].blue);
+ */
+
+ /*
+ boxp->Rhalferror = (RSDF(dummyqo.cmap[0].red) + (Rmin+Rmax)/2)/2;
+ boxp->Ghalferror = (GSDF(dummyqo.cmap[0].green) + (Gmin+Gmax)/2)/2;
+ boxp->Bhalferror = (BSDF(dummyqo.cmap[0].blue) + (Bmin+Bmax)/2)/2;
+ */
+
+
+#endif
+ /*
+ fprintf(stderr, " %d,%d", dummyqo.cmap[0].blue, boxp->Bmax);
+
+ gimp_assert (boxp->Rhalferror >= boxp->Rmin);
+ gimp_assert (boxp->Rhalferror < boxp->Rmax);
+ gimp_assert (boxp->Ghalferror >= boxp->Gmin);
+ gimp_assert (boxp->Ghalferror < boxp->Gmax);
+ gimp_assert (boxp->Bhalferror >= boxp->Bmin);
+ gimp_assert (boxp->Bhalferror < boxp->Bmax);*/
+
+ /*boxp->error = (sqrt((double)(boxp->error/ccount)));*/
+ /* boxp->rerror = (sqrt((double)((boxp->rerror)/ccount)));
+ boxp->gerror = (sqrt((double)((boxp->gerror)/ccount)));
+ boxp->berror = (sqrt((double)((boxp->berror)/ccount)));*/
+ /*printf(":%lld / %ld: ", boxp->error, ccount);
+ printf("(%d-%d-%d)(%d-%d-%d)(%d-%d-%d)\n",
+ Rmin, boxp->Rhalferror, Rmax,
+ Gmin, boxp->Ghalferror, Gmax,
+ Bmin, boxp->Bhalferror, Bmax
+ );
+ fflush(stdout);*/
+
+ boxp->colorcount = ccount;
+}
+
+
+/* Repeatedly select and split the largest box until we have enough
+ * boxes
+ */
+static gint
+median_cut_gray (CFHistogram histogram,
+ boxptr boxlist,
+ gint numboxes,
+ gint desired_colors)
+{
+ gint lb;
+ boxptr b1, b2;
+
+ while (numboxes < desired_colors)
+ {
+ /* Select box to split.
+ * Current algorithm: by population for first half, then by volume.
+ */
+
+ b1 = find_biggest_volume (boxlist, numboxes);
+
+ if (b1 == NULL) /* no splittable boxes left! */
+ break;
+
+ b2 = boxlist + numboxes; /* where new box will go */
+ /* Copy the color bounds to the new box. */
+ b2->Rmax = b1->Rmax;
+ b2->Rmin = b1->Rmin;
+
+ /* Current algorithm: split at halfway point.
+ * (Since the box has been shrunk to minimum volume,
+ * any split will produce two nonempty subboxes.)
+ * Note that lb value is max for lower box, so must be < old max.
+ */
+ lb = (b1->Rmax + b1->Rmin) / 2;
+ b1->Rmax = lb;
+ b2->Rmin = lb + 1;
+
+ /* Update stats for boxes */
+ update_box_gray (histogram, b1);
+ update_box_gray (histogram, b2);
+ numboxes++;
+ }
+
+ return numboxes;
+}
+
+/* Repeatedly select and split the largest box until we have enough
+ * boxes
+ */
+static gint
+median_cut_rgb (CFHistogram histogram,
+ boxptr boxlist,
+ gint numboxes,
+ gint desired_colors,
+ GimpProgress *progress)
+{
+ gint lb;
+ boxptr b1, b2;
+ AxisType which_axis;
+
+ while (numboxes < desired_colors)
+ {
+ b1 = find_split_candidate (boxlist, numboxes, &which_axis, desired_colors);
+
+ if (b1 == NULL) /* no splittable boxes left! */
+ break;
+
+ b2 = boxlist + numboxes; /* where new box will go */
+ /* Copy the color bounds to the new box. */
+ b2->Rmax = b1->Rmax; b2->Gmax = b1->Gmax; b2->Bmax = b1->Bmax;
+ b2->Rmin = b1->Rmin; b2->Gmin = b1->Gmin; b2->Bmin = b1->Bmin;
+
+
+ /* Choose split point along selected axis, and update box bounds.
+ * Note that lb value is max for lower box, so must be < old max.
+ */
+ switch (which_axis)
+ {
+ case AXIS_RED:
+ lb = b1->Rhalferror;/* *0 + (b1->Rmax + b1->Rmin) / 2; */
+ b1->Rmax = lb;
+ b2->Rmin = lb+1;
+ g_return_val_if_fail (b1->Rmax >= b1->Rmin, numboxes);
+ g_return_val_if_fail (b2->Rmax >= b2->Rmin, numboxes);
+ break;
+ case AXIS_GREEN:
+ lb = b1->Ghalferror;/* *0 + (b1->Gmax + b1->Gmin) / 2; */
+ b1->Gmax = lb;
+ b2->Gmin = lb+1;
+ g_return_val_if_fail (b1->Gmax >= b1->Gmin, numboxes);
+ g_return_val_if_fail (b2->Gmax >= b2->Gmin, numboxes);
+ break;
+ case AXIS_BLUE:
+ lb = b1->Bhalferror;/* *0 + (b1->Bmax + b1->Bmin) / 2; */
+ b1->Bmax = lb;
+ b2->Bmin = lb+1;
+ g_return_val_if_fail (b1->Bmax >= b1->Bmin, numboxes);
+ g_return_val_if_fail (b2->Bmax >= b2->Bmin, numboxes);
+ break;
+ default:
+ g_error ("Uh-oh.");
+ }
+ /* Update stats for boxes */
+ numboxes++;
+
+ if (progress && (numboxes % 16 == 0))
+ gimp_progress_set_value (progress, (gdouble) numboxes / desired_colors);
+
+ update_box_rgb (histogram, b1, desired_colors - numboxes);
+ update_box_rgb (histogram, b2, desired_colors - numboxes);
+ }
+
+ return numboxes;
+}
+
+
+/* Compute representative color for a box, put it in colormap[icolor]
+ */
+static void
+compute_color_gray (QuantizeObj *quantobj,
+ CFHistogram histogram,
+ boxptr boxp,
+ int icolor)
+{
+ gint i, min, max;
+ guint64 count;
+ guint64 total;
+ guint64 gtotal;
+
+ min = boxp->Rmin;
+ max = boxp->Rmax;
+
+ total = 0;
+ gtotal = 0;
+
+ for (i = min; i <= max; i++)
+ {
+ count = histogram[i];
+ if (count != 0)
+ {
+ total += count;
+ gtotal += i * count;
+ }
+ }
+
+ if (total != 0)
+ {
+ quantobj->cmap[icolor].red =
+ quantobj->cmap[icolor].green =
+ quantobj->cmap[icolor].blue = (gtotal + (total >> 1)) / total;
+ }
+ else
+ {
+ /* The only situation where total==0 is if the image was null or
+ * all-transparent. In that case we just put a dummy value in
+ * the colormap.
+ */
+ quantobj->cmap[icolor].red =
+ quantobj->cmap[icolor].green =
+ quantobj->cmap[icolor].blue = 0;
+ }
+}
+
+
+/* Compute representative color for a box, put it in colormap[icolor]
+ */
+static void
+compute_color_rgb (QuantizeObj *quantobj,
+ CFHistogram histogram,
+ boxptr boxp,
+ int icolor)
+{
+ /* Current algorithm: mean weighted by pixels (not colors) */
+ /* Note it is important to get the rounding correct! */
+ gint R, G, B;
+ gint Rmin, Rmax;
+ gint Gmin, Gmax;
+ gint Bmin, Bmax;
+ ColorFreq total = 0;
+ ColorFreq Rtotal = 0;
+ ColorFreq Gtotal = 0;
+ ColorFreq Btotal = 0;
+
+ Rmin = boxp->Rmin; Rmax = boxp->Rmax;
+ Gmin = boxp->Gmin; Gmax = boxp->Gmax;
+ Bmin = boxp->Bmin; Bmax = boxp->Bmax;
+
+ for (R = Rmin; R <= Rmax; R++)
+ for (G = Gmin; G <= Gmax; G++)
+ {
+ for (B = Bmin; B <= Bmax; B++)
+ {
+ ColorFreq this_freq = *HIST_LIN (histogram, R, G, B);
+
+ if (this_freq != 0)
+ {
+ total += this_freq;
+ Rtotal += R * this_freq;
+ Gtotal += G * this_freq;
+ Btotal += B * this_freq;
+ }
+ }
+ }
+
+ if (total > 0)
+ {
+ guchar red, green, blue;
+
+ lin_to_rgb (/*(Rtotal + (total>>1)) / total,
+ (Gtotal + (total>>1)) / total,
+ (Btotal + (total>>1)) / total,*/
+ (double)Rtotal / (double)total,
+ (double)Gtotal / (double)total,
+ (double)Btotal / (double)total,
+ &red, &green, &blue);
+
+ quantobj->cmap[icolor].red = red;
+ quantobj->cmap[icolor].green = green;
+ quantobj->cmap[icolor].blue = blue;
+ }
+ else
+ {
+ /* The only situation where total==0 is if the image was null or
+ * all-transparent. In that case we just put a dummy value in
+ * the colormap.
+ */
+ quantobj->cmap[icolor].red = 0;
+ quantobj->cmap[icolor].green = 0;
+ quantobj->cmap[icolor].blue = 0;
+ }
+}
+
+
+/* Compute representative color for a box, put it in colormap[icolor]
+ */
+static void
+compute_color_lin8 (QuantizeObj *quantobj,
+ CFHistogram histogram,
+ boxptr boxp,
+ const gint icolor)
+{
+ /* Current algorithm: mean weighted by pixels (not colors) */
+ /* Note it is important to get the rounding correct! */
+ gint R, G, B;
+ gint Rmin, Rmax;
+ gint Gmin, Gmax;
+ gint Bmin, Bmax;
+ ColorFreq total = 0;
+ ColorFreq Rtotal = 0;
+ ColorFreq Gtotal = 0;
+ ColorFreq Btotal = 0;
+
+ Rmin = boxp->Rmin; Rmax = boxp->Rmax;
+ Gmin = boxp->Gmin; Gmax = boxp->Gmax;
+ Bmin = boxp->Bmin; Bmax = boxp->Bmax;
+
+ for (R = Rmin; R <= Rmax; R++)
+ for (G = Gmin; G <= Gmax; G++)
+ {
+ for (B = Bmin; B <= Bmax; B++)
+ {
+ ColorFreq this_freq = *HIST_LIN (histogram, R, G, B);
+
+ if (this_freq != 0)
+ {
+ Rtotal += R * this_freq;
+ Gtotal += G * this_freq;
+ Btotal += B * this_freq;
+ total += this_freq;
+ }
+ }
+ }
+
+ if (total != 0)
+ {
+ quantobj->cmap[icolor].red = ((Rtotal << R_SHIFT) + (total>>1)) / total;
+ quantobj->cmap[icolor].green = ((Gtotal << G_SHIFT) + (total>>1)) / total;
+ quantobj->cmap[icolor].blue = ((Btotal << B_SHIFT) + (total>>1)) / total;
+ }
+ else
+ {
+ /* The only situation where total==0 is if the image was null or
+ * all-transparent. In that case we just put a dummy value in
+ * the colormap.
+ */
+ g_warning ("eep.");
+ quantobj->cmap[icolor].red = 0;
+ quantobj->cmap[icolor].green = 128;
+ quantobj->cmap[icolor].blue = 128;
+ }
+}
+
+
+/* Master routine for color selection
+ */
+static void
+select_colors_gray (QuantizeObj *quantobj,
+ CFHistogram histogram)
+{
+ boxptr boxlist;
+ gint numboxes;
+ gint desired = quantobj->desired_number_of_colors;
+ gint i;
+
+ /* Allocate workspace for box list */
+ boxlist = g_new (box, desired);
+
+ /* Initialize one box containing whole space */
+ numboxes = 1;
+ boxlist[0].Rmin = 0;
+ boxlist[0].Rmax = 255;
+ /* Shrink it to actually-used volume and set its statistics */
+ update_box_gray (histogram, boxlist);
+ /* Perform median-cut to produce final box list */
+ numboxes = median_cut_gray (histogram, boxlist, numboxes, desired);
+
+ quantobj->actual_number_of_colors = numboxes;
+ /* Compute the representative color for each box, fill colormap */
+ for (i = 0; i < numboxes; i++)
+ compute_color_gray (quantobj, histogram, boxlist + i, i);
+}
+
+
+/* Master routine for color selection
+ */
+static void
+select_colors_rgb (QuantizeObj *quantobj,
+ CFHistogram histogram)
+{
+ boxptr boxlist;
+ gint numboxes;
+ gint desired = quantobj->desired_number_of_colors;
+ gint i;
+
+ /* Allocate workspace for box list */
+ boxlist = g_new (box, desired);
+
+ /* Initialize one box containing whole space */
+ numboxes = 1;
+ boxlist[0].Rmin = 0;
+ boxlist[0].Rmax = HIST_R_ELEMS - 1;
+ boxlist[0].Gmin = 0;
+ boxlist[0].Gmax = HIST_G_ELEMS - 1;
+ boxlist[0].Bmin = 0;
+ boxlist[0].Bmax = HIST_B_ELEMS - 1;
+ /* Shrink it to actually-used volume and set its statistics */
+ update_box_rgb (histogram, &boxlist[0], quantobj->desired_number_of_colors);
+ /* Perform median-cut to produce final box list */
+ numboxes = median_cut_rgb (histogram, boxlist, numboxes, desired,
+ quantobj->progress);
+
+ quantobj->actual_number_of_colors = numboxes;
+ /* Compute the representative color for each box, fill colormap */
+ for (i = 0; i < numboxes; i++)
+ {
+ compute_color_rgb (quantobj, histogram, &boxlist[i], i);
+ }
+
+ g_free (boxlist);
+}
+
+
+/*
+ * These routines are concerned with the time-critical task of mapping input
+ * colors to the nearest color in the selected colormap.
+ *
+ * We re-use the histogram space as an "inverse color map", essentially a
+ * cache for the results of nearest-color searches. All colors within a
+ * histogram cell will be mapped to the same colormap entry, namely the one
+ * closest to the cell's center. This may not be quite the closest entry to
+ * the actual input color, but it's almost as good. A zero in the cache
+ * indicates we haven't found the nearest color for that cell yet; the array
+ * is cleared to zeroes before starting the mapping pass. When we find the
+ * nearest color for a cell, its colormap index plus one is recorded in the
+ * cache for future use. The pass2 scanning routines call fill_inverse_cmap
+ * when they need to use an unfilled entry in the cache.
+ *
+ * Our method of efficiently finding nearest colors is based on the "locally
+ * sorted search" idea described by Heckbert and on the incremental distance
+ * calculation described by Spencer W. Thomas in chapter III.1 of Graphics
+ * Gems II (James Arvo, ed. Academic Press, 1991). Thomas points out that
+ * the distances from a given colormap entry to each cell of the histogram can
+ * be computed quickly using an incremental method: the differences between
+ * distances to adjacent cells themselves differ by a constant. This allows a
+ * fairly fast implementation of the "brute force" approach of computing the
+ * distance from every colormap entry to every histogram cell. Unfortunately,
+ * it needs a work array to hold the best-distance-so-far for each histogram
+ * cell (because the inner loop has to be over cells, not colormap entries).
+ * The work array elements have to be ints, so the work array would need
+ * 256Kb at our recommended precision. This is not feasible in DOS machines.
+ *
+ * To get around these problems, we apply Thomas' method to compute the
+ * nearest colors for only the cells within a small subbox of the histogram.
+ * The work array need be only as big as the subbox, so the memory usage
+ * problem is solved. Furthermore, we need not fill subboxes that are never
+ * referenced in pass2; many images use only part of the color gamut, so a
+ * fair amount of work is saved. An additional advantage of this
+ * approach is that we can apply Heckbert's locality criterion to quickly
+ * eliminate colormap entries that are far away from the subbox; typically
+ * three-fourths of the colormap entries are rejected by Heckbert's criterion,
+ * and we need not compute their distances to individual cells in the subbox.
+ * The speed of this approach is heavily influenced by the subbox size: too
+ * small means too much overhead, too big loses because Heckbert's criterion
+ * can't eliminate as many colormap entries. Empirically the best subbox
+ * size seems to be about 1/512th of the histogram (1/8th in each direction).
+ *
+ * Thomas' article also describes a refined method which is asymptotically
+ * faster than the brute-force method, but it is also far more complex and
+ * cannot efficiently be applied to small subboxes. It is therefore not
+ * useful for programs intended to be portable to DOS machines. On machines
+ * with plenty of memory, filling the whole histogram in one shot with Thomas'
+ * refined method might be faster than the present code --- but then again,
+ * it might not be any faster, and it's certainly more complicated.
+ */
+
+
+/* log2(histogram cells in update box) for each axis; this can be adjusted */
+/*#define BOX_R_LOG (PRECISION_R-3)
+ #define BOX_G_LOG (PRECISION_G-3)
+ #define BOX_B_LOG (PRECISION_B-3)*/
+
+/*adam*/
+#define BOX_R_LOG 0
+#define BOX_G_LOG 0
+#define BOX_B_LOG 0
+
+#define BOX_R_ELEMS (1<<BOX_R_LOG) /* # of hist cells in update box */
+#define BOX_G_ELEMS (1<<BOX_G_LOG)
+#define BOX_B_ELEMS (1<<BOX_B_LOG)
+
+#define BOX_R_SHIFT (R_SHIFT + BOX_R_LOG)
+#define BOX_G_SHIFT (G_SHIFT + BOX_G_LOG)
+#define BOX_B_SHIFT (B_SHIFT + BOX_B_LOG)
+
+
+/*
+ * The next three routines implement inverse colormap filling. They
+ * could all be folded into one big routine, but splitting them up
+ * this way saves some stack space (the mindist[] and bestdist[]
+ * arrays need not coexist) and may allow some compilers to produce
+ * better code by registerizing more inner-loop variables.
+ */
+
+/* Locate the colormap entries close enough to an update box to be
+ * candidates for the nearest entry to some cell(s) in the update box.
+ * The update box is specified by the center coordinates of its first
+ * cell. The number of candidate colormap entries is returned, and
+ * their colormap indexes are placed in colorlist[].
+ *
+ * This routine uses Heckbert's "locally sorted search" criterion to
+ * select the colors that need further consideration.
+ */
+static gint
+find_nearby_colors (QuantizeObj *quantobj,
+ int minR,
+ int minG,
+ int minB,
+ int colorlist[])
+{
+ int numcolors = quantobj->actual_number_of_colors;
+ int maxR, maxG, maxB;
+ int centerR, centerG, centerB;
+ int i, x, ncolors;
+ int minmaxdist, min_dist, max_dist, tdist;
+ int mindist[MAXNUMCOLORS]; /* min distance to colormap entry i */
+
+ /* Compute true coordinates of update box's upper corner and center.
+ * Actually we compute the coordinates of the center of the upper-corner
+ * histogram cell, which are the upper bounds of the volume we care about.
+ * Note that since ">>" rounds down, the "center" values may be closer to
+ * min than to max; hence comparisons to them must be "<=", not "<".
+ */
+ maxR = minR + ((1 << BOX_R_SHIFT) - (1 << R_SHIFT));
+ centerR = (minR + maxR + 1) >> 1;
+ maxG = minG + ((1 << BOX_G_SHIFT) - (1 << G_SHIFT));
+ centerG = (minG + maxG + 1) >> 1;
+ maxB = minB + ((1 << BOX_B_SHIFT) - (1 << B_SHIFT));
+ centerB = (minB + maxB + 1) >> 1;
+
+ /* For each color in colormap, find:
+ * 1. its minimum squared-distance to any point in the update box
+ * (zero if color is within update box);
+ * 2. its maximum squared-distance to any point in the update box.
+ * Both of these can be found by considering only the corners of the box.
+ * We save the minimum distance for each color in mindist[];
+ * only the smallest maximum distance is of interest.
+ */
+ minmaxdist = 0x7FFFFFFFL;
+
+ for (i = 0; i < numcolors; i++)
+ {
+ /* We compute the squared-R-distance term, then add in the other two. */
+ x = quantobj->clin[i].red;
+ if (x < minR)
+ {
+ tdist = (x - minR) * R_SCALE;
+ min_dist = tdist*tdist;
+ tdist = (x - maxR) * R_SCALE;
+ max_dist = tdist*tdist;
+ }
+ else if (x > maxR)
+ {
+ tdist = (x - maxR) * R_SCALE;
+ min_dist = tdist*tdist;
+ tdist = (x - minR) * R_SCALE;
+ max_dist = tdist*tdist;
+ }
+ else
+ {
+ /* within cell range so no contribution to min_dist */
+ min_dist = 0;
+ if (x <= centerR)
+ {
+ tdist = (x - maxR) * R_SCALE;
+ max_dist = tdist*tdist;
+ }
+ else
+ {
+ tdist = (x - minR) * R_SCALE;
+ max_dist = tdist*tdist;
+ }
+ }
+
+ x = quantobj->clin[i].green;
+ if (x < minG)
+ {
+ tdist = (x - minG) * G_SCALE;
+ min_dist += tdist*tdist;
+ tdist = (x - maxG) * G_SCALE;
+ max_dist += tdist*tdist;
+ }
+ else if (x > maxG)
+ {
+ tdist = (x - maxG) * G_SCALE;
+ min_dist += tdist*tdist;
+ tdist = (x - minG) * G_SCALE;
+ max_dist += tdist*tdist;
+ }
+ else
+ {
+ /* within cell range so no contribution to min_dist */
+ if (x <= centerG)
+ {
+ tdist = (x - maxG) * G_SCALE;
+ max_dist += tdist*tdist;
+ }
+ else
+ {
+ tdist = (x - minG) * G_SCALE;
+ max_dist += tdist*tdist;
+ }
+ }
+
+ x = quantobj->clin[i].blue;
+ if (x < minB)
+ {
+ tdist = (x - minB) * B_SCALE;
+ min_dist += tdist*tdist;
+ tdist = (x - maxB) * B_SCALE;
+ max_dist += tdist*tdist;
+ }
+ else if (x > maxB)
+ {
+ tdist = (x - maxB) * B_SCALE;
+ min_dist += tdist*tdist;
+ tdist = (x - minB) * B_SCALE;
+ max_dist += tdist*tdist;
+ }
+ else
+ {
+ /* within cell range so no contribution to min_dist */
+ if (x <= centerB)
+ {
+ tdist = (x - maxB) * B_SCALE;
+ max_dist += tdist*tdist;
+ }
+ else
+ {
+ tdist = (x - minB) * B_SCALE;
+ max_dist += tdist*tdist;
+ }
+ }
+
+ mindist[i] = min_dist; /* save away the results */
+ if (max_dist < minmaxdist)
+ minmaxdist = max_dist;
+ }
+
+ /* Now we know that no cell in the update box is more than minmaxdist
+ * away from some colormap entry. Therefore, only colors that are
+ * within minmaxdist of some part of the box need be considered.
+ */
+ ncolors = 0;
+ for (i = 0; i < numcolors; i++)
+ {
+ if (mindist[i] <= minmaxdist)
+ colorlist[ncolors++] = i;
+ }
+
+ return ncolors;
+}
+
+
+/* Find the closest colormap entry for each cell in the update box,
+ * given the list of candidate colors prepared by find_nearby_colors.
+ * Return the indexes of the closest entries in the bestcolor[] array.
+ * This routine uses Thomas' incremental distance calculation method
+ * to find the distance from a colormap entry to successive cells in
+ * the box.
+ */
+static void
+find_best_colors (QuantizeObj *quantobj,
+ gint minR,
+ gint minG,
+ gint minB,
+ gint numcolors,
+ gint colorlist[],
+ gint bestcolor[])
+{
+ gint iR, iG, iB;
+ gint i, icolor;
+ gint *bptr; /* pointer into bestdist[] array */
+ gint *cptr; /* pointer into bestcolor[] array */
+ gint dist0, dist1; /* initial distance values */
+ gint dist2; /* current distance in inner loop */
+ gint xx0, xx1; /* distance increments */
+ gint xx2;
+ gint inR, inG, inB; /* initial values for increments */
+
+ /* This array holds the distance to the nearest-so-far color for each cell */
+ gint bestdist[BOX_R_ELEMS * BOX_G_ELEMS * BOX_B_ELEMS] = { 0, };
+
+ /* Initialize best-distance for each cell of the update box */
+ bptr = bestdist;
+ for (i = BOX_R_ELEMS*BOX_G_ELEMS*BOX_B_ELEMS-1; i >= 0; i--)
+ *bptr++ = 0x7FFFFFFFL;
+
+ /* For each color selected by find_nearby_colors,
+ * compute its distance to the center of each cell in the box.
+ * If that's less than best-so-far, update best distance and color number.
+ */
+
+ /* Nominal steps between cell centers ("x" in Thomas article) */
+#define STEP_R ((1 << R_SHIFT) * R_SCALE)
+#define STEP_G ((1 << G_SHIFT) * G_SCALE)
+#define STEP_B ((1 << B_SHIFT) * B_SCALE)
+
+ for (i = 0; i < numcolors; i++)
+ {
+ icolor = colorlist[i];
+ /* Compute (square of) distance from minR/G/B to this color */
+ inR = (minR - quantobj->clin[icolor].red) * R_SCALE;
+ dist0 = inR*inR;
+ /* special-case for L*==0: chroma diffs irrelevant */
+ /* if (minR > 0 || quantobj->clin[icolor].red > 0) */
+ {
+ inG = (minG - quantobj->clin[icolor].green) * G_SCALE;
+ dist0 += inG*inG;
+ inB = (minB - quantobj->clin[icolor].blue) * B_SCALE;
+ dist0 += inB*inB;
+ }
+ /* else
+ {
+ inG = 0;
+ inB = 0;
+ } */
+ /* Form the initial difference increments */
+ inR = inR * (2 * STEP_R) + STEP_R * STEP_R;
+ inG = inG * (2 * STEP_G) + STEP_G * STEP_G;
+ inB = inB * (2 * STEP_B) + STEP_B * STEP_B;
+ /* Now loop over all cells in box, updating distance per Thomas method */
+ bptr = bestdist;
+ cptr = bestcolor;
+ xx0 = inR;
+ for (iR = BOX_R_ELEMS-1; iR >= 0; iR--)
+ {
+ dist1 = dist0;
+ xx1 = inG;
+ for (iG = BOX_G_ELEMS-1; iG >= 0; iG--)
+ {
+ dist2 = dist1;
+ xx2 = inB;
+ for (iB = BOX_B_ELEMS-1; iB >= 0; iB--)
+ {
+ if (dist2 < *bptr)
+ {
+ *bptr = dist2;
+ *cptr = icolor;
+ }
+ dist2 += xx2;
+ xx2 += 2 * STEP_B * STEP_B;
+ bptr++;
+ cptr++;
+ }
+ dist1 += xx1;
+ xx1 += 2 * STEP_G * STEP_G;
+ }
+ dist0 += xx0;
+ xx0 += 2 * STEP_R * STEP_R;
+ }
+ }
+}
+
+
+/* Fill the inverse-colormap entries in the update box that contains
+ * histogram cell R/G/B. (Only that one cell MUST be filled, but we
+ * can fill as many others as we wish.)
+ */
+static void
+fill_inverse_cmap_gray (QuantizeObj *quantobj,
+ CFHistogram histogram,
+ gint pixel)
+{
+ Color *cmap = quantobj->cmap;
+ gint64 mindist;
+ gint mindisti;
+ gint i;
+
+ g_return_if_fail (quantobj->actual_number_of_colors > 0);
+
+ mindist = G_MAXLONG;
+ mindisti = -1;
+
+ for (i = 0; i < quantobj->actual_number_of_colors; i++)
+ {
+ gint64 dist = ABS (pixel - cmap[i].red);
+
+ if (dist < mindist)
+ {
+ mindist = dist;
+ mindisti = i;
+
+ if (mindist == 0)
+ break;
+ }
+ }
+
+ histogram[pixel] = mindisti + 1;
+}
+
+
+/* Fill the inverse-colormap entries in the update box that contains
+ * histogram cell R/G/B. (Only that one cell MUST be filled, but we
+ * can fill as many others as we wish.)
+ */
+static void
+fill_inverse_cmap_rgb (QuantizeObj *quantobj,
+ CFHistogram histogram,
+ gint R,
+ gint G,
+ gint B)
+{
+ gint minR, minG, minB; /* lower left corner of update box */
+ gint iR, iG, iB;
+ gint *cptr; /* pointer into bestcolor[] array */
+ /* This array lists the candidate colormap indexes. */
+ gint colorlist[MAXNUMCOLORS];
+ gint numcolors; /* number of candidate colors */
+ /* This array holds the actually closest colormap index for each cell. */
+ gint bestcolor[BOX_R_ELEMS * BOX_G_ELEMS * BOX_B_ELEMS] = { 0, };
+
+ /* Convert cell coordinates to update box id */
+ R >>= BOX_R_LOG;
+ G >>= BOX_G_LOG;
+ B >>= BOX_B_LOG;
+
+ /* Compute true coordinates of update box's origin corner.
+ * Actually we compute the coordinates of the center of the corner
+ * histogram cell, which are the lower bounds of the volume we care about.
+ */
+ minR = (R << BOX_R_SHIFT) + ((1 << R_SHIFT) >> 1);
+ minG = (G << BOX_G_SHIFT) + ((1 << G_SHIFT) >> 1);
+ minB = (B << BOX_B_SHIFT) + ((1 << B_SHIFT) >> 1);
+
+ /* Determine which colormap entries are close enough to be candidates
+ * for the nearest entry to some cell in the update box.
+ */
+ numcolors = find_nearby_colors (quantobj, minR, minG, minB, colorlist);
+
+ /* Determine the actually nearest colors. */
+ find_best_colors (quantobj, minR, minG, minB, numcolors, colorlist,
+ bestcolor);
+
+ /* Save the best color numbers (plus 1) in the main cache array */
+ R <<= BOX_R_LOG; /* convert id back to base cell indexes */
+ G <<= BOX_G_LOG;
+ B <<= BOX_B_LOG;
+ cptr = bestcolor;
+ for (iR = 0; iR < BOX_R_ELEMS; iR++)
+ {
+ for (iG = 0; iG < BOX_G_ELEMS; iG++)
+ {
+ for (iB = 0; iB < BOX_B_ELEMS; iB++)
+ {
+ *HIST_LIN (histogram, R + iR, G + iG, B + iB) = (*cptr++) + 1;
+ }
+ }
+ }
+}
+
+
+/* This is pass 1 */
+
+static void
+median_cut_pass1_gray (QuantizeObj *quantobj)
+{
+ select_colors_gray (quantobj, quantobj->histogram);
+}
+
+static void
+snap_to_black_and_white (QuantizeObj *quantobj)
+{
+ /* find whitest and blackest colors in palette, if they are closer
+ * than 24 units of euclidian distance in sRGB snap them to pure
+ * black / white.
+ */
+#define POW2(a) ((a)*(a))
+ gint desired = quantobj->desired_number_of_colors;
+ gint whitest = 0;
+ gint blackest = 0;
+
+ gint64 white_dist = POW2(255) * 3;
+ gint64 black_dist = POW2(255) * 3;
+ gint i;
+
+ for (i = 0; i < desired; i ++)
+ {
+ int dist;
+
+ dist = POW2 (quantobj->cmap[i].red - 255) +
+ POW2 (quantobj->cmap[i].green - 255) +
+ POW2( quantobj->cmap[i].blue - 255);
+ if (dist < white_dist)
+ {
+ white_dist = dist;
+ whitest = i;
+ }
+
+ dist = POW2(quantobj->cmap[i].red - 0) +
+ POW2(quantobj->cmap[i].green - 0) +
+ POW2(quantobj->cmap[i].blue - 0);
+ if (dist < black_dist)
+ {
+ black_dist = dist;
+ blackest = i;
+ }
+ }
+
+ if (desired > 2 &&
+ had_white &&
+ white_dist < POW2(128))
+ {
+ quantobj->cmap[whitest].red =
+ quantobj->cmap[whitest].green =
+ quantobj->cmap[whitest].blue = 255;
+ }
+ if (desired > 2 &&
+ had_black &&
+ black_dist < POW2(128))
+ {
+ quantobj->cmap[blackest].red =
+ quantobj->cmap[blackest].green =
+ quantobj->cmap[blackest].blue = 0;
+ }
+#undef POW2
+}
+
+static void
+median_cut_pass1_rgb (QuantizeObj *quantobj)
+{
+ select_colors_rgb (quantobj, quantobj->histogram);
+ snap_to_black_and_white (quantobj);
+}
+
+
+static void
+monopal_pass1 (QuantizeObj *quantobj)
+{
+ quantobj->actual_number_of_colors = 2;
+
+ quantobj->cmap[0].red = 0;
+ quantobj->cmap[0].green = 0;
+ quantobj->cmap[0].blue = 0;
+ quantobj->cmap[1].red = 255;
+ quantobj->cmap[1].green = 255;
+ quantobj->cmap[1].blue = 255;
+}
+
+static void
+webpal_pass1 (QuantizeObj *quantobj)
+{
+ int i;
+
+ quantobj->actual_number_of_colors = 216;
+
+ for (i=0; i < 216; i++)
+ {
+ quantobj->cmap[i].red = webpal[i * 3];
+ quantobj->cmap[i].green = webpal[i * 3 +1];
+ quantobj->cmap[i].blue = webpal[i * 3 +2];
+ }
+}
+
+static void
+custompal_pass1 (QuantizeObj *quantobj)
+{
+ gint i;
+ GList *list;
+
+ /* fprintf(stderr,
+ "custompal_pass1: using (theCustomPalette %s) from (file %s)\n",
+ theCustomPalette->name, theCustomPalette->filename); */
+
+ for (i = 0, list = gimp_palette_get_colors (quantobj->custom_palette);
+ list;
+ i++, list = g_list_next (list))
+ {
+ GimpPaletteEntry *entry = list->data;
+ guchar r, g, b;
+
+ gimp_rgb_get_uchar (&entry->color, &r, &g, &b);
+
+ quantobj->cmap[i].red = (gint) r;
+ quantobj->cmap[i].green = (gint) g;
+ quantobj->cmap[i].blue = (gint) b;
+ }
+
+ quantobj -> actual_number_of_colors = i;
+}
+
+/*
+ * Map some rows of pixels to the output colormapped representation.
+ */
+
+static void
+median_cut_pass2_no_dither_gray (QuantizeObj *quantobj,
+ GimpLayer *layer,
+ GeglBuffer *new_buffer)
+{
+ GeglBufferIterator *iter;
+ CFHistogram histogram = quantobj->histogram;
+ ColorFreq *cachep;
+ const Babl *src_format;
+ const Babl *dest_format;
+ GeglRectangle *src_roi;
+ gint src_bpp;
+ gint dest_bpp;
+ gint has_alpha;
+ guint64 *index_used_count = quantobj->index_used_count;
+ gboolean dither_alpha = quantobj->want_dither_alpha;
+ gint offsetx, offsety;
+
+ gimp_item_get_offset (GIMP_ITEM (layer), &offsetx, &offsety);
+
+ src_format = gimp_drawable_get_format (GIMP_DRAWABLE (layer));
+ dest_format = gegl_buffer_get_format (new_buffer);
+
+ src_bpp = babl_format_get_bytes_per_pixel (src_format);
+ dest_bpp = babl_format_get_bytes_per_pixel (dest_format);
+
+ has_alpha = babl_format_has_alpha (src_format);
+
+ iter = gegl_buffer_iterator_new (gimp_drawable_get_buffer (GIMP_DRAWABLE (layer)),
+ NULL, 0, NULL,
+ GEGL_ACCESS_READ, GEGL_ABYSS_NONE, 2);
+ src_roi = &iter->items[0].roi;
+
+ gegl_buffer_iterator_add (iter, new_buffer,
+ NULL, 0, NULL,
+ GEGL_ACCESS_WRITE, GEGL_ABYSS_NONE);
+
+ while (gegl_buffer_iterator_next (iter))
+ {
+ const guchar *src = iter->items[0].data;
+ guchar *dest = iter->items[1].data;
+ gint row;
+
+ for (row = 0; row < src_roi->height; row++)
+ {
+ gint col;
+
+ for (col = 0; col < src_roi->width; col++)
+ {
+ /* get pixel value and index into the cache */
+ gint pixel = src[GRAY];
+
+ cachep = &histogram[pixel];
+ /* If we have not seen this color before, find nearest
+ * colormap entry and update the cache
+ */
+ if (*cachep == 0)
+ fill_inverse_cmap_gray (quantobj, histogram, pixel);
+
+ if (has_alpha)
+ {
+ gboolean transparent = FALSE;
+
+ if (dither_alpha)
+ {
+ gint dither_x = (col + offsetx + src_roi->x) & DM_WIDTHMASK;
+ gint dither_y = (row + offsety + src_roi->y) & DM_HEIGHTMASK;
+
+ if ((src[ALPHA_G]) < DM[dither_x][dither_y])
+ transparent = TRUE;
+ }
+ else
+ {
+ if (src[ALPHA_G] <= 127)
+ transparent = TRUE;
+ }
+
+ if (transparent)
+ {
+ dest[ALPHA_I] = 0;
+ }
+ else
+ {
+ dest[ALPHA_I] = 255;
+ index_used_count[dest[INDEXED] = *cachep - 1]++;
+ }
+ }
+ else
+ {
+ /* Now emit the colormap index for this cell */
+ index_used_count[dest[INDEXED] = *cachep - 1]++;
+ }
+
+ src += src_bpp;
+ dest += dest_bpp;
+ }
+ }
+ }
+}
+
+static void
+median_cut_pass2_fixed_dither_gray (QuantizeObj *quantobj,
+ GimpLayer *layer,
+ GeglBuffer *new_buffer)
+{
+ GeglBufferIterator *iter;
+ CFHistogram histogram = quantobj->histogram;
+ ColorFreq *cachep;
+ const Babl *src_format;
+ const Babl *dest_format;
+ GeglRectangle *src_roi;
+ gint src_bpp;
+ gint dest_bpp;
+ gboolean has_alpha;
+ gint pixval1 = 0;
+ gint pixval2 = 0;
+ gint err1;
+ gint err2;
+ Color *color1;
+ Color *color2;
+ guint64 *index_used_count = quantobj->index_used_count;
+ gboolean dither_alpha = quantobj->want_dither_alpha;
+ gint offsetx, offsety;
+
+ gimp_item_get_offset (GIMP_ITEM (layer), &offsetx, &offsety);
+
+ src_format = gimp_drawable_get_format (GIMP_DRAWABLE (layer));
+ dest_format = gegl_buffer_get_format (new_buffer);
+
+ src_bpp = babl_format_get_bytes_per_pixel (src_format);
+ dest_bpp = babl_format_get_bytes_per_pixel (dest_format);
+
+ has_alpha = babl_format_has_alpha (src_format);
+
+ iter = gegl_buffer_iterator_new (gimp_drawable_get_buffer (GIMP_DRAWABLE (layer)),
+ NULL, 0, NULL,
+ GEGL_ACCESS_READ, GEGL_ABYSS_NONE, 2);
+ src_roi = &iter->items[0].roi;
+
+ gegl_buffer_iterator_add (iter, new_buffer,
+ NULL, 0, NULL,
+ GEGL_ACCESS_WRITE, GEGL_ABYSS_NONE);
+
+ while (gegl_buffer_iterator_next (iter))
+ {
+ const guchar *src = iter->items[0].data;
+ guchar *dest = iter->items[1].data;
+ gint row;
+
+ for (row = 0; row < src_roi->height; row++)
+ {
+ gint col;
+
+ for (col = 0; col < src_roi->width; col++)
+ {
+ gint pixel;
+ const gint dmval =
+ DM[(col + offsetx + src_roi->x) & DM_WIDTHMASK]
+ [(row + offsety + src_roi->y) & DM_HEIGHTMASK];
+
+ /* get pixel value and index into the cache */
+ pixel = src[GRAY];
+
+ cachep = &histogram[pixel];
+ /* If we have not seen this color before, find nearest
+ * colormap entry and update the cache
+ */
+ if (*cachep == 0)
+ fill_inverse_cmap_gray (quantobj, histogram, pixel);
+
+ pixval1 = *cachep - 1;
+ color1 = &quantobj->cmap[pixval1];
+
+ if (quantobj->actual_number_of_colors > 2)
+ {
+ const int re = src[GRAY] - (int)color1->red;
+ int RV = src[GRAY] + re;
+
+ do
+ {
+ const gint R = CLAMP0255 (RV);
+
+ cachep = &histogram[R];
+ /* If we have not seen this color before, find
+ * nearest colormap entry and update the cache
+ */
+ if (*cachep == 0)
+ fill_inverse_cmap_gray (quantobj, histogram, R);
+
+ pixval2 = *cachep - 1;
+ RV += re;
+ }
+ while ((pixval1 == pixval2) &&
+ (! (RV>255 || RV<0) ) &&
+ re);
+ }
+ else
+ {
+ /* not enough colors to bother looking for an 'alternative'
+ color (we may fail to do so anyway), so decide that
+ the alternative color is simply the other cmap entry. */
+ pixval2 = (pixval1 + 1) %
+ (quantobj->actual_number_of_colors);
+ }
+
+ /* always deterministically sort pixval1 and pixval2, to
+ avoid artifacts in the dither range due to inverting our
+ relative color viewpoint -- most obvious in 1-bit dither. */
+ if (pixval1 > pixval2)
+ {
+ gint tmpval = pixval1;
+ pixval1 = pixval2;
+ pixval2 = tmpval;
+ color1 = &quantobj->cmap[pixval1];
+ }
+
+ color2 = &quantobj->cmap[pixval2];
+
+ err1 = ABS(color1->red - src[GRAY]);
+ err2 = ABS(color2->red - src[GRAY]);
+ if (err1 || err2)
+ {
+ const int proportion2 = (256 * 255 * err2) / (err1 + err2);
+
+ if ((dmval * 256) > proportion2)
+ {
+ pixval1 = pixval2; /* use color2 instead of color1*/
+ }
+ }
+
+ if (has_alpha)
+ {
+ gboolean transparent = FALSE;
+
+ if (dither_alpha)
+ {
+ if (src[ALPHA_G] < dmval)
+ transparent = TRUE;
+ }
+ else
+ {
+ if (src[ALPHA_G] <= 127)
+ transparent = TRUE;
+ }
+
+ if (transparent)
+ {
+ dest[ALPHA_I] = 0;
+ }
+ else
+ {
+ dest[ALPHA_I] = 255;
+ index_used_count[dest[INDEXED] = pixval1]++;
+ }
+ }
+ else
+ {
+ /* Now emit the colormap index for this cell, barfbarf */
+ index_used_count[dest[INDEXED] = pixval1]++;
+ }
+
+ src += src_bpp;
+ dest += dest_bpp;
+ }
+ }
+ }
+}
+
+static void
+median_cut_pass2_no_dither_rgb (QuantizeObj *quantobj,
+ GimpLayer *layer,
+ GeglBuffer *new_buffer)
+{
+ GeglBufferIterator *iter;
+ CFHistogram histogram = quantobj->histogram;
+ ColorFreq *cachep;
+ const Babl *src_format;
+ const Babl *dest_format;
+ GeglRectangle *src_roi;
+ gint src_bpp;
+ gint dest_bpp;
+ gint has_alpha;
+ gint R, G, B;
+ gint red_pix = RED;
+ gint green_pix = GREEN;
+ gint blue_pix = BLUE;
+ gint alpha_pix = ALPHA;
+ gboolean dither_alpha = quantobj->want_dither_alpha;
+ gint offsetx, offsety;
+ guint64 *index_used_count = quantobj->index_used_count;
+ gint64 total_size = 0;
+ gint64 layer_size;
+ gint count = 0;
+
+ gimp_item_get_offset (GIMP_ITEM (layer), &offsetx, &offsety);
+
+ src_format = gimp_drawable_get_format (GIMP_DRAWABLE (layer));
+ dest_format = gegl_buffer_get_format (new_buffer);
+
+ src_bpp = babl_format_get_bytes_per_pixel (src_format);
+ dest_bpp = babl_format_get_bytes_per_pixel (dest_format);
+
+ has_alpha = babl_format_has_alpha (src_format);
+
+ /* In the case of web/mono palettes, we actually force
+ * grayscale drawables through the rgb pass2 functions
+ */
+ if (gimp_drawable_is_gray (GIMP_DRAWABLE (layer)))
+ {
+ red_pix = green_pix = blue_pix = GRAY;
+ alpha_pix = ALPHA_G;
+ }
+
+ iter = gegl_buffer_iterator_new (gimp_drawable_get_buffer (GIMP_DRAWABLE (layer)),
+ NULL, 0, NULL,
+ GEGL_ACCESS_READ, GEGL_ABYSS_NONE, 2);
+ src_roi = &iter->items[0].roi;
+
+ gegl_buffer_iterator_add (iter, new_buffer,
+ NULL, 0, NULL,
+ GEGL_ACCESS_WRITE, GEGL_ABYSS_NONE);
+
+ layer_size = (gimp_item_get_width (GIMP_ITEM (layer)) *
+ gimp_item_get_height (GIMP_ITEM (layer)));
+
+ while (gegl_buffer_iterator_next (iter))
+ {
+ const guchar *src = iter->items[0].data;
+ guchar *dest = iter->items[1].data;
+ gint row;
+
+ total_size += src_roi->height * src_roi->width;
+
+ for (row = 0; row < src_roi->height; row++)
+ {
+ gint col;
+
+ for (col = 0; col < src_roi->width; col++)
+ {
+ if (has_alpha)
+ {
+ gboolean transparent = FALSE;
+
+ if (dither_alpha)
+ {
+ gint dither_x = (col + offsetx + src_roi->x) & DM_WIDTHMASK;
+ gint dither_y = (row + offsety + src_roi->y) & DM_HEIGHTMASK;
+
+ if ((src[alpha_pix]) < DM[dither_x][dither_y])
+ transparent = TRUE;
+ }
+ else
+ {
+ if (src[alpha_pix] <= 127)
+ transparent = TRUE;
+ }
+
+ if (transparent)
+ {
+ dest[ALPHA_I] = 0;
+ goto next_pixel;
+ }
+ else
+ {
+ dest[ALPHA_I] = 255;
+ }
+ }
+
+ /* get pixel value and index into the cache */
+ rgb_to_lin (src[red_pix], src[green_pix], src[blue_pix],
+ &R, &G, &B);
+
+ cachep = HIST_LIN (histogram, R, G, B);
+ /* If we have not seen this color before, find nearest
+ * colormap entry and update the cache
+ */
+ if (*cachep == 0)
+ fill_inverse_cmap_rgb (quantobj, histogram, R, G, B);
+
+ /* Now emit the colormap index for this cell, barfbarf */
+ index_used_count[dest[INDEXED] = *cachep - 1]++;
+
+ next_pixel:
+
+ src += src_bpp;
+ dest += dest_bpp;
+ }
+ }
+
+ if (quantobj->progress && (count % 16 == 0))
+ gimp_progress_set_value (quantobj->progress,
+ (gdouble) total_size / (gdouble) layer_size);
+ }
+}
+
+static void
+median_cut_pass2_fixed_dither_rgb (QuantizeObj *quantobj,
+ GimpLayer *layer,
+ GeglBuffer *new_buffer)
+{
+ GeglBufferIterator *iter;
+ CFHistogram histogram = quantobj->histogram;
+ ColorFreq *cachep;
+ const Babl *src_format;
+ const Babl *dest_format;
+ GeglRectangle *src_roi;
+ gint src_bpp;
+ gint dest_bpp;
+ gint has_alpha;
+ gint pixval1 = 0;
+ gint pixval2 = 0;
+ Color *color1;
+ Color *color2;
+ gint R, G, B;
+ gint err1;
+ gint err2;
+ gint red_pix = RED;
+ gint green_pix = GREEN;
+ gint blue_pix = BLUE;
+ gint alpha_pix = ALPHA;
+ gboolean dither_alpha = quantobj->want_dither_alpha;
+ gint offsetx, offsety;
+ guint64 *index_used_count = quantobj->index_used_count;
+ gint64 total_size = 0;
+ gint64 layer_size;
+ gint count = 0;
+
+ gimp_item_get_offset (GIMP_ITEM (layer), &offsetx, &offsety);
+
+ src_format = gimp_drawable_get_format (GIMP_DRAWABLE (layer));
+ dest_format = gegl_buffer_get_format (new_buffer);
+
+ src_bpp = babl_format_get_bytes_per_pixel (src_format);
+ dest_bpp = babl_format_get_bytes_per_pixel (dest_format);
+
+ has_alpha = babl_format_has_alpha (src_format);
+
+ /* In the case of web/mono palettes, we actually force
+ * grayscale drawables through the rgb pass2 functions
+ */
+ if (gimp_drawable_is_gray (GIMP_DRAWABLE (layer)))
+ {
+ red_pix = green_pix = blue_pix = GRAY;
+ alpha_pix = ALPHA_G;
+ }
+
+ iter = gegl_buffer_iterator_new (gimp_drawable_get_buffer (GIMP_DRAWABLE (layer)),
+ NULL, 0, NULL,
+ GEGL_ACCESS_READ, GEGL_ABYSS_NONE, 2);
+ src_roi = &iter->items[0].roi;
+
+ gegl_buffer_iterator_add (iter, new_buffer,
+ NULL, 0, NULL,
+ GEGL_ACCESS_WRITE, GEGL_ABYSS_NONE);
+
+ layer_size = (gimp_item_get_width (GIMP_ITEM (layer)) *
+ gimp_item_get_height (GIMP_ITEM (layer)));
+
+ while (gegl_buffer_iterator_next (iter))
+ {
+ const guchar *src = iter->items[0].data;
+ guchar *dest = iter->items[1].data;
+ gint row;
+
+ total_size += src_roi->height * src_roi->width;
+
+ for (row = 0; row < src_roi->height; row++)
+ {
+ gint col;
+
+ for (col = 0; col < src_roi->width; col++)
+ {
+ const int dmval =
+ DM[(col + offsetx + src_roi->x) & DM_WIDTHMASK]
+ [(row + offsety + src_roi->y) & DM_HEIGHTMASK];
+
+ if (has_alpha)
+ {
+ gboolean transparent = FALSE;
+
+ if (dither_alpha)
+ {
+ if (src[alpha_pix] < dmval)
+ transparent = TRUE;
+ }
+ else
+ {
+ if (src[alpha_pix] <= 127)
+ transparent = TRUE;
+ }
+
+ if (transparent)
+ {
+ dest[ALPHA_I] = 0;
+ goto next_pixel;
+ }
+ else
+ {
+ dest[ALPHA_I] = 255;
+ }
+ }
+
+ /* get pixel value and index into the cache */
+ rgb_to_lin (src[red_pix], src[green_pix], src[blue_pix],
+ &R, &G, &B);
+
+ cachep = HIST_LIN (histogram, R, G, B);
+ /* If we have not seen this color before, find nearest
+ * colormap entry and update the cache
+ */
+ if (*cachep == 0)
+ fill_inverse_cmap_rgb (quantobj, histogram, R, G, B);
+
+ /* We now try to find a color which, when mixed in some
+ * fashion with the closest match, yields something
+ * closer to the desired color. We do this by
+ * repeatedly extrapolating the color vector from one to
+ * the other until we find another color cell. Then we
+ * assess the distance of both mixer colors from the
+ * intended color to determine their relative
+ * probabilities of being chosen.
+ */
+ pixval1 = *cachep - 1;
+ color1 = &quantobj->cmap[pixval1];
+
+ if (quantobj->actual_number_of_colors > 2)
+ {
+ const gint re = src[red_pix] - (gint) color1->red;
+ const gint ge = src[green_pix] - (gint) color1->green;
+ const gint be = src[blue_pix] - (gint) color1->blue;
+ gint RV = src[red_pix] + re;
+ gint GV = src[green_pix] + ge;
+ gint BV = src[blue_pix] + be;
+
+ do
+ {
+ rgb_to_lin ((CLAMP0255(RV)),
+ (CLAMP0255(GV)),
+ (CLAMP0255(BV)),
+ &R, &G, &B);
+
+ cachep = HIST_LIN (histogram, R, G, B);
+ /* If we have not seen this color before, find
+ * nearest colormap entry and update the cache
+ */
+ if (*cachep == 0)
+ fill_inverse_cmap_rgb (quantobj, histogram, R, G, B);
+
+ pixval2 = *cachep - 1;
+ RV += re; GV += ge; BV += be;
+ }
+ while ((pixval1 == pixval2) &&
+ (!( (RV>255 || RV<0) || (GV>255 || GV<0) || (BV>255 || BV<0) )) &&
+ (re || ge || be));
+ }
+
+ if (quantobj->actual_number_of_colors <= 2
+ /* || pixval1 == pixval2 */) {
+ /* not enough colors to bother looking for an 'alternative'
+ color (we may fail to do so anyway), so decide that
+ the alternative color is simply the other cmap entry. */
+ pixval2 = (pixval1 + 1) %
+ (quantobj->actual_number_of_colors);
+ }
+
+ /* always deterministically sort pixval1 and pixval2, to
+ avoid artifacts in the dither range due to inverting our
+ relative color viewpoint -- most obvious in 1-bit dither. */
+ if (pixval1 > pixval2)
+ {
+ gint tmpval = pixval1;
+ pixval1 = pixval2;
+ pixval2 = tmpval;
+ color1 = &quantobj->cmap[pixval1];
+ }
+
+ color2 = &quantobj->cmap[pixval2];
+
+ /* now figure out the relative probabilites of choosing
+ either of our candidates. */
+#define DISTP(R1,G1,B1,R2,G2,B2,D) do {D = sqrt( 30*SQR((R1)-(R2)) + \
+ 59*SQR((G1)-(G2)) + \
+ 11*SQR((B1)-(B2)) ); }while(0)
+#define LIN_DISTP(R1,G1,B1,R2,G2,B2,D) do { \
+ int spacer1, spaceg1, spaceb1; \
+ int spacer2, spaceg2, spaceb2; \
+ rgb_to_unshifted_lin (R1,G1,B1, &spacer1, &spaceg1, &spaceb1); \
+ rgb_to_unshifted_lin (R2,G2,B2, &spacer2, &spaceg2, &spaceb2); \
+ D = sqrt(R_SCALE * SQR((spacer1)-(spacer2)) + \
+ G_SCALE * SQR((spaceg1)-(spaceg2)) + \
+ B_SCALE * SQR((spaceb1)-(spaceb2))); \
+ } while(0)
+
+ /* although LIN_DISTP is more correct, DISTP is much faster and
+ barely distinguishable. */
+ DISTP (color1->red, color1->green, color1->blue,
+ src[red_pix], src[green_pix], src[blue_pix],
+ err1);
+ DISTP (color2->red, color2->green, color2->blue,
+ src[red_pix], src[green_pix], src[blue_pix],
+ err2);
+
+ if (err1 || err2)
+ {
+ const int proportion2 = (255 * err2) / (err1 + err2);
+ if (dmval > proportion2)
+ {
+ pixval1 = pixval2; /* use color2 instead of color1*/
+ }
+ }
+
+ /* Now emit the colormap index for this cell, barfbarf */
+ index_used_count[dest[INDEXED] = pixval1]++;
+
+ next_pixel:
+
+ src += src_bpp;
+ dest += dest_bpp;
+ }
+ }
+
+ if (quantobj->progress && (count % 16 == 0))
+ gimp_progress_set_value (quantobj->progress,
+ (gdouble) total_size / (gdouble) layer_size);
+ }
+}
+
+static void
+median_cut_pass2_nodestruct_dither_rgb (QuantizeObj *quantobj,
+ GimpLayer *layer,
+ GeglBuffer *new_buffer)
+{
+ GeglBufferIterator *iter;
+ const Babl *src_format;
+ const Babl *dest_format;
+ GeglRectangle *src_roi;
+ gint src_bpp;
+ gint dest_bpp;
+ gint has_alpha;
+ gboolean dither_alpha = quantobj->want_dither_alpha;
+ gint red_pix = RED;
+ gint green_pix = GREEN;
+ gint blue_pix = BLUE;
+ gint alpha_pix = ALPHA;
+ gint lastindex = 0;
+ gint lastred = -1;
+ gint lastgreen = -1;
+ gint lastblue = -1;
+ gint offsetx, offsety;
+
+ gimp_item_get_offset (GIMP_ITEM (layer), &offsetx, &offsety);
+
+ src_format = gimp_drawable_get_format (GIMP_DRAWABLE (layer));
+ dest_format = gegl_buffer_get_format (new_buffer);
+
+ src_bpp = babl_format_get_bytes_per_pixel (src_format);
+ dest_bpp = babl_format_get_bytes_per_pixel (dest_format);
+
+ has_alpha = babl_format_has_alpha (src_format);
+
+ iter = gegl_buffer_iterator_new (gimp_drawable_get_buffer (GIMP_DRAWABLE (layer)),
+ NULL, 0, NULL,
+ GEGL_ACCESS_READ, GEGL_ABYSS_NONE, 2);
+ src_roi = &iter->items[0].roi;
+
+ gegl_buffer_iterator_add (iter, new_buffer,
+ NULL, 0, NULL,
+ GEGL_ACCESS_WRITE, GEGL_ABYSS_NONE);
+
+ while (gegl_buffer_iterator_next (iter))
+ {
+ const guchar *src = iter->items[0].data;
+ guchar *dest = iter->items[1].data;
+ gint row;
+
+ for (row = 0; row < src_roi->height; row++)
+ {
+ gint col;
+
+ for (col = 0; col < src_roi->width; col++)
+ {
+ gboolean transparent = FALSE;
+
+ if (has_alpha)
+ {
+ if (dither_alpha)
+ {
+ gint dither_x = (col + src_roi->x + offsetx) & DM_WIDTHMASK;
+ gint dither_y = (row + src_roi->y + offsety) & DM_HEIGHTMASK;
+
+ if ((src[alpha_pix]) < DM[dither_x][dither_y])
+ transparent = TRUE;
+ }
+ else
+ {
+ if (src[alpha_pix] < 128)
+ transparent = TRUE;
+ }
+ }
+
+ if (! transparent)
+ {
+ if ((lastred == src[red_pix]) &&
+ (lastgreen == src[green_pix]) &&
+ (lastblue == src[blue_pix]))
+ {
+ /* same pixel color as last time */
+ dest[INDEXED] = lastindex;
+ if (has_alpha)
+ dest[ALPHA_I] = 255;
+ }
+ else
+ {
+ gint i;
+
+ for (i = 0 ;
+ i < quantobj->actual_number_of_colors;
+ i++)
+ {
+ if ((quantobj->cmap[i].green == src[green_pix]) &&
+ (quantobj->cmap[i].red == src[red_pix]) &&
+ (quantobj->cmap[i].blue == src[blue_pix]))
+ {
+ lastred = src[red_pix];
+ lastgreen = src[green_pix];
+ lastblue = src[blue_pix];
+ lastindex = i;
+
+ goto got_color;
+ }
+ }
+ g_error ("Non-existant color was expected to "
+ "be in non-destructive colormap.");
+ got_color:
+ dest[INDEXED] = lastindex;
+ if (has_alpha)
+ dest[ALPHA_I] = 255;
+ }
+ }
+ else
+ { /* have alpha, and transparent */
+ dest[ALPHA_I] = 0;
+ }
+
+ src += src_bpp;
+ dest += dest_bpp;
+ }
+ }
+ }
+}
+
+
+/*
+ * Initialize the error-limiting transfer function (lookup table).
+ * The raw F-S error computation can potentially compute error values of up to
+ * +- MAXJSAMPLE. But we want the maximum correction applied to a pixel to be
+ * much less, otherwise obviously wrong pixels will be created. (Typical
+ * effects include weird fringes at color-area boundaries, isolated bright
+ * pixels in a dark area, etc.) The standard advice for avoiding this problem
+ * is to ensure that the "corners" of the color cube are allocated as output
+ * colors; then repeated errors in the same direction cannot cause cascading
+ * error buildup. However, that only prevents the error from getting
+ * completely out of hand; Aaron Giles reports that error limiting improves
+ * the results even with corner colors allocated.
+ * A simple clamping of the error values to about +- MAXJSAMPLE/8 works pretty
+ * well, but the smoother transfer function used below is even better. Thanks
+ * to Aaron Giles for this idea.
+ */
+
+static gint *
+init_error_limit (const int error_freedom)
+/* Allocate and fill in the error_limiter table */
+{
+ gint *table;
+ gint inp, out;
+
+ /* #define STEPSIZE 16 */
+ /* #define STEPSIZE 200 */
+
+ table = g_new (gint, 255 * 2 + 1);
+ table += 255; /* so we can index -255 ... +255 */
+
+ if (error_freedom == 0)
+ {
+ /* Coarse function, much bleeding. */
+
+ const gint STEPSIZE = 190;
+
+ for (inp = 0; inp < STEPSIZE; inp++)
+ {
+ table[inp] = inp;
+ table[-inp] = -inp;
+ }
+
+ for (; inp <= 255; inp++)
+ {
+ table[inp] = STEPSIZE;
+ table[-inp] = -STEPSIZE;
+ }
+
+ return (table);
+ }
+ else
+ {
+ /* Smooth function, bleeding more constrained */
+
+ const gint STEPSIZE = 24;
+
+ /* Map errors 1:1 up to +- STEPSIZE */
+ out = 0;
+ for (inp = 0; inp < STEPSIZE; inp++, out++)
+ {
+ table[inp] = out;
+ table[-inp] = -out;
+ }
+
+ /* Map errors 1:2 up to +- 3*STEPSIZE */
+ for (; inp < STEPSIZE*3; inp++, out += (inp&1) ? 0 : 1)
+ {
+ table[inp] = out;
+ table[-inp] = -out;
+ }
+
+ /* Clamp the rest to final out value (which is STEPSIZE*2) */
+ for (; inp <= 255; inp++)
+ {
+ table[inp] = out;
+ table[-inp] = -out;
+ }
+
+ return table;
+ }
+}
+
+
+/*
+ * Map some rows of pixels to the output colormapped representation.
+ * Perform floyd-steinberg dithering.
+ */
+
+static void
+median_cut_pass2_fs_dither_gray (QuantizeObj *quantobj,
+ GimpLayer *layer,
+ GeglBuffer *new_buffer)
+{
+ GeglBuffer *src_buffer;
+ CFHistogram histogram = quantobj->histogram;
+ ColorFreq *cachep;
+ Color *color;
+ gint *error_limiter;
+ const gshort *fs_err1, *fs_err2;
+ const gshort *fs_err3, *fs_err4;
+ const guchar *range_limiter;
+ const Babl *src_format;
+ const Babl *dest_format;
+ gint src_bpp;
+ gint dest_bpp;
+ guchar *src_buf, *dest_buf;
+ gint *next_row, *prev_row;
+ gint *nr, *pr;
+ gint *tmp;
+ gint pixel;
+ gint pixele;
+ gint row, col;
+ gint index;
+ gint step_dest, step_src;
+ gint odd_row;
+ gboolean has_alpha;
+ gint offsetx, offsety;
+ gboolean dither_alpha = quantobj->want_dither_alpha;
+ gint width, height;
+ guint64 *index_used_count = quantobj->index_used_count;
+
+ src_buffer = gimp_drawable_get_buffer (GIMP_DRAWABLE (layer));
+
+ gimp_item_get_offset (GIMP_ITEM (layer), &offsetx, &offsety);
+
+ src_format = gimp_drawable_get_format (GIMP_DRAWABLE (layer));
+ dest_format = gegl_buffer_get_format (new_buffer);
+
+ src_bpp = babl_format_get_bytes_per_pixel (src_format);
+ dest_bpp = babl_format_get_bytes_per_pixel (dest_format);
+
+ has_alpha = babl_format_has_alpha (src_format);
+
+ width = gimp_item_get_width (GIMP_ITEM (layer));
+ height = gimp_item_get_height (GIMP_ITEM (layer));
+
+ error_limiter = init_error_limit (quantobj->error_freedom);
+ range_limiter = range_array + 256;
+
+ src_buf = g_malloc (width * src_bpp);
+ dest_buf = g_malloc (width * dest_bpp);
+
+ next_row = g_new (gint, width + 2);
+ prev_row = g_new0 (gint, width + 2);
+
+ fs_err1 = floyd_steinberg_error1 + 511;
+ fs_err2 = floyd_steinberg_error2 + 511;
+ fs_err3 = floyd_steinberg_error3 + 511;
+ fs_err4 = floyd_steinberg_error4 + 511;
+
+ odd_row = 0;
+
+ for (row = 0; row < height; row++)
+ {
+ const guchar *src;
+ guchar *dest;
+
+ gegl_buffer_get (src_buffer, GEGL_RECTANGLE (0, row, width, 1),
+ 1.0, NULL, src_buf,
+ GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
+
+ src = src_buf;
+ dest = dest_buf;
+
+ nr = next_row;
+ pr = prev_row + 1;
+
+ if (odd_row)
+ {
+ step_dest = -dest_bpp;
+ step_src = -src_bpp;
+
+ src += (width * src_bpp) - src_bpp;
+ dest += (width * dest_bpp) - dest_bpp;
+
+ nr += width + 1;
+ pr += width;
+
+ *(nr - 1) = 0;
+ }
+ else
+ {
+ step_dest = dest_bpp;
+ step_src = src_bpp;
+
+ *(nr + 1) = 0;
+ }
+
+ *nr = 0;
+
+ for (col = 0; col < width; col++)
+ {
+ pixel = range_limiter[src[GRAY] + error_limiter[*pr]];
+
+ cachep = &histogram[pixel];
+ /* If we have not seen this color before, find nearest
+ * colormap entry and update the cache
+ */
+ if (*cachep == 0)
+ fill_inverse_cmap_gray (quantobj, histogram, pixel);
+
+ if (has_alpha)
+ {
+ gboolean transparent = FALSE;
+
+ if (odd_row)
+ {
+ if (dither_alpha)
+ {
+ gint dither_x = ((width-col)+offsetx-1) & DM_WIDTHMASK;
+ gint dither_y = (row+offsety) & DM_HEIGHTMASK;
+
+ if ((src[ALPHA_G]) < DM[dither_x][dither_y])
+ transparent = TRUE;
+ }
+ else
+ {
+ if (src[ALPHA_G] <= 127)
+ transparent = TRUE;
+ }
+
+ if (transparent)
+ {
+ dest[ALPHA_I] = 0;
+ pr--;
+ nr--;
+ *(nr - 1) = 0;
+ goto next_pixel;
+ }
+ else
+ {
+ dest[ALPHA_I] = 255;
+ }
+ }
+ else
+ {
+ if (dither_alpha)
+ {
+ gint dither_x = (col + offsetx) & DM_WIDTHMASK;
+ gint dither_y = (row + offsety) & DM_HEIGHTMASK;
+
+ if ((src[ALPHA_G]) < DM[dither_x][dither_y])
+ transparent = TRUE;
+ }
+ else
+ {
+ if (src[ALPHA_G] <= 127)
+ transparent = TRUE;
+ }
+
+ if (transparent)
+ {
+ dest[ALPHA_I] = 0;
+ pr++;
+ nr++;
+ *(nr + 1) = 0;
+ goto next_pixel;
+ }
+ else
+ {
+ dest[ALPHA_I] = 255;
+ }
+ }
+ }
+
+ index = *cachep - 1;
+ index_used_count[dest[INDEXED] = index]++;
+
+ color = &quantobj->cmap[index];
+ pixele = pixel - color->red;
+
+ if (odd_row)
+ {
+ *(--pr) += fs_err1[pixele];
+ *nr-- += fs_err2[pixele];
+ *nr += fs_err3[pixele];
+ *(nr-1) = fs_err4[pixele];
+ }
+ else
+ {
+ *(++pr) += fs_err1[pixele];
+ *nr++ += fs_err2[pixele];
+ *nr += fs_err3[pixele];
+ *(nr+1) = fs_err4[pixele];
+ }
+
+ next_pixel:
+
+ dest += step_dest;
+ src += step_src;
+ }
+
+ tmp = next_row;
+ next_row = prev_row;
+ prev_row = tmp;
+
+ odd_row = !odd_row;
+
+ gegl_buffer_set (new_buffer, GEGL_RECTANGLE (0, row, width, 1),
+ 0, NULL, dest_buf,
+ GEGL_AUTO_ROWSTRIDE);
+ }
+
+ g_free (error_limiter - 255); /* good lord. */
+ g_free (next_row);
+ g_free (prev_row);
+ g_free (src_buf);
+ g_free (dest_buf);
+}
+
+static void
+median_cut_pass2_rgb_init (QuantizeObj *quantobj)
+{
+ int i;
+
+ zero_histogram_rgb (quantobj->histogram);
+
+ /* Mark all indices as currently unused */
+ memset (quantobj->index_used_count, 0, 256 * sizeof (guint64));
+
+ /* Make a version of our discovered colormap in linear space */
+ for (i = 0; i < quantobj->actual_number_of_colors; i++)
+ {
+ rgb_to_unshifted_lin (quantobj->cmap[i].red,
+ quantobj->cmap[i].green,
+ quantobj->cmap[i].blue,
+ &quantobj->clin[i].red,
+ &quantobj->clin[i].green,
+ &quantobj->clin[i].blue);
+ }
+}
+
+static void
+median_cut_pass2_gray_init (QuantizeObj *quantobj)
+{
+ zero_histogram_gray (quantobj->histogram);
+
+ /* Mark all indices as currently unused */
+ memset (quantobj->index_used_count, 0, 256 * sizeof (guint64));
+}
+
+static void
+median_cut_pass2_fs_dither_rgb (QuantizeObj *quantobj,
+ GimpLayer *layer,
+ GeglBuffer *new_buffer)
+{
+ GeglBuffer *src_buffer;
+ CFHistogram histogram = quantobj->histogram;
+ ColorFreq *cachep;
+ Color *color;
+ gint *error_limiter;
+ const gshort *fs_err1, *fs_err2;
+ const gshort *fs_err3, *fs_err4;
+ const guchar *range_limiter;
+ const Babl *src_format;
+ const Babl *dest_format;
+ gint src_bpp;
+ gint dest_bpp;
+ guchar *src_buf, *dest_buf;
+ gint *red_n_row, *red_p_row;
+ gint *grn_n_row, *grn_p_row;
+ gint *blu_n_row, *blu_p_row;
+ gint *rnr, *rpr;
+ gint *gnr, *gpr;
+ gint *bnr, *bpr;
+ gint *tmp;
+ gint re, ge, be;
+ gint row, col;
+ gint index;
+ gint step_dest, step_src;
+ gint odd_row;
+ gboolean has_alpha;
+ gint width, height;
+ gint red_pix = RED;
+ gint green_pix = GREEN;
+ gint blue_pix = BLUE;
+ gint alpha_pix = ALPHA;
+ gint offsetx, offsety;
+ gboolean dither_alpha = quantobj->want_dither_alpha;
+ guint64 *index_used_count = quantobj->index_used_count;
+ gint global_rmax = 0, global_rmin = G_MAXINT;
+ gint global_gmax = 0, global_gmin = G_MAXINT;
+ gint global_bmax = 0, global_bmin = G_MAXINT;
+
+ src_buffer = gimp_drawable_get_buffer (GIMP_DRAWABLE (layer));
+
+ gimp_item_get_offset (GIMP_ITEM (layer), &offsetx, &offsety);
+
+ /* In the case of web/mono palettes, we actually force
+ * grayscale drawables through the rgb pass2 functions
+ */
+ if (gimp_drawable_is_gray (GIMP_DRAWABLE (layer)))
+ red_pix = green_pix = blue_pix = GRAY;
+
+ src_format = gimp_drawable_get_format (GIMP_DRAWABLE (layer));
+ dest_format = gegl_buffer_get_format (new_buffer);
+
+ src_bpp = babl_format_get_bytes_per_pixel (src_format);
+ dest_bpp = babl_format_get_bytes_per_pixel (dest_format);
+
+ has_alpha = babl_format_has_alpha (src_format);
+
+ width = gimp_item_get_width (GIMP_ITEM (layer));
+ height = gimp_item_get_height (GIMP_ITEM (layer));
+
+ error_limiter = init_error_limit (quantobj->error_freedom);
+ range_limiter = range_array + 256;
+
+ /* find the bounding box of the palette colors --
+ we use this for hard-clamping our error-corrected
+ values so that we can't continuously accelerate outside
+ of our attainable gamut, which looks icky. */
+ for (index = 0; index < quantobj->actual_number_of_colors; index++)
+ {
+ global_rmax = MAX(global_rmax, quantobj->clin[index].red);
+ global_rmin = MIN(global_rmin, quantobj->clin[index].red);
+ global_gmax = MAX(global_gmax, quantobj->clin[index].green);
+ global_gmin = MIN(global_gmin, quantobj->clin[index].green);
+ global_bmax = MAX(global_bmax, quantobj->clin[index].blue);
+ global_bmin = MIN(global_bmin, quantobj->clin[index].blue);
+ }
+
+ src_buf = g_malloc (width * src_bpp);
+ dest_buf = g_malloc (width * dest_bpp);
+
+ red_n_row = g_new (gint, width + 2);
+ red_p_row = g_new0 (gint, width + 2);
+ grn_n_row = g_new (gint, width + 2);
+ grn_p_row = g_new0 (gint, width + 2);
+ blu_n_row = g_new (gint, width + 2);
+ blu_p_row = g_new0 (gint, width + 2);
+
+ fs_err1 = floyd_steinberg_error1 + 511;
+ fs_err2 = floyd_steinberg_error2 + 511;
+ fs_err3 = floyd_steinberg_error3 + 511;
+ fs_err4 = floyd_steinberg_error4 + 511;
+
+ odd_row = 0;
+
+ for (row = 0; row < height; row++)
+ {
+ const guchar *src;
+ guchar *dest;
+
+ gegl_buffer_get (src_buffer, GEGL_RECTANGLE (0, row, width, 1),
+ 1.0, NULL, src_buf,
+ GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
+
+ src = src_buf;
+ dest = dest_buf;
+
+ rnr = red_n_row;
+ gnr = grn_n_row;
+ bnr = blu_n_row;
+ rpr = red_p_row + 1;
+ gpr = grn_p_row + 1;
+ bpr = blu_p_row + 1;
+
+ if (odd_row)
+ {
+ step_dest = -dest_bpp;
+ step_src = -src_bpp;
+
+ src += (width * src_bpp) - src_bpp;
+ dest += (width * dest_bpp) - dest_bpp;
+
+ rnr += width + 1;
+ gnr += width + 1;
+ bnr += width + 1;
+ rpr += width;
+ gpr += width;
+ bpr += width;
+
+ *(rnr - 1) = *(gnr - 1) = *(bnr - 1) = 0;
+ }
+ else
+ {
+ step_dest = dest_bpp;
+ step_src = src_bpp;
+
+ *(rnr + 1) = *(gnr + 1) = *(bnr + 1) = 0;
+ }
+
+ *rnr = *gnr = *bnr = 0;
+
+ for (col = 0; col < width; col++)
+ {
+ if (has_alpha)
+ {
+ gboolean transparent = FALSE;
+
+ if (odd_row)
+ {
+ if (dither_alpha)
+ {
+ gint dither_x = ((width-col)+offsetx-1) & DM_WIDTHMASK;
+ gint dither_y = (row+offsety) & DM_HEIGHTMASK;
+
+ if ((src[alpha_pix]) < DM[dither_x][dither_y])
+ transparent = TRUE;
+ }
+ else
+ {
+ if (src[alpha_pix] <= 127)
+ transparent = TRUE;
+ }
+
+ if (transparent)
+ {
+ dest[ALPHA_I] = 0;
+ rpr--; gpr--; bpr--;
+ rnr--; gnr--; bnr--;
+ *(rnr - 1) = *(gnr - 1) = *(bnr - 1) = 0;
+ goto next_pixel;
+ }
+ else
+ {
+ dest[ALPHA_I] = 255;
+ }
+ }
+ else
+ {
+ if (dither_alpha)
+ {
+ gint dither_x = (col + offsetx) & DM_WIDTHMASK;
+ gint dither_y = (row + offsety) & DM_HEIGHTMASK;
+
+ if ((src[alpha_pix]) < DM[dither_x][dither_y])
+ transparent = TRUE;
+ }
+ else
+ {
+ if (src[alpha_pix] <= 127)
+ transparent = TRUE;
+ }
+
+ if (transparent)
+ {
+ dest[ALPHA_I] = 0;
+ rpr++; gpr++; bpr++;
+ rnr++; gnr++; bnr++;
+ *(rnr + 1) = *(gnr + 1) = *(bnr + 1) = 0;
+ goto next_pixel;
+ }
+ else
+ {
+ dest[ALPHA_I] = 255;
+ }
+ }
+ }
+
+#if 0
+ /* hmm. */
+
+ r = range_limiter[src[red_pix] + error_limiter[*rpr]];
+ g = range_limiter[src[green_pix] + error_limiter[*gpr]];
+ b = range_limiter[src[blue_pix] + error_limiter[*bpr]];
+
+ re = r >> R_SHIFT;
+ ge = g >> G_SHIFT;
+ be = b >> B_SHIFT;
+
+ rgb_to_lin (r, g, b, &re, &ge, &be);
+#endif
+ rgb_to_unshifted_lin (src[red_pix], src[green_pix], src[blue_pix],
+ &re, &ge, &be);
+
+ /*
+ re = CLAMP(re, global_rmin, global_rmax);
+ ge = CLAMP(ge, global_gmin, global_gmax);
+ be = CLAMP(be, global_bmin, global_bmax);*/
+
+ re = range_limiter[re + error_limiter[*rpr]];
+ ge = range_limiter[ge + error_limiter[*gpr]];
+ be = range_limiter[be + error_limiter[*bpr]];
+
+ cachep = HIST_LIN (histogram,
+ RSDF (re),
+ GSDF (ge),
+ BSDF (be));
+ /* If we have not seen this color before, find nearest
+ * colormap entry and update the cache
+ */
+ if (*cachep == 0)
+ fill_inverse_cmap_rgb (quantobj, histogram,
+ RSDF (re),
+ GSDF (ge),
+ BSDF (be));
+
+ index = *cachep - 1;
+ index_used_count[index]++;
+ dest[INDEXED] = index;
+
+ /*if (re > global_rmax)
+ re = (re + 3*global_rmax) / 4;
+ else if (re < global_rmin)
+ re = (re + 3*global_rmin) / 4;*/
+
+ /* We constrain chroma error extra-hard so that it
+ doesn't run away and steal the thunder from the
+ lightness error where all the detail usually is. */
+ if (ge > global_gmax)
+ ge = (ge + 3*global_gmax) / 4;
+ else if (ge < global_gmin)
+ ge = (ge + 3*global_gmin) / 4;
+ if (be > global_bmax)
+ be = (be + 3*global_bmax) / 4;
+ else if (be < global_bmin)
+ be = (be + 3*global_bmin) / 4;
+
+ color = &quantobj->clin[index];
+
+#if 0
+ if ((re > 0 && re < 255) /* HMM &&
+ ge >= 0 && ge <= 255 &&
+ be >= 0 && be <= 255*/)
+ {
+ ge = ge - color->green;
+ be = be - color->blue;
+ re = re - color->red;
+ }
+ else
+ {
+ /* color pretty much undefined now; nullify error. */
+ re = ge = be = 0;
+ }
+#endif
+
+ if (re <= 0 || re >= 255)
+ re = ge = be = 0;
+ else
+ {
+ re = re - color->red;
+ ge = ge - color->green;
+ be = be - color->blue;
+ }
+
+ if (odd_row)
+ {
+ *(--rpr) += fs_err1[re];
+ *(--gpr) += fs_err1[ge];
+ *(--bpr) += fs_err1[be];
+
+ *rnr-- += fs_err2[re];
+ *gnr-- += fs_err2[ge];
+ *bnr-- += fs_err2[be];
+
+ *rnr += fs_err3[re];
+ *gnr += fs_err3[ge];
+ *bnr += fs_err3[be];
+
+ *(rnr-1) = fs_err4[re];
+ *(gnr-1) = fs_err4[ge];
+ *(bnr-1) = fs_err4[be];
+ }
+ else
+ {
+ *(++rpr) += fs_err1[re];
+ *(++gpr) += fs_err1[ge];
+ *(++bpr) += fs_err1[be];
+
+ *rnr++ += fs_err2[re];
+ *gnr++ += fs_err2[ge];
+ *bnr++ += fs_err2[be];
+
+ *rnr += fs_err3[re];
+ *gnr += fs_err3[ge];
+ *bnr += fs_err3[be];
+
+ *(rnr+1) = fs_err4[re];
+ *(gnr+1) = fs_err4[ge];
+ *(bnr+1) = fs_err4[be];
+ }
+
+ next_pixel:
+
+ dest += step_dest;
+ src += step_src;
+ }
+
+ tmp = red_n_row;
+ red_n_row = red_p_row;
+ red_p_row = tmp;
+
+ tmp = grn_n_row;
+ grn_n_row = grn_p_row;
+ grn_p_row = tmp;
+
+ tmp = blu_n_row;
+ blu_n_row = blu_p_row;
+ blu_p_row = tmp;
+
+ odd_row = !odd_row;
+
+ gegl_buffer_set (new_buffer, GEGL_RECTANGLE (0, row, width, 1),
+ 0, NULL, dest_buf,
+ GEGL_AUTO_ROWSTRIDE);
+
+ if (quantobj->progress && (row % 16 == 0))
+ gimp_progress_set_value (quantobj->progress,
+ (gdouble) row / (gdouble) height);
+ }
+
+ g_free (error_limiter - 255);
+ g_free (red_n_row);
+ g_free (red_p_row);
+ g_free (grn_n_row);
+ g_free (grn_p_row);
+ g_free (blu_n_row);
+ g_free (blu_p_row);
+ g_free (src_buf);
+ g_free (dest_buf);
+}
+
+
+static void
+delete_median_cut (QuantizeObj *quantobj)
+{
+ g_free (quantobj->histogram);
+ g_free (quantobj);
+}
+
+
+void
+gimp_image_convert_indexed_set_dither_matrix (const guchar *matrix,
+ gint width,
+ gint height)
+{
+ gint x;
+ gint y;
+
+ /* if matrix is invalid, restore the default matrix */
+ if (matrix == NULL || width == 0 || height == 0)
+ {
+ matrix = (const guchar *) DM_ORIGINAL;
+ width = DM_WIDTH;
+ height = DM_HEIGHT;
+ }
+
+ g_return_if_fail ((DM_WIDTH % width) == 0);
+ g_return_if_fail ((DM_HEIGHT % height) == 0);
+
+ for (y = 0; y < DM_HEIGHT; y++)
+ {
+ for (x = 0; x < DM_WIDTH; x++)
+ {
+ DM[x][y] = matrix[((x % width) * height) + (y % height)];
+ }
+ }
+}
+
+
+/**************************************************************/
+static QuantizeObj *
+initialize_median_cut (GimpImageBaseType type,
+ gint num_colors,
+ GimpConvertDitherType dither_type,
+ GimpConvertPaletteType palette_type,
+ GimpPalette *custom_palette,
+ gboolean want_dither_alpha,
+ GimpProgress *progress)
+{
+ QuantizeObj *quantobj;
+
+ /* Initialize the data structures */
+ quantobj = g_new (QuantizeObj, 1);
+
+ if (type == GIMP_GRAY && palette_type == GIMP_CONVERT_PALETTE_GENERATE)
+ quantobj->histogram = g_new (ColorFreq, 256);
+ else
+ quantobj->histogram = g_new (ColorFreq,
+ HIST_R_ELEMS * HIST_G_ELEMS * HIST_B_ELEMS);
+
+ quantobj->custom_palette = custom_palette;
+ quantobj->desired_number_of_colors = num_colors;
+ quantobj->want_dither_alpha = want_dither_alpha;
+ quantobj->progress = progress;
+
+ switch (type)
+ {
+ case GIMP_GRAY:
+ switch (palette_type)
+ {
+ case GIMP_CONVERT_PALETTE_GENERATE:
+ quantobj->first_pass = median_cut_pass1_gray;
+ break;
+ case GIMP_CONVERT_PALETTE_WEB:
+ quantobj->first_pass = webpal_pass1;
+ break;
+ case GIMP_CONVERT_PALETTE_CUSTOM:
+ quantobj->first_pass = custompal_pass1;
+ needs_quantize = TRUE;
+ break;
+ case GIMP_CONVERT_PALETTE_MONO:
+ default:
+ quantobj->first_pass = monopal_pass1;
+ }
+
+ if (palette_type == GIMP_CONVERT_PALETTE_WEB ||
+ palette_type == GIMP_CONVERT_PALETTE_CUSTOM)
+ {
+ switch (dither_type)
+ {
+ case GIMP_CONVERT_DITHER_NODESTRUCT:
+ default:
+ g_warning("Uh-oh, bad dither type, W1");
+ case GIMP_CONVERT_DITHER_NONE:
+ quantobj->second_pass_init = median_cut_pass2_rgb_init;
+ quantobj->second_pass = median_cut_pass2_no_dither_rgb;
+ break;
+ case GIMP_CONVERT_DITHER_FS:
+ quantobj->error_freedom = 0;
+ quantobj->second_pass_init = median_cut_pass2_rgb_init;
+ quantobj->second_pass = median_cut_pass2_fs_dither_rgb;
+ break;
+ case GIMP_CONVERT_DITHER_FS_LOWBLEED:
+ quantobj->error_freedom = 1;
+ quantobj->second_pass_init = median_cut_pass2_rgb_init;
+ quantobj->second_pass = median_cut_pass2_fs_dither_rgb;
+ break;
+ case GIMP_CONVERT_DITHER_FIXED:
+ quantobj->second_pass_init = median_cut_pass2_rgb_init;
+ quantobj->second_pass = median_cut_pass2_fixed_dither_rgb;
+ break;
+ }
+ }
+ else
+ {
+ switch (dither_type)
+ {
+ case GIMP_CONVERT_DITHER_NODESTRUCT:
+ default:
+ g_warning("Uh-oh, bad dither type, W2");
+ case GIMP_CONVERT_DITHER_NONE:
+ quantobj->second_pass_init = median_cut_pass2_gray_init;
+ quantobj->second_pass = median_cut_pass2_no_dither_gray;
+ break;
+ case GIMP_CONVERT_DITHER_FS:
+ quantobj->error_freedom = 0;
+ quantobj->second_pass_init = median_cut_pass2_gray_init;
+ quantobj->second_pass = median_cut_pass2_fs_dither_gray;
+ break;
+ case GIMP_CONVERT_DITHER_FS_LOWBLEED:
+ quantobj->error_freedom = 1;
+ quantobj->second_pass_init = median_cut_pass2_gray_init;
+ quantobj->second_pass = median_cut_pass2_fs_dither_gray;
+ break;
+ case GIMP_CONVERT_DITHER_FIXED:
+ quantobj->second_pass_init = median_cut_pass2_gray_init;
+ quantobj->second_pass = median_cut_pass2_fixed_dither_gray;
+ break;
+ }
+ }
+ break;
+
+ case GIMP_RGB:
+ switch (palette_type)
+ {
+ case GIMP_CONVERT_PALETTE_GENERATE:
+ quantobj->first_pass = median_cut_pass1_rgb;
+ break;
+ case GIMP_CONVERT_PALETTE_WEB:
+ quantobj->first_pass = webpal_pass1;
+ needs_quantize = TRUE;
+ break;
+ case GIMP_CONVERT_PALETTE_CUSTOM:
+ quantobj->first_pass = custompal_pass1;
+ needs_quantize = TRUE;
+ break;
+ case GIMP_CONVERT_PALETTE_MONO:
+ default:
+ quantobj->first_pass = monopal_pass1;
+ }
+
+ switch (dither_type)
+ {
+ case GIMP_CONVERT_DITHER_NONE:
+ quantobj->second_pass_init = median_cut_pass2_rgb_init;
+ quantobj->second_pass = median_cut_pass2_no_dither_rgb;
+ break;
+ case GIMP_CONVERT_DITHER_FS:
+ quantobj->error_freedom = 0;
+ quantobj->second_pass_init = median_cut_pass2_rgb_init;
+ quantobj->second_pass = median_cut_pass2_fs_dither_rgb;
+ break;
+ case GIMP_CONVERT_DITHER_FS_LOWBLEED:
+ quantobj->error_freedom = 1;
+ quantobj->second_pass_init = median_cut_pass2_rgb_init;
+ quantobj->second_pass = median_cut_pass2_fs_dither_rgb;
+ break;
+ case GIMP_CONVERT_DITHER_NODESTRUCT:
+ quantobj->second_pass_init = NULL;
+ quantobj->second_pass = median_cut_pass2_nodestruct_dither_rgb;
+ break;
+ case GIMP_CONVERT_DITHER_FIXED:
+ quantobj->second_pass_init = median_cut_pass2_rgb_init;
+ quantobj->second_pass = median_cut_pass2_fixed_dither_rgb;
+ break;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ quantobj->delete_func = delete_median_cut;
+
+ return quantobj;
+}
diff --git a/app/core/gimpimage-convert-indexed.h b/app/core/gimpimage-convert-indexed.h
new file mode 100644
index 0000000..ba51f02
--- /dev/null
+++ b/app/core/gimpimage-convert-indexed.h
@@ -0,0 +1,41 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_IMAGE_CONVERT_INDEXED_H__
+#define __GIMP_IMAGE_CONVERT_INDEXED_H__
+
+
+#define MAXNUMCOLORS 256
+
+
+gboolean gimp_image_convert_indexed (GimpImage *image,
+ GimpConvertPaletteType palette_type,
+ gint max_colors,
+ gboolean remove_duplicates,
+ GimpConvertDitherType dither_type,
+ gboolean dither_alpha,
+ gboolean dither_text_layers,
+ GimpPalette *custom_palette,
+ GimpProgress *progress,
+ GError **error);
+
+void gimp_image_convert_indexed_set_dither_matrix (const guchar *matrix,
+ gint width,
+ gint height);
+
+
+#endif /* __GIMP_IMAGE_CONVERT_INDEXED_H__ */
diff --git a/app/core/gimpimage-convert-precision.c b/app/core/gimpimage-convert-precision.c
new file mode 100644
index 0000000..71a613c
--- /dev/null
+++ b/app/core/gimpimage-convert-precision.c
@@ -0,0 +1,304 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpimage-convert-precision.c
+ * Copyright (C) 2012 Michael Natterer <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <cairo.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpcolor/gimpcolor.h"
+
+#include "core-types.h"
+
+#include "gegl/gimp-babl.h"
+#include "gegl/gimp-gegl-loops.h"
+
+#include "gimpchannel.h"
+#include "gimpdrawable.h"
+#include "gimpdrawable-operation.h"
+#include "gimpimage.h"
+#include "gimpimage-color-profile.h"
+#include "gimpimage-convert-precision.h"
+#include "gimpimage-undo.h"
+#include "gimpimage-undo-push.h"
+#include "gimpobjectqueue.h"
+#include "gimpprogress.h"
+
+#include "text/gimptextlayer.h"
+
+#include "gimp-intl.h"
+
+
+void
+gimp_image_convert_precision (GimpImage *image,
+ GimpPrecision precision,
+ GeglDitherMethod layer_dither_type,
+ GeglDitherMethod text_layer_dither_type,
+ GeglDitherMethod mask_dither_type,
+ GimpProgress *progress)
+{
+ GimpColorProfile *old_profile;
+ GimpColorProfile *new_profile = NULL;
+ const Babl *old_format;
+ const Babl *new_format;
+ GimpObjectQueue *queue;
+ GimpProgress *sub_progress;
+ GList *layers;
+ GimpDrawable *drawable;
+ const gchar *undo_desc = NULL;
+
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+ g_return_if_fail (precision != gimp_image_get_precision (image));
+ g_return_if_fail (gimp_babl_is_valid (gimp_image_get_base_type (image),
+ precision));
+ g_return_if_fail (progress == NULL || GIMP_IS_PROGRESS (progress));
+
+ switch (precision)
+ {
+ case GIMP_PRECISION_U8_LINEAR:
+ undo_desc = C_("undo-type", "Convert Image to 8 bit linear integer");
+ break;
+ case GIMP_PRECISION_U8_GAMMA:
+ undo_desc = C_("undo-type", "Convert Image to 8 bit gamma integer");
+ break;
+ case GIMP_PRECISION_U16_LINEAR:
+ undo_desc = C_("undo-type", "Convert Image to 16 bit linear integer");
+ break;
+ case GIMP_PRECISION_U16_GAMMA:
+ undo_desc = C_("undo-type", "Convert Image to 16 bit gamma integer");
+ break;
+ case GIMP_PRECISION_U32_LINEAR:
+ undo_desc = C_("undo-type", "Convert Image to 32 bit linear integer");
+ break;
+ case GIMP_PRECISION_U32_GAMMA:
+ undo_desc = C_("undo-type", "Convert Image to 32 bit gamma integer");
+ break;
+ case GIMP_PRECISION_HALF_LINEAR:
+ undo_desc = C_("undo-type", "Convert Image to 16 bit linear floating point");
+ break;
+ case GIMP_PRECISION_HALF_GAMMA:
+ undo_desc = C_("undo-type", "Convert Image to 16 bit gamma floating point");
+ break;
+ case GIMP_PRECISION_FLOAT_LINEAR:
+ undo_desc = C_("undo-type", "Convert Image to 32 bit linear floating point");
+ break;
+ case GIMP_PRECISION_FLOAT_GAMMA:
+ undo_desc = C_("undo-type", "Convert Image to 32 bit gamma floating point");
+ break;
+ case GIMP_PRECISION_DOUBLE_LINEAR:
+ undo_desc = C_("undo-type", "Convert Image to 64 bit linear floating point");
+ break;
+ case GIMP_PRECISION_DOUBLE_GAMMA:
+ undo_desc = C_("undo-type", "Convert Image to 64 bit gamma floating point");
+ break;
+ }
+
+ if (progress)
+ gimp_progress_start (progress, FALSE, "%s", undo_desc);
+
+ queue = gimp_object_queue_new (progress);
+ sub_progress = GIMP_PROGRESS (queue);
+
+ layers = gimp_image_get_layer_list (image);
+ gimp_object_queue_push_list (queue, layers);
+ g_list_free (layers);
+
+ gimp_object_queue_push (queue, gimp_image_get_mask (image));
+ gimp_object_queue_push_container (queue, gimp_image_get_channels (image));
+
+ g_object_freeze_notify (G_OBJECT (image));
+
+ gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_IMAGE_CONVERT,
+ undo_desc);
+
+ /* Push the image precision to the stack */
+ gimp_image_undo_push_image_precision (image, NULL);
+
+ old_profile = gimp_image_get_color_profile (image);
+ old_format = gimp_image_get_layer_format (image, FALSE);
+
+ gimp_image_set_converting (image, TRUE);
+
+ /* Set the new precision */
+ g_object_set (image, "precision", precision, NULL);
+
+ new_format = gimp_image_get_layer_format (image, FALSE);
+
+ if (old_profile)
+ {
+ if (gimp_babl_format_get_linear (old_format) !=
+ gimp_babl_format_get_linear (new_format))
+ {
+ /* when converting between linear and gamma, we create a new
+ * profile using the original profile's chromacities and
+ * whitepoint, but a linear/sRGB-gamma TRC.
+ */
+
+ if (gimp_babl_format_get_linear (new_format))
+ {
+ new_profile =
+ gimp_color_profile_new_linear_from_color_profile (old_profile);
+ }
+ else
+ {
+ new_profile =
+ gimp_color_profile_new_srgb_trc_from_color_profile (old_profile);
+ }
+
+ /* if a new profile cannot be be generated, convert to the
+ * builtin profile, which is better than leaving the user with
+ * broken colors
+ */
+ if (! new_profile)
+ {
+ new_profile = gimp_image_get_builtin_color_profile (image);
+ g_object_ref (new_profile);
+ }
+ }
+
+ if (! new_profile)
+ new_profile = g_object_ref (old_profile);
+ }
+
+ while ((drawable = gimp_object_queue_pop (queue)))
+ {
+ if (drawable == GIMP_DRAWABLE (gimp_image_get_mask (image)))
+ {
+ GeglBuffer *buffer;
+
+ gimp_image_undo_push_mask_precision (image, NULL,
+ GIMP_CHANNEL (drawable));
+
+ buffer = gegl_buffer_new (GEGL_RECTANGLE (0, 0,
+ gimp_image_get_width (image),
+ gimp_image_get_height (image)),
+ gimp_image_get_mask_format (image));
+
+ gimp_gegl_buffer_copy (gimp_drawable_get_buffer (drawable), NULL,
+ GEGL_ABYSS_NONE,
+ buffer, NULL);
+
+ gimp_drawable_set_buffer (drawable, FALSE, NULL, buffer);
+ g_object_unref (buffer);
+
+ gimp_progress_set_value (sub_progress, 1.0);
+ }
+ else
+ {
+ gint dither_type;
+
+ if (gimp_item_is_text_layer (GIMP_ITEM (drawable)))
+ dither_type = text_layer_dither_type;
+ else
+ dither_type = layer_dither_type;
+
+ gimp_drawable_convert_type (drawable, image,
+ gimp_drawable_get_base_type (drawable),
+ precision,
+ gimp_drawable_has_alpha (drawable),
+ new_profile,
+ dither_type,
+ mask_dither_type,
+ TRUE, sub_progress);
+ }
+ }
+
+ if (new_profile)
+ {
+ if (new_profile != old_profile)
+ gimp_image_set_color_profile (image, new_profile, NULL);
+
+ g_object_unref (new_profile);
+ }
+
+ gimp_image_set_converting (image, FALSE);
+
+ gimp_image_undo_group_end (image);
+
+ gimp_image_precision_changed (image);
+ g_object_thaw_notify (G_OBJECT (image));
+
+ g_object_unref (queue);
+
+ if (progress)
+ gimp_progress_end (progress);
+}
+
+void
+gimp_image_convert_dither_u8 (GimpImage *image,
+ GimpProgress *progress)
+{
+ GeglNode *dither;
+
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+ g_return_if_fail (progress == NULL || GIMP_IS_PROGRESS (progress));
+
+ dither = gegl_node_new_child (NULL,
+ "operation", "gegl:noise-rgb",
+ "red", 1.0 / 256.0,
+ "green", 1.0 / 256.0,
+ "blue", 1.0 / 256.0,
+ "linear", FALSE,
+ "gaussian", FALSE,
+ NULL);
+
+ if (dither)
+ {
+ GimpObjectQueue *queue;
+ GimpProgress *sub_progress;
+ GList *layers;
+ GList *list;
+ GimpDrawable *drawable;
+
+ if (progress)
+ gimp_progress_start (progress, FALSE, "%s", _("Dithering"));
+
+ queue = gimp_object_queue_new (progress);
+ sub_progress = GIMP_PROGRESS (queue);
+
+ layers = gimp_image_get_layer_list (image);
+
+ for (list = layers; list; list = g_list_next (list))
+ {
+ if (! gimp_viewable_get_children (list->data) &&
+ ! gimp_item_is_text_layer (list->data))
+ {
+ gimp_object_queue_push (queue, list->data);
+ }
+ }
+
+ g_list_free (layers);
+
+ while ((drawable = gimp_object_queue_pop (queue)))
+ {
+ gimp_drawable_apply_operation (drawable, sub_progress,
+ _("Dithering"),
+ dither);
+ }
+
+ g_object_unref (queue);
+
+ if (progress)
+ gimp_progress_end (progress);
+
+ g_object_unref (dither);
+ }
+}
diff --git a/app/core/gimpimage-convert-precision.h b/app/core/gimpimage-convert-precision.h
new file mode 100644
index 0000000..cd3a010
--- /dev/null
+++ b/app/core/gimpimage-convert-precision.h
@@ -0,0 +1,36 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpimage-convert-precision.h
+ * Copyright (C) 2012 Michael Natterer <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_IMAGE_CONVERT_PRECISION_H__
+#define __GIMP_IMAGE_CONVERT_PRECISION_H__
+
+
+void gimp_image_convert_precision (GimpImage *image,
+ GimpPrecision precision,
+ GeglDitherMethod layer_dither_type,
+ GeglDitherMethod text_layer_dither_type,
+ GeglDitherMethod mask_dither_type,
+ GimpProgress *progress);
+
+void gimp_image_convert_dither_u8 (GimpImage *image,
+ GimpProgress *progress);
+
+
+#endif /* __GIMP_IMAGE_CONVERT_PRECISION_H__ */
diff --git a/app/core/gimpimage-convert-type.c b/app/core/gimpimage-convert-type.c
new file mode 100644
index 0000000..4298a49
--- /dev/null
+++ b/app/core/gimpimage-convert-type.c
@@ -0,0 +1,162 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <cairo.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpcolor/gimpcolor.h"
+
+#include "core-types.h"
+
+#include "gegl/gimp-babl.h"
+
+#include "gimp.h"
+#include "gimpdrawable.h"
+#include "gimpimage.h"
+#include "gimpimage-color-profile.h"
+#include "gimpimage-colormap.h"
+#include "gimpimage-convert-type.h"
+#include "gimpimage-undo.h"
+#include "gimpimage-undo-push.h"
+#include "gimpobjectqueue.h"
+#include "gimpprogress.h"
+
+#include "gimp-intl.h"
+
+
+gboolean
+gimp_image_convert_type (GimpImage *image,
+ GimpImageBaseType new_type,
+ GimpColorProfile *dest_profile,
+ GimpProgress *progress,
+ GError **error)
+{
+ GimpImageBaseType old_type;
+ const Babl *new_layer_format;
+ GimpObjectQueue *queue;
+ GList *all_layers;
+ GimpDrawable *drawable;
+ const gchar *undo_desc = NULL;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);
+ g_return_val_if_fail (new_type != gimp_image_get_base_type (image), FALSE);
+ g_return_val_if_fail (new_type != GIMP_INDEXED, FALSE);
+ g_return_val_if_fail (gimp_babl_is_valid (new_type,
+ gimp_image_get_precision (image)),
+ FALSE);
+ g_return_val_if_fail (dest_profile == NULL || GIMP_IS_COLOR_PROFILE (dest_profile),
+ FALSE);
+ g_return_val_if_fail (progress == NULL || GIMP_IS_PROGRESS (progress), FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ new_layer_format = gimp_babl_format (new_type,
+ gimp_image_get_precision (image),
+ TRUE);
+
+ if (dest_profile &&
+ ! gimp_image_validate_color_profile_by_format (new_layer_format,
+ dest_profile,
+ NULL, error))
+ {
+ return FALSE;
+ }
+
+ switch (new_type)
+ {
+ case GIMP_RGB:
+ undo_desc = C_("undo-type", "Convert Image to RGB");
+ break;
+
+ case GIMP_GRAY:
+ undo_desc = C_("undo-type", "Convert Image to Grayscale");
+ break;
+
+ default:
+ g_return_val_if_reached (FALSE);
+ }
+
+ gimp_set_busy (image->gimp);
+
+ queue = gimp_object_queue_new (progress);
+ progress = GIMP_PROGRESS (queue);
+
+ all_layers = gimp_image_get_layer_list (image);
+ gimp_object_queue_push_list (queue, all_layers);
+ g_list_free (all_layers);
+
+ g_object_freeze_notify (G_OBJECT (image));
+
+ gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_IMAGE_CONVERT,
+ undo_desc);
+
+ /* Push the image type to the stack */
+ gimp_image_undo_push_image_type (image, NULL);
+
+ /* Set the new base type */
+ old_type = gimp_image_get_base_type (image);
+
+ g_object_set (image, "base-type", new_type, NULL);
+
+ /* When converting to/from GRAY, convert to the new type's builtin
+ * profile if none was passed.
+ */
+ if (old_type == GIMP_GRAY ||
+ new_type == GIMP_GRAY)
+ {
+ if (! dest_profile && gimp_image_get_color_profile (image))
+ dest_profile = gimp_image_get_builtin_color_profile (image);
+ }
+
+ while ((drawable = gimp_object_queue_pop (queue)))
+ {
+ gimp_drawable_convert_type (drawable, image,
+ new_type,
+ gimp_drawable_get_precision (drawable),
+ gimp_drawable_has_alpha (drawable),
+ dest_profile,
+ GEGL_DITHER_NONE, GEGL_DITHER_NONE,
+ TRUE, progress);
+ }
+
+ if (old_type == GIMP_INDEXED)
+ gimp_image_unset_colormap (image, TRUE);
+
+ /* When converting to/from GRAY, set the new profile.
+ */
+ if (old_type == GIMP_GRAY ||
+ new_type == GIMP_GRAY)
+ {
+ if (gimp_image_get_color_profile (image))
+ gimp_image_set_color_profile (image, dest_profile, NULL);
+ else
+ gimp_color_managed_profile_changed (GIMP_COLOR_MANAGED (image));
+ }
+
+ gimp_image_undo_group_end (image);
+
+ gimp_image_mode_changed (image);
+ g_object_thaw_notify (G_OBJECT (image));
+
+ g_object_unref (queue);
+
+ gimp_unset_busy (image->gimp);
+
+ return TRUE;
+}
diff --git a/app/core/gimpimage-convert-type.h b/app/core/gimpimage-convert-type.h
new file mode 100644
index 0000000..53c6950
--- /dev/null
+++ b/app/core/gimpimage-convert-type.h
@@ -0,0 +1,29 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_IMAGE_CONVERT_TYPE_H__
+#define __GIMP_IMAGE_CONVERT_TYPE_H__
+
+
+gboolean gimp_image_convert_type (GimpImage *image,
+ GimpImageBaseType new_type,
+ GimpColorProfile *dest_profile,
+ GimpProgress *progress,
+ GError **error);
+
+
+#endif /* __GIMP_IMAGE_CONVERT_TYPE_H__ */
diff --git a/app/core/gimpimage-crop.c b/app/core/gimpimage-crop.c
new file mode 100644
index 0000000..c6814d4
--- /dev/null
+++ b/app/core/gimpimage-crop.c
@@ -0,0 +1,234 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "core-types.h"
+
+#include "gimp.h"
+#include "gimpcontext.h"
+#include "gimpguide.h"
+#include "gimpimage.h"
+#include "gimpimage-crop.h"
+#include "gimpimage-guides.h"
+#include "gimpimage-sample-points.h"
+#include "gimpimage-undo.h"
+#include "gimpimage-undo-push.h"
+#include "gimplayer.h"
+#include "gimpsamplepoint.h"
+
+#include "gimp-intl.h"
+
+
+/* public functions */
+
+void
+gimp_image_crop (GimpImage *image,
+ GimpContext *context,
+ GimpFillType fill_type,
+ gint x,
+ gint y,
+ gint width,
+ gint height,
+ gboolean crop_layers)
+{
+ GList *list;
+ gint previous_width;
+ gint previous_height;
+
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+ g_return_if_fail (GIMP_IS_CONTEXT (context));
+
+ previous_width = gimp_image_get_width (image);
+ previous_height = gimp_image_get_height (image);
+
+ /* Make sure new width and height are non-zero */
+ if (width < 1 || height < 1)
+ return;
+
+ gimp_set_busy (image->gimp);
+
+ g_object_freeze_notify (G_OBJECT (image));
+
+ if (crop_layers)
+ gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_IMAGE_CROP,
+ C_("undo-type", "Crop Image"));
+ else
+ gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_IMAGE_RESIZE,
+ C_("undo-type", "Resize Image"));
+
+ /* Push the image size to the stack */
+ gimp_image_undo_push_image_size (image, NULL,
+ x, y, width, height);
+
+ /* Set the new width and height */
+ g_object_set (image,
+ "width", width,
+ "height", height,
+ NULL);
+
+ /* Resize all channels */
+ for (list = gimp_image_get_channel_iter (image);
+ list;
+ list = g_list_next (list))
+ {
+ GimpItem *item = list->data;
+
+ gimp_item_resize (item, context, GIMP_FILL_TRANSPARENT,
+ width, height, -x, -y);
+ }
+
+ /* Resize all vectors */
+ for (list = gimp_image_get_vectors_iter (image);
+ list;
+ list = g_list_next (list))
+ {
+ GimpItem *item = list->data;
+
+ gimp_item_resize (item, context, GIMP_FILL_TRANSPARENT,
+ width, height, -x, -y);
+ }
+
+ /* Don't forget the selection mask! */
+ gimp_item_resize (GIMP_ITEM (gimp_image_get_mask (image)),
+ context, GIMP_FILL_TRANSPARENT,
+ width, height, -x, -y);
+
+ /* crop all layers */
+ list = gimp_image_get_layer_iter (image);
+
+ while (list)
+ {
+ GimpItem *item = list->data;
+
+ list = g_list_next (list);
+
+ gimp_item_translate (item, -x, -y, TRUE);
+
+ if (crop_layers && ! gimp_item_is_content_locked (item))
+ {
+ gint off_x, off_y;
+ gint lx1, ly1, lx2, ly2;
+
+ gimp_item_get_offset (item, &off_x, &off_y);
+
+ lx1 = CLAMP (off_x, 0, gimp_image_get_width (image));
+ ly1 = CLAMP (off_y, 0, gimp_image_get_height (image));
+ lx2 = CLAMP (gimp_item_get_width (item) + off_x,
+ 0, gimp_image_get_width (image));
+ ly2 = CLAMP (gimp_item_get_height (item) + off_y,
+ 0, gimp_image_get_height (image));
+
+ width = lx2 - lx1;
+ height = ly2 - ly1;
+
+ if (width > 0 && height > 0)
+ {
+ gimp_item_resize (item, context, fill_type,
+ width, height,
+ -(lx1 - off_x),
+ -(ly1 - off_y));
+ }
+ else
+ {
+ gimp_image_remove_layer (image, GIMP_LAYER (item),
+ TRUE, NULL);
+ }
+ }
+ }
+
+ /* Reposition or remove guides */
+ list = gimp_image_get_guides (image);
+
+ while (list)
+ {
+ GimpGuide *guide = list->data;
+ gboolean remove_guide = FALSE;
+ gint position = gimp_guide_get_position (guide);
+
+ list = g_list_next (list);
+
+ switch (gimp_guide_get_orientation (guide))
+ {
+ case GIMP_ORIENTATION_HORIZONTAL:
+ position -= y;
+ if ((position < 0) || (position > height))
+ remove_guide = TRUE;
+ break;
+
+ case GIMP_ORIENTATION_VERTICAL:
+ position -= x;
+ if ((position < 0) || (position > width))
+ remove_guide = TRUE;
+ break;
+
+ default:
+ break;
+ }
+
+ if (remove_guide)
+ gimp_image_remove_guide (image, guide, TRUE);
+ else if (position != gimp_guide_get_position (guide))
+ gimp_image_move_guide (image, guide, position, TRUE);
+ }
+
+ /* Reposition or remove sample points */
+ list = gimp_image_get_sample_points (image);
+
+ while (list)
+ {
+ GimpSamplePoint *sample_point = list->data;
+ gboolean remove_sample_point = FALSE;
+ gint old_x;
+ gint old_y;
+ gint new_x;
+ gint new_y;
+
+ list = g_list_next (list);
+
+ gimp_sample_point_get_position (sample_point, &old_x, &old_y);
+ new_x = old_x;
+ new_y = old_y;
+
+ new_y -= y;
+ if ((new_y < 0) || (new_y > height))
+ remove_sample_point = TRUE;
+
+ new_x -= x;
+ if ((new_x < 0) || (new_x > width))
+ remove_sample_point = TRUE;
+
+ if (remove_sample_point)
+ gimp_image_remove_sample_point (image, sample_point, TRUE);
+ else if (new_x != old_x || new_y != old_y)
+ gimp_image_move_sample_point (image, sample_point,
+ new_x, new_y, TRUE);
+ }
+
+ gimp_image_undo_group_end (image);
+
+ gimp_image_size_changed_detailed (image,
+ -x, -y,
+ previous_width, previous_height);
+
+ g_object_thaw_notify (G_OBJECT (image));
+
+ gimp_unset_busy (image->gimp);
+}
diff --git a/app/core/gimpimage-crop.h b/app/core/gimpimage-crop.h
new file mode 100644
index 0000000..19304da
--- /dev/null
+++ b/app/core/gimpimage-crop.h
@@ -0,0 +1,32 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_IMAGE_CROP_H__
+#define __GIMP_IMAGE_CROP_H__
+
+
+void gimp_image_crop (GimpImage *image,
+ GimpContext *context,
+ GimpFillType fill_type,
+ gint x,
+ gint y,
+ gint width,
+ gint height,
+ gboolean crop_layers);
+
+
+#endif /* __GIMP_IMAGE_CROP_H__ */
diff --git a/app/core/gimpimage-duplicate.c b/app/core/gimpimage-duplicate.c
new file mode 100644
index 0000000..4dcaedd
--- /dev/null
+++ b/app/core/gimpimage-duplicate.c
@@ -0,0 +1,535 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpbase/gimpbase.h"
+
+#include "core-types.h"
+
+#include "gegl/gimp-gegl-loops.h"
+
+#include "vectors/gimpvectors.h"
+
+#include "gimp.h"
+#include "gimpchannel.h"
+#include "gimpguide.h"
+#include "gimpimage.h"
+#include "gimpimage-color-profile.h"
+#include "gimpimage-colormap.h"
+#include "gimpimage-duplicate.h"
+#include "gimpimage-grid.h"
+#include "gimpimage-guides.h"
+#include "gimpimage-metadata.h"
+#include "gimpimage-private.h"
+#include "gimpimage-undo.h"
+#include "gimpimage-sample-points.h"
+#include "gimpitemstack.h"
+#include "gimplayer.h"
+#include "gimplayermask.h"
+#include "gimplayer-floating-selection.h"
+#include "gimpparasitelist.h"
+#include "gimpsamplepoint.h"
+
+
+static void gimp_image_duplicate_resolution (GimpImage *image,
+ GimpImage *new_image);
+static void gimp_image_duplicate_save_source_file (GimpImage *image,
+ GimpImage *new_image);
+static void gimp_image_duplicate_colormap (GimpImage *image,
+ GimpImage *new_image);
+static GimpItem * gimp_image_duplicate_item (GimpItem *item,
+ GimpImage *new_image);
+static GimpLayer * gimp_image_duplicate_layers (GimpImage *image,
+ GimpImage *new_image);
+static GimpChannel * gimp_image_duplicate_channels (GimpImage *image,
+ GimpImage *new_image);
+static GimpVectors * gimp_image_duplicate_vectors (GimpImage *image,
+ GimpImage *new_image);
+static void gimp_image_duplicate_floating_sel (GimpImage *image,
+ GimpImage *new_image);
+static void gimp_image_duplicate_mask (GimpImage *image,
+ GimpImage *new_image);
+static void gimp_image_duplicate_components (GimpImage *image,
+ GimpImage *new_image);
+static void gimp_image_duplicate_guides (GimpImage *image,
+ GimpImage *new_image);
+static void gimp_image_duplicate_sample_points (GimpImage *image,
+ GimpImage *new_image);
+static void gimp_image_duplicate_grid (GimpImage *image,
+ GimpImage *new_image);
+static void gimp_image_duplicate_metadata (GimpImage *image,
+ GimpImage *new_image);
+static void gimp_image_duplicate_quick_mask (GimpImage *image,
+ GimpImage *new_image);
+static void gimp_image_duplicate_parasites (GimpImage *image,
+ GimpImage *new_image);
+static void gimp_image_duplicate_color_profile (GimpImage *image,
+ GimpImage *new_image);
+
+
+GimpImage *
+gimp_image_duplicate (GimpImage *image)
+{
+ GimpImage *new_image;
+ GimpLayer *active_layer;
+ GimpChannel *active_channel;
+ GimpVectors *active_vectors;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+
+ gimp_set_busy_until_idle (image->gimp);
+
+ /* Create a new image */
+ new_image = gimp_create_image (image->gimp,
+ gimp_image_get_width (image),
+ gimp_image_get_height (image),
+ gimp_image_get_base_type (image),
+ gimp_image_get_precision (image),
+ FALSE);
+ gimp_image_undo_disable (new_image);
+
+ /* Store the source uri to be used by the save dialog */
+ gimp_image_duplicate_save_source_file (image, new_image);
+
+ /* Copy the colormap if necessary */
+ gimp_image_duplicate_colormap (image, new_image);
+
+ /* Copy resolution information */
+ gimp_image_duplicate_resolution (image, new_image);
+
+ /* Copy parasites first so we have a color profile */
+ gimp_image_duplicate_parasites (image, new_image);
+ gimp_image_duplicate_color_profile (image, new_image);
+
+ /* Copy the layers */
+ active_layer = gimp_image_duplicate_layers (image, new_image);
+
+ /* Copy the channels */
+ active_channel = gimp_image_duplicate_channels (image, new_image);
+
+ /* Copy any vectors */
+ active_vectors = gimp_image_duplicate_vectors (image, new_image);
+
+ /* Copy floating layer */
+ gimp_image_duplicate_floating_sel (image, new_image);
+
+ /* Copy the selection mask */
+ gimp_image_duplicate_mask (image, new_image);
+
+ /* Set active layer, active channel, active vectors */
+ if (active_layer)
+ gimp_image_set_active_layer (new_image, active_layer);
+
+ if (active_channel)
+ gimp_image_set_active_channel (new_image, active_channel);
+
+ if (active_vectors)
+ gimp_image_set_active_vectors (new_image, active_vectors);
+
+ /* Copy state of all color components */
+ gimp_image_duplicate_components (image, new_image);
+
+ /* Copy any guides */
+ gimp_image_duplicate_guides (image, new_image);
+
+ /* Copy any sample points */
+ gimp_image_duplicate_sample_points (image, new_image);
+
+ /* Copy the grid */
+ gimp_image_duplicate_grid (image, new_image);
+
+ /* Copy the metadata */
+ gimp_image_duplicate_metadata (image, new_image);
+
+ /* Copy the quick mask info */
+ gimp_image_duplicate_quick_mask (image, new_image);
+
+ gimp_image_undo_enable (new_image);
+
+ /* Explicitly mark image as dirty, so that its dirty time is set */
+ gimp_image_dirty (new_image, GIMP_DIRTY_ALL);
+
+ return new_image;
+}
+
+static void
+gimp_image_duplicate_resolution (GimpImage *image,
+ GimpImage *new_image)
+{
+ gdouble xres;
+ gdouble yres;
+
+ gimp_image_get_resolution (image, &xres, &yres);
+ gimp_image_set_resolution (new_image, xres, yres);
+ gimp_image_set_unit (new_image, gimp_image_get_unit (image));
+}
+
+static void
+gimp_image_duplicate_save_source_file (GimpImage *image,
+ GimpImage *new_image)
+{
+ GFile *file = gimp_image_get_file (image);
+
+ if (file)
+ g_object_set_data_full (G_OBJECT (new_image), "gimp-image-source-file",
+ g_object_ref (file),
+ (GDestroyNotify) g_object_unref);
+}
+
+static void
+gimp_image_duplicate_colormap (GimpImage *image,
+ GimpImage *new_image)
+{
+ if (gimp_image_get_base_type (new_image) == GIMP_INDEXED)
+ gimp_image_set_colormap (new_image,
+ gimp_image_get_colormap (image),
+ gimp_image_get_colormap_size (image),
+ FALSE);
+}
+
+static GimpItem *
+gimp_image_duplicate_item (GimpItem *item,
+ GimpImage *new_image)
+{
+ GimpItem *new_item;
+
+ new_item = gimp_item_convert (item, new_image,
+ G_TYPE_FROM_INSTANCE (item));
+
+ /* Make sure the copied item doesn't say: "<old item> copy" */
+ gimp_object_set_name (GIMP_OBJECT (new_item),
+ gimp_object_get_name (item));
+
+ return new_item;
+}
+
+static GimpLayer *
+gimp_image_duplicate_layers (GimpImage *image,
+ GimpImage *new_image)
+{
+ GimpLayer *active_layer = NULL;
+ GList *list;
+ gint count;
+
+ for (list = gimp_image_get_layer_iter (image), count = 0;
+ list;
+ list = g_list_next (list))
+ {
+ GimpLayer *layer = list->data;
+ GimpLayer *new_layer;
+
+ if (gimp_layer_is_floating_sel (layer))
+ continue;
+
+ new_layer = GIMP_LAYER (gimp_image_duplicate_item (GIMP_ITEM (layer),
+ new_image));
+
+ /* Make sure that if the layer has a layer mask,
+ * its name isn't screwed up
+ */
+ if (new_layer->mask)
+ gimp_object_set_name (GIMP_OBJECT (new_layer->mask),
+ gimp_object_get_name (layer->mask));
+
+ if (gimp_image_get_active_layer (image) == layer)
+ active_layer = new_layer;
+
+ gimp_image_add_layer (new_image, new_layer,
+ NULL, count++, FALSE);
+ }
+
+ return active_layer;
+}
+
+static GimpChannel *
+gimp_image_duplicate_channels (GimpImage *image,
+ GimpImage *new_image)
+{
+ GimpChannel *active_channel = NULL;
+ GList *list;
+ gint count;
+
+ for (list = gimp_image_get_channel_iter (image), count = 0;
+ list;
+ list = g_list_next (list))
+ {
+ GimpChannel *channel = list->data;
+ GimpChannel *new_channel;
+
+ new_channel = GIMP_CHANNEL (gimp_image_duplicate_item (GIMP_ITEM (channel),
+ new_image));
+
+ if (gimp_image_get_active_channel (image) == channel)
+ active_channel = new_channel;
+
+ gimp_image_add_channel (new_image, new_channel,
+ NULL, count++, FALSE);
+ }
+
+ return active_channel;
+}
+
+static GimpVectors *
+gimp_image_duplicate_vectors (GimpImage *image,
+ GimpImage *new_image)
+{
+ GimpVectors *active_vectors = NULL;
+ GList *list;
+ gint count;
+
+ for (list = gimp_image_get_vectors_iter (image), count = 0;
+ list;
+ list = g_list_next (list))
+ {
+ GimpVectors *vectors = list->data;
+ GimpVectors *new_vectors;
+
+ new_vectors = GIMP_VECTORS (gimp_image_duplicate_item (GIMP_ITEM (vectors),
+ new_image));
+
+ if (gimp_image_get_active_vectors (image) == vectors)
+ active_vectors = new_vectors;
+
+ gimp_image_add_vectors (new_image, new_vectors,
+ NULL, count++, FALSE);
+ }
+
+ return active_vectors;
+}
+
+static void
+gimp_image_duplicate_floating_sel (GimpImage *image,
+ GimpImage *new_image)
+{
+ GimpLayer *floating_sel;
+ GimpDrawable *floating_sel_drawable;
+ GList *floating_sel_path;
+ GimpItemStack *new_item_stack;
+ GimpLayer *new_floating_sel;
+ GimpDrawable *new_floating_sel_drawable;
+
+ floating_sel = gimp_image_get_floating_selection (image);
+
+ if (! floating_sel)
+ return;
+
+ floating_sel_drawable = gimp_layer_get_floating_sel_drawable (floating_sel);
+
+ if (GIMP_IS_LAYER_MASK (floating_sel_drawable))
+ {
+ GimpLayer *layer;
+
+ layer = gimp_layer_mask_get_layer (GIMP_LAYER_MASK (floating_sel_drawable));
+
+ floating_sel_path = gimp_item_get_path (GIMP_ITEM (layer));
+
+ new_item_stack = GIMP_ITEM_STACK (gimp_image_get_layers (new_image));
+ }
+ else
+ {
+ floating_sel_path = gimp_item_get_path (GIMP_ITEM (floating_sel_drawable));
+
+ if (GIMP_IS_LAYER (floating_sel_drawable))
+ new_item_stack = GIMP_ITEM_STACK (gimp_image_get_layers (new_image));
+ else
+ new_item_stack = GIMP_ITEM_STACK (gimp_image_get_channels (new_image));
+ }
+
+ /* adjust path[0] for the floating layer missing in new_image */
+ floating_sel_path->data =
+ GUINT_TO_POINTER (GPOINTER_TO_UINT (floating_sel_path->data) - 1);
+
+ if (GIMP_IS_LAYER (floating_sel_drawable))
+ {
+ new_floating_sel =
+ GIMP_LAYER (gimp_image_duplicate_item (GIMP_ITEM (floating_sel),
+ new_image));
+ }
+ else
+ {
+ /* can't use gimp_item_convert() for floating selections of channels
+ * or layer masks because they maybe don't have a normal layer's type
+ */
+ new_floating_sel =
+ GIMP_LAYER (gimp_item_duplicate (GIMP_ITEM (floating_sel),
+ G_TYPE_FROM_INSTANCE (floating_sel)));
+ gimp_item_set_image (GIMP_ITEM (new_floating_sel), new_image);
+
+ gimp_object_set_name (GIMP_OBJECT (new_floating_sel),
+ gimp_object_get_name (floating_sel));
+ }
+
+ /* Make sure the copied layer doesn't say: "<old layer> copy" */
+ gimp_object_set_name (GIMP_OBJECT (new_floating_sel),
+ gimp_object_get_name (floating_sel));
+
+ new_floating_sel_drawable =
+ GIMP_DRAWABLE (gimp_item_stack_get_item_by_path (new_item_stack,
+ floating_sel_path));
+
+ if (GIMP_IS_LAYER_MASK (floating_sel_drawable))
+ new_floating_sel_drawable =
+ GIMP_DRAWABLE (gimp_layer_get_mask (GIMP_LAYER (new_floating_sel_drawable)));
+
+ floating_sel_attach (new_floating_sel, new_floating_sel_drawable);
+
+ g_list_free (floating_sel_path);
+}
+
+static void
+gimp_image_duplicate_mask (GimpImage *image,
+ GimpImage *new_image)
+{
+ GimpDrawable *mask;
+ GimpDrawable *new_mask;
+
+ mask = GIMP_DRAWABLE (gimp_image_get_mask (image));
+ new_mask = GIMP_DRAWABLE (gimp_image_get_mask (new_image));
+
+ gimp_gegl_buffer_copy (gimp_drawable_get_buffer (mask), NULL, GEGL_ABYSS_NONE,
+ gimp_drawable_get_buffer (new_mask), NULL);
+
+ GIMP_CHANNEL (new_mask)->bounds_known = FALSE;
+ GIMP_CHANNEL (new_mask)->boundary_known = FALSE;
+}
+
+static void
+gimp_image_duplicate_components (GimpImage *image,
+ GimpImage *new_image)
+{
+ GimpImagePrivate *private = GIMP_IMAGE_GET_PRIVATE (image);
+ GimpImagePrivate *new_private = GIMP_IMAGE_GET_PRIVATE (new_image);
+ gint count;
+
+ for (count = 0; count < MAX_CHANNELS; count++)
+ {
+ new_private->visible[count] = private->visible[count];
+ new_private->active[count] = private->active[count];
+ }
+}
+
+static void
+gimp_image_duplicate_guides (GimpImage *image,
+ GimpImage *new_image)
+{
+ GList *list;
+
+ for (list = gimp_image_get_guides (image);
+ list;
+ list = g_list_next (list))
+ {
+ GimpGuide *guide = list->data;
+ gint position = gimp_guide_get_position (guide);
+
+ switch (gimp_guide_get_orientation (guide))
+ {
+ case GIMP_ORIENTATION_HORIZONTAL:
+ gimp_image_add_hguide (new_image, position, FALSE);
+ break;
+
+ case GIMP_ORIENTATION_VERTICAL:
+ gimp_image_add_vguide (new_image, position, FALSE);
+ break;
+
+ default:
+ g_error ("Unknown guide orientation.\n");
+ }
+ }
+}
+
+static void
+gimp_image_duplicate_sample_points (GimpImage *image,
+ GimpImage *new_image)
+{
+ GList *list;
+
+ for (list = gimp_image_get_sample_points (image);
+ list;
+ list = g_list_next (list))
+ {
+ GimpSamplePoint *sample_point = list->data;
+ gint x;
+ gint y;
+
+ gimp_sample_point_get_position (sample_point, &x, &y);
+
+ gimp_image_add_sample_point_at_pos (new_image, x, y, FALSE);
+ }
+}
+
+static void
+gimp_image_duplicate_grid (GimpImage *image,
+ GimpImage *new_image)
+{
+ if (gimp_image_get_grid (image))
+ gimp_image_set_grid (new_image, gimp_image_get_grid (image), FALSE);
+}
+
+static void
+gimp_image_duplicate_metadata (GimpImage *image,
+ GimpImage *new_image)
+{
+ GimpMetadata *metadata = gimp_image_get_metadata (image);
+
+ if (metadata)
+ {
+ metadata = gimp_metadata_duplicate (metadata);
+ gimp_image_set_metadata (new_image, metadata, FALSE);
+ g_object_unref (metadata);
+ }
+}
+
+static void
+gimp_image_duplicate_quick_mask (GimpImage *image,
+ GimpImage *new_image)
+{
+ GimpImagePrivate *private = GIMP_IMAGE_GET_PRIVATE (image);
+ GimpImagePrivate *new_private = GIMP_IMAGE_GET_PRIVATE (new_image);
+
+ new_private->quick_mask_state = private->quick_mask_state;
+ new_private->quick_mask_inverted = private->quick_mask_inverted;
+ new_private->quick_mask_color = private->quick_mask_color;
+}
+
+static void
+gimp_image_duplicate_parasites (GimpImage *image,
+ GimpImage *new_image)
+{
+ GimpImagePrivate *private = GIMP_IMAGE_GET_PRIVATE (image);
+ GimpImagePrivate *new_private = GIMP_IMAGE_GET_PRIVATE (new_image);
+
+ if (private->parasites)
+ {
+ g_object_unref (new_private->parasites);
+ new_private->parasites = gimp_parasite_list_copy (private->parasites);
+ }
+}
+
+static void
+gimp_image_duplicate_color_profile (GimpImage *image,
+ GimpImage *new_image)
+{
+ GimpColorProfile *profile = gimp_image_get_color_profile (image);
+ gboolean is_color_managed = gimp_image_get_is_color_managed (image);
+
+ gimp_image_set_color_profile (new_image, profile, NULL);
+ gimp_image_set_is_color_managed (new_image, is_color_managed, FALSE);
+}
diff --git a/app/core/gimpimage-duplicate.h b/app/core/gimpimage-duplicate.h
new file mode 100644
index 0000000..cbf55a2
--- /dev/null
+++ b/app/core/gimpimage-duplicate.h
@@ -0,0 +1,25 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_IMAGE_DUPLICATE_H__
+#define __GIMP_IMAGE_DUPLICATE_H__
+
+
+GimpImage * gimp_image_duplicate (GimpImage *image);
+
+
+#endif /* __GIMP_IMAGE_DUPLICATE_H__ */
diff --git a/app/core/gimpimage-flip.c b/app/core/gimpimage-flip.c
new file mode 100644
index 0000000..5db05fe
--- /dev/null
+++ b/app/core/gimpimage-flip.c
@@ -0,0 +1,276 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpmath/gimpmath.h"
+
+#include "core-types.h"
+
+#include "gimp.h"
+#include "gimpchannel.h"
+#include "gimpcontainer.h"
+#include "gimpcontext.h"
+#include "gimpguide.h"
+#include "gimpimage.h"
+#include "gimpimage-flip.h"
+#include "gimpimage-guides.h"
+#include "gimpimage-sample-points.h"
+#include "gimpimage-undo.h"
+#include "gimpimage-undo-push.h"
+#include "gimpitem.h"
+#include "gimpobjectqueue.h"
+#include "gimpprogress.h"
+#include "gimpsamplepoint.h"
+
+
+/* local function prototypes */
+
+static void gimp_image_flip_guides (GimpImage *image,
+ GimpOrientationType flip_type,
+ gdouble axis);
+static void gimp_image_flip_sample_points (GimpImage *image,
+ GimpOrientationType flip_type,
+ gdouble axis);
+
+
+/* private functions */
+
+static void
+gimp_image_flip_guides (GimpImage *image,
+ GimpOrientationType flip_type,
+ gdouble axis)
+{
+ gint width = gimp_image_get_width (image);
+ gint height = gimp_image_get_height (image);
+ GList *iter;
+
+ for (iter = gimp_image_get_guides (image); iter;)
+ {
+ GimpGuide *guide = iter->data;
+ gint position = gimp_guide_get_position (guide);
+
+ iter = g_list_next (iter);
+
+ position = SIGNED_ROUND (2.0 * axis - position);
+
+ switch (gimp_guide_get_orientation (guide))
+ {
+ case GIMP_ORIENTATION_HORIZONTAL:
+ if (flip_type == GIMP_ORIENTATION_VERTICAL)
+ {
+ if (position >= 0 && position <= height)
+ gimp_image_move_guide (image, guide, position, TRUE);
+ else
+ gimp_image_remove_guide (image, guide, TRUE);
+ }
+ break;
+
+ case GIMP_ORIENTATION_VERTICAL:
+ if (flip_type == GIMP_ORIENTATION_HORIZONTAL)
+ {
+ if (position >= 0 && position <= width)
+ gimp_image_move_guide (image, guide, position, TRUE);
+ else
+ gimp_image_remove_guide (image, guide, TRUE);
+ }
+ break;
+
+ case GIMP_ORIENTATION_UNKNOWN:
+ g_return_if_reached ();
+ }
+ }
+}
+
+static void
+gimp_image_flip_sample_points (GimpImage *image,
+ GimpOrientationType flip_type,
+ gdouble axis)
+{
+ gint width = gimp_image_get_width (image);
+ gint height = gimp_image_get_height (image);
+ GList *iter;
+
+ for (iter = gimp_image_get_sample_points (image); iter;)
+ {
+ GimpSamplePoint *sample_point = iter->data;
+ gint x;
+ gint y;
+
+ iter = g_list_next (iter);
+
+ gimp_sample_point_get_position (sample_point, &x, &y);
+
+ switch (flip_type)
+ {
+ case GIMP_ORIENTATION_HORIZONTAL:
+ x = SIGNED_ROUND (2.0 * axis - x);
+ break;
+
+ case GIMP_ORIENTATION_VERTICAL:
+ y = SIGNED_ROUND (2.0 * axis - y);
+ break;
+
+ case GIMP_ORIENTATION_UNKNOWN:
+ g_return_if_reached ();
+ }
+
+ if (x >= 0 && x < width &&
+ y >= 0 && y < height)
+ {
+ gimp_image_move_sample_point (image, sample_point, x, y, TRUE);
+ }
+ else
+ {
+ gimp_image_remove_sample_point (image, sample_point, TRUE);
+ }
+ }
+}
+
+
+/* public functions */
+
+void
+gimp_image_flip (GimpImage *image,
+ GimpContext *context,
+ GimpOrientationType flip_type,
+ GimpProgress *progress)
+{
+ gdouble axis = 0.0;
+
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+ g_return_if_fail (GIMP_IS_CONTEXT (context));
+ g_return_if_fail (progress == NULL || GIMP_IS_PROGRESS (progress));
+
+ switch (flip_type)
+ {
+ case GIMP_ORIENTATION_HORIZONTAL:
+ axis = gimp_image_get_width (image) / 2.0;
+ break;
+
+ case GIMP_ORIENTATION_VERTICAL:
+ axis = gimp_image_get_height (image) / 2.0;
+ break;
+
+ case GIMP_ORIENTATION_UNKNOWN:
+ g_return_if_reached ();
+ }
+
+ gimp_image_flip_full (image, context, flip_type, axis,
+ GIMP_TRANSFORM_RESIZE_CLIP, progress);
+}
+
+void
+gimp_image_flip_full (GimpImage *image,
+ GimpContext *context,
+ GimpOrientationType flip_type,
+ gdouble axis,
+ gboolean clip_result,
+ GimpProgress *progress)
+{
+ GimpObjectQueue *queue;
+ GimpItem *item;
+ gint width;
+ gint height;
+ gint offset_x = 0;
+ gint offset_y = 0;
+
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+ g_return_if_fail (GIMP_IS_CONTEXT (context));
+ g_return_if_fail (progress == NULL || GIMP_IS_PROGRESS (progress));
+
+ width = gimp_image_get_width (image);
+ height = gimp_image_get_height (image);
+
+ if (! clip_result)
+ {
+ switch (flip_type)
+ {
+ case GIMP_ORIENTATION_HORIZONTAL:
+ offset_x = SIGNED_ROUND (2.0 * axis - width);
+ axis = width / 2.0;
+ break;
+
+ case GIMP_ORIENTATION_VERTICAL:
+ offset_y = SIGNED_ROUND (2.0 * axis - height);
+ axis = height / 2.0;
+ break;
+
+ case GIMP_ORIENTATION_UNKNOWN:
+ g_return_if_reached ();
+ }
+ }
+
+ gimp_set_busy (image->gimp);
+
+ queue = gimp_object_queue_new (progress);
+ progress = GIMP_PROGRESS (queue);
+
+ gimp_object_queue_push_container (queue, gimp_image_get_layers (image));
+ gimp_object_queue_push (queue, gimp_image_get_mask (image));
+ gimp_object_queue_push_container (queue, gimp_image_get_channels (image));
+ gimp_object_queue_push_container (queue, gimp_image_get_vectors (image));
+
+ gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_IMAGE_FLIP, NULL);
+
+ /* Flip all layers, channels (including selection mask), and vectors */
+ while ((item = gimp_object_queue_pop (queue)))
+ {
+ gboolean clip = FALSE;
+
+ if (GIMP_IS_CHANNEL (item))
+ clip = clip_result;
+
+ gimp_item_flip (item, context, flip_type, axis, clip);
+
+ gimp_progress_set_value (progress, 1.0);
+ }
+
+ /* Flip all Guides */
+ gimp_image_flip_guides (image, flip_type, axis);
+
+ /* Flip all sample points */
+ gimp_image_flip_sample_points (image, flip_type, axis);
+
+ if (offset_x || offset_y)
+ {
+ gimp_image_undo_push_image_size (image,
+ NULL,
+ offset_x,
+ offset_y,
+ width,
+ height);
+ }
+
+ gimp_image_undo_group_end (image);
+
+ g_object_unref (queue);
+
+ if (offset_x || offset_y)
+ {
+ gimp_image_size_changed_detailed (image,
+ -offset_x,
+ -offset_y,
+ width,
+ height);
+ }
+
+ gimp_unset_busy (image->gimp);
+}
diff --git a/app/core/gimpimage-flip.h b/app/core/gimpimage-flip.h
new file mode 100644
index 0000000..5fa6ce6
--- /dev/null
+++ b/app/core/gimpimage-flip.h
@@ -0,0 +1,34 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattisbvf
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_IMAGE_FLIP_H__
+#define __GIMP_IMAGE_FLIP_H__
+
+
+void gimp_image_flip (GimpImage *image,
+ GimpContext *context,
+ GimpOrientationType flip_type,
+ GimpProgress *progress);
+void gimp_image_flip_full (GimpImage *image,
+ GimpContext *context,
+ GimpOrientationType flip_type,
+ gdouble axis,
+ gboolean clip_result,
+ GimpProgress *progress);
+
+
+#endif /* __GIMP_IMAGE_FLIP_H__ */
diff --git a/app/core/gimpimage-grid.c b/app/core/gimpimage-grid.c
new file mode 100644
index 0000000..dec1850
--- /dev/null
+++ b/app/core/gimpimage-grid.c
@@ -0,0 +1,67 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * GimpGrid
+ * Copyright (C) 2003 Henrik Brix Andersen <brix@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpconfig/gimpconfig.h"
+
+#include "core-types.h"
+
+#include "gimpgrid.h"
+#include "gimpimage.h"
+#include "gimpimage-grid.h"
+#include "gimpimage-private.h"
+#include "gimpimage-undo-push.h"
+
+#include "gimp-intl.h"
+
+
+GimpGrid *
+gimp_image_get_grid (GimpImage *image)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+
+ return GIMP_IMAGE_GET_PRIVATE (image)->grid;
+}
+
+void
+gimp_image_set_grid (GimpImage *image,
+ GimpGrid *grid,
+ gboolean push_undo)
+{
+ GimpImagePrivate *private;
+
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+ g_return_if_fail (GIMP_IS_GRID (grid));
+
+ private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ if (gimp_config_is_equal_to (GIMP_CONFIG (private->grid), GIMP_CONFIG (grid)))
+ return;
+
+ if (push_undo)
+ gimp_image_undo_push_image_grid (image,
+ C_("undo-type", "Grid"), private->grid);
+
+ gimp_config_sync (G_OBJECT (grid), G_OBJECT (private->grid), 0);
+}
diff --git a/app/core/gimpimage-grid.h b/app/core/gimpimage-grid.h
new file mode 100644
index 0000000..c2a4a3f
--- /dev/null
+++ b/app/core/gimpimage-grid.h
@@ -0,0 +1,31 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattisbvf
+ *
+ * GimpGrid
+ * Copyright (C) 2003 Henrik Brix Andersen <brix@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_IMAGE_GRID_H__
+#define __GIMP_IMAGE_GRID_H__
+
+
+GimpGrid * gimp_image_get_grid (GimpImage *image);
+void gimp_image_set_grid (GimpImage *image,
+ GimpGrid *grid,
+ gboolean push_undo);
+
+
+#endif /* __GIMP_IMAGE_GRID_H__ */
diff --git a/app/core/gimpimage-guides.c b/app/core/gimpimage-guides.c
new file mode 100644
index 0000000..a265d1e
--- /dev/null
+++ b/app/core/gimpimage-guides.c
@@ -0,0 +1,216 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "core-types.h"
+
+#include "gimp.h"
+#include "gimpimage.h"
+#include "gimpguide.h"
+#include "gimpimage-guides.h"
+#include "gimpimage-private.h"
+#include "gimpimage-undo-push.h"
+
+#include "gimp-intl.h"
+
+
+/* public functions */
+
+GimpGuide *
+gimp_image_add_hguide (GimpImage *image,
+ gint position,
+ gboolean push_undo)
+{
+ GimpGuide *guide;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+ g_return_val_if_fail (position >= 0 &&
+ position <= gimp_image_get_height (image), NULL);
+
+ guide = gimp_guide_new (GIMP_ORIENTATION_HORIZONTAL,
+ image->gimp->next_guide_ID++);
+
+ if (push_undo)
+ gimp_image_undo_push_guide (image,
+ C_("undo-type", "Add Horizontal Guide"), guide);
+
+ gimp_image_add_guide (image, guide, position);
+ g_object_unref (G_OBJECT (guide));
+
+ return guide;
+}
+
+GimpGuide *
+gimp_image_add_vguide (GimpImage *image,
+ gint position,
+ gboolean push_undo)
+{
+ GimpGuide *guide;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+ g_return_val_if_fail (position >= 0 &&
+ position <= gimp_image_get_width (image), NULL);
+
+ guide = gimp_guide_new (GIMP_ORIENTATION_VERTICAL,
+ image->gimp->next_guide_ID++);
+
+ if (push_undo)
+ gimp_image_undo_push_guide (image,
+ C_("undo-type", "Add Vertical Guide"), guide);
+
+ gimp_image_add_guide (image, guide, position);
+ g_object_unref (guide);
+
+ return guide;
+}
+
+void
+gimp_image_add_guide (GimpImage *image,
+ GimpGuide *guide,
+ gint position)
+{
+ GimpImagePrivate *private;
+
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+ g_return_if_fail (GIMP_IS_GUIDE (guide));
+
+ private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ private->guides = g_list_prepend (private->guides, guide);
+
+ gimp_guide_set_position (guide, position);
+ g_object_ref (guide);
+
+ gimp_image_guide_added (image, guide);
+}
+
+void
+gimp_image_remove_guide (GimpImage *image,
+ GimpGuide *guide,
+ gboolean push_undo)
+{
+ GimpImagePrivate *private;
+
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+ g_return_if_fail (GIMP_IS_GUIDE (guide));
+
+ private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ if (gimp_guide_is_custom (guide))
+ push_undo = FALSE;
+
+ if (push_undo)
+ gimp_image_undo_push_guide (image, C_("undo-type", "Remove Guide"), guide);
+
+ private->guides = g_list_remove (private->guides, guide);
+ gimp_aux_item_removed (GIMP_AUX_ITEM (guide));
+
+ gimp_image_guide_removed (image, guide);
+
+ gimp_guide_set_position (guide, GIMP_GUIDE_POSITION_UNDEFINED);
+ g_object_unref (guide);
+}
+
+void
+gimp_image_move_guide (GimpImage *image,
+ GimpGuide *guide,
+ gint position,
+ gboolean push_undo)
+{
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+ g_return_if_fail (GIMP_IS_GUIDE (guide));
+ g_return_if_fail (position >= 0);
+
+ if (gimp_guide_get_orientation (guide) == GIMP_ORIENTATION_HORIZONTAL)
+ g_return_if_fail (position <= gimp_image_get_height (image));
+ else
+ g_return_if_fail (position <= gimp_image_get_width (image));
+
+ if (gimp_guide_is_custom (guide))
+ push_undo = FALSE;
+
+ if (push_undo)
+ gimp_image_undo_push_guide (image, C_("undo-type", "Move Guide"), guide);
+
+ gimp_guide_set_position (guide, position);
+
+ gimp_image_guide_moved (image, guide);
+}
+
+GList *
+gimp_image_get_guides (GimpImage *image)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+
+ return GIMP_IMAGE_GET_PRIVATE (image)->guides;
+}
+
+GimpGuide *
+gimp_image_get_guide (GimpImage *image,
+ guint32 id)
+{
+ GList *guides;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+
+ for (guides = GIMP_IMAGE_GET_PRIVATE (image)->guides;
+ guides;
+ guides = g_list_next (guides))
+ {
+ GimpGuide *guide = guides->data;
+
+ if (gimp_aux_item_get_ID (GIMP_AUX_ITEM (guide)) == id)
+ return guide;
+ }
+
+ return NULL;
+}
+
+GimpGuide *
+gimp_image_get_next_guide (GimpImage *image,
+ guint32 id,
+ gboolean *guide_found)
+{
+ GList *guides;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+ g_return_val_if_fail (guide_found != NULL, NULL);
+
+ if (id == 0)
+ *guide_found = TRUE;
+ else
+ *guide_found = FALSE;
+
+ for (guides = GIMP_IMAGE_GET_PRIVATE (image)->guides;
+ guides;
+ guides = g_list_next (guides))
+ {
+ GimpGuide *guide = guides->data;
+
+ if (*guide_found) /* this is the first guide after the found one */
+ return guide;
+
+ if (gimp_aux_item_get_ID (GIMP_AUX_ITEM (guide)) == id) /* found it, next one will be returned */
+ *guide_found = TRUE;
+ }
+
+ return NULL;
+}
diff --git a/app/core/gimpimage-guides.h b/app/core/gimpimage-guides.h
new file mode 100644
index 0000000..7f9706e
--- /dev/null
+++ b/app/core/gimpimage-guides.h
@@ -0,0 +1,54 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_IMAGE_GUIDES_H__
+#define __GIMP_IMAGE_GUIDES_H__
+
+
+/* public guide adding API
+ */
+GimpGuide * gimp_image_add_hguide (GimpImage *image,
+ gint position,
+ gboolean push_undo);
+GimpGuide * gimp_image_add_vguide (GimpImage *image,
+ gint position,
+ gboolean push_undo);
+
+/* internal guide adding API, does not check the guide's position and
+ * is publicly declared only to be used from undo
+ */
+void gimp_image_add_guide (GimpImage *image,
+ GimpGuide *guide,
+ gint position);
+
+void gimp_image_remove_guide (GimpImage *image,
+ GimpGuide *guide,
+ gboolean push_undo);
+void gimp_image_move_guide (GimpImage *image,
+ GimpGuide *guide,
+ gint position,
+ gboolean push_undo);
+
+GList * gimp_image_get_guides (GimpImage *image);
+GimpGuide * gimp_image_get_guide (GimpImage *image,
+ guint32 id);
+GimpGuide * gimp_image_get_next_guide (GimpImage *image,
+ guint32 id,
+ gboolean *guide_found);
+
+
+#endif /* __GIMP_IMAGE_GUIDES_H__ */
diff --git a/app/core/gimpimage-item-list.c b/app/core/gimpimage-item-list.c
new file mode 100644
index 0000000..6b6bad4
--- /dev/null
+++ b/app/core/gimpimage-item-list.c
@@ -0,0 +1,401 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpbase/gimpbase.h"
+
+#include "core-types.h"
+
+#include "gimpcontext.h"
+#include "gimpimage.h"
+#include "gimpimage-item-list.h"
+#include "gimpimage-undo.h"
+#include "gimpitem.h"
+#include "gimpobjectqueue.h"
+#include "gimpprogress.h"
+
+#include "gimp-intl.h"
+
+
+/* public functions */
+
+gboolean
+gimp_image_item_list_bounds (GimpImage *image,
+ GList *list,
+ gint *x,
+ gint *y,
+ gint *width,
+ gint *height)
+{
+ GList *l;
+ gboolean bounds = FALSE;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);
+ g_return_val_if_fail (x != 0, FALSE);
+ g_return_val_if_fail (y != 0, FALSE);
+ g_return_val_if_fail (width != 0, FALSE);
+ g_return_val_if_fail (height != 0, FALSE);
+
+ for (l = list; l; l = g_list_next (l))
+ {
+ GimpItem *item = l->data;
+ gint tmp_x, tmp_y;
+ gint tmp_w, tmp_h;
+
+ if (gimp_item_bounds (item, &tmp_x, &tmp_y, &tmp_w, &tmp_h))
+ {
+ gint off_x, off_y;
+
+ gimp_item_get_offset (item, &off_x, &off_y);
+
+ if (bounds)
+ {
+ gimp_rectangle_union (*x, *y, *width, *height,
+ tmp_x + off_x, tmp_y + off_y,
+ tmp_w, tmp_h,
+ x, y, width, height);
+ }
+ else
+ {
+ *x = tmp_x + off_x;
+ *y = tmp_y + off_y;
+ *width = tmp_w;
+ *height = tmp_h;
+
+ bounds = TRUE;
+ }
+ }
+ }
+
+ if (! bounds)
+ {
+ *x = 0;
+ *y = 0;
+ *width = gimp_image_get_width (image);
+ *height = gimp_image_get_height (image);
+ }
+
+ return bounds;
+}
+
+void
+gimp_image_item_list_translate (GimpImage *image,
+ GList *list,
+ gint offset_x,
+ gint offset_y,
+ gboolean push_undo)
+{
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+
+ if (list)
+ {
+ GList *l;
+
+ if (list->next)
+ {
+ if (push_undo)
+ {
+ gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_ITEM_DISPLACE,
+ C_("undo-type", "Translate Items"));
+ }
+
+ for (l = list; l; l = g_list_next (l))
+ gimp_item_start_transform (GIMP_ITEM (l->data), push_undo);
+ }
+
+ for (l = list; l; l = g_list_next (l))
+ gimp_item_translate (GIMP_ITEM (l->data),
+ offset_x, offset_y, push_undo);
+
+ if (list->next)
+ {
+ for (l = list; l; l = g_list_next (l))
+ gimp_item_end_transform (GIMP_ITEM (l->data), push_undo);
+
+ if (push_undo)
+ gimp_image_undo_group_end (image);
+ }
+ }
+}
+
+void
+gimp_image_item_list_flip (GimpImage *image,
+ GList *list,
+ GimpContext *context,
+ GimpOrientationType flip_type,
+ gdouble axis,
+ gboolean clip_result)
+{
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+ g_return_if_fail (GIMP_IS_CONTEXT (context));
+
+ if (list)
+ {
+ GList *l;
+
+ if (list->next)
+ {
+ gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_TRANSFORM,
+ C_("undo-type", "Flip Items"));
+
+ for (l = list; l; l = g_list_next (l))
+ gimp_item_start_transform (GIMP_ITEM (l->data), TRUE);
+ }
+
+ for (l = list; l; l = g_list_next (l))
+ {
+ GimpItem *item = l->data;
+
+ gimp_item_flip (item, context,
+ flip_type, axis,
+ gimp_item_get_clip (item, clip_result));
+ }
+
+ if (list->next)
+ {
+ for (l = list; l; l = g_list_next (l))
+ gimp_item_end_transform (GIMP_ITEM (l->data), TRUE);
+
+ gimp_image_undo_group_end (image);
+ }
+ }
+}
+
+void
+gimp_image_item_list_rotate (GimpImage *image,
+ GList *list,
+ GimpContext *context,
+ GimpRotationType rotate_type,
+ gdouble center_x,
+ gdouble center_y,
+ gboolean clip_result)
+{
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+ g_return_if_fail (GIMP_IS_CONTEXT (context));
+
+ if (list)
+ {
+ GList *l;
+
+ if (list->next)
+ {
+ gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_TRANSFORM,
+ C_("undo-type", "Rotate Items"));
+
+ for (l = list; l; l = g_list_next (l))
+ gimp_item_start_transform (GIMP_ITEM (l->data), TRUE);
+ }
+
+ for (l = list; l; l = g_list_next (l))
+ {
+ GimpItem *item = l->data;
+
+ gimp_item_rotate (item, context,
+ rotate_type, center_x, center_y,
+ gimp_item_get_clip (item, clip_result));
+ }
+
+ if (list->next)
+ {
+ for (l = list; l; l = g_list_next (l))
+ gimp_item_end_transform (GIMP_ITEM (l->data), TRUE);
+
+ gimp_image_undo_group_end (image);
+ }
+ }
+}
+
+void
+gimp_image_item_list_transform (GimpImage *image,
+ GList *list,
+ GimpContext *context,
+ const GimpMatrix3 *matrix,
+ GimpTransformDirection direction,
+ GimpInterpolationType interpolation_type,
+ GimpTransformResize clip_result,
+ GimpProgress *progress)
+{
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+ g_return_if_fail (GIMP_IS_CONTEXT (context));
+ g_return_if_fail (progress == NULL || GIMP_IS_PROGRESS (progress));
+
+ if (list)
+ {
+ GimpObjectQueue *queue = NULL;
+ GList *l;
+
+ if (progress)
+ {
+ queue = gimp_object_queue_new (progress);
+ progress = GIMP_PROGRESS (queue);
+
+ gimp_object_queue_push_list (queue, list);
+ }
+
+ if (list->next)
+ {
+ gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_TRANSFORM,
+ C_("undo-type", "Transform Items"));
+
+ for (l = list; l; l = g_list_next (l))
+ gimp_item_start_transform (GIMP_ITEM (l->data), TRUE);
+ }
+
+ for (l = list; l; l = g_list_next (l))
+ {
+ GimpItem *item = l->data;
+
+ if (queue)
+ gimp_object_queue_pop (queue);
+
+ gimp_item_transform (item, context,
+ matrix, direction,
+ interpolation_type,
+ gimp_item_get_clip (item, clip_result),
+ progress);
+ }
+
+ if (list->next)
+ {
+ for (l = list; l; l = g_list_next (l))
+ gimp_item_end_transform (GIMP_ITEM (l->data), TRUE);
+
+ gimp_image_undo_group_end (image);
+ }
+
+ g_clear_object (&queue);
+ }
+}
+
+/**
+ * gimp_image_item_list_get_list:
+ * @image: An @image.
+ * @type: Which type of items to return.
+ * @set: Set the returned items are part of.
+ *
+ * This function returns a #GList of #GimpItem<!-- -->s for which the
+ * @type and @set criterions match.
+ *
+ * Return value: The list of items.
+ **/
+GList *
+gimp_image_item_list_get_list (GimpImage *image,
+ GimpItemTypeMask type,
+ GimpItemSet set)
+{
+ GList *all_items;
+ GList *list;
+ GList *return_list = NULL;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+
+ if (type & GIMP_ITEM_TYPE_LAYERS)
+ {
+ all_items = gimp_image_get_layer_list (image);
+
+ for (list = all_items; list; list = g_list_next (list))
+ {
+ GimpItem *item = list->data;
+
+ if (gimp_item_is_in_set (item, set))
+ return_list = g_list_prepend (return_list, item);
+ }
+
+ g_list_free (all_items);
+ }
+
+ if (type & GIMP_ITEM_TYPE_CHANNELS)
+ {
+ all_items = gimp_image_get_channel_list (image);
+
+ for (list = all_items; list; list = g_list_next (list))
+ {
+ GimpItem *item = list->data;
+
+ if (gimp_item_is_in_set (item, set))
+ return_list = g_list_prepend (return_list, item);
+ }
+
+ g_list_free (all_items);
+ }
+
+ if (type & GIMP_ITEM_TYPE_VECTORS)
+ {
+ all_items = gimp_image_get_vectors_list (image);
+
+ for (list = all_items; list; list = g_list_next (list))
+ {
+ GimpItem *item = list->data;
+
+ if (gimp_item_is_in_set (item, set))
+ return_list = g_list_prepend (return_list, item);
+ }
+
+ g_list_free (all_items);
+ }
+
+ return g_list_reverse (return_list);
+}
+
+static GList *
+gimp_image_item_list_remove_children (GList *list,
+ const GimpItem *parent)
+{
+ GList *l = list;
+
+ while (l)
+ {
+ GimpItem *item = l->data;
+
+ l = g_list_next (l);
+
+ if (gimp_viewable_is_ancestor (GIMP_VIEWABLE (parent),
+ GIMP_VIEWABLE (item)))
+ {
+ list = g_list_remove (list, item);
+ }
+ }
+
+ return list;
+}
+
+GList *
+gimp_image_item_list_filter (GList *list)
+{
+ GList *l;
+
+ if (! list)
+ return NULL;
+
+ for (l = list; l; l = g_list_next (l))
+ {
+ GimpItem *item = l->data;
+ GList *next;
+
+ next = gimp_image_item_list_remove_children (g_list_next (l), item);
+
+ l->next = next;
+ if (next)
+ next->prev = l;
+ }
+
+ return list;
+}
diff --git a/app/core/gimpimage-item-list.h b/app/core/gimpimage-item-list.h
new file mode 100644
index 0000000..cd69637
--- /dev/null
+++ b/app/core/gimpimage-item-list.h
@@ -0,0 +1,63 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_IMAGE_ITEM_LIST_H__
+#define __GIMP_IMAGE_ITEM_LIST_H__
+
+
+gboolean gimp_image_item_list_bounds (GimpImage *image,
+ GList *list,
+ gint *x,
+ gint *y,
+ gint *width,
+ gint *height);
+
+void gimp_image_item_list_translate (GimpImage *image,
+ GList *list,
+ gint offset_x,
+ gint offset_y,
+ gboolean push_undo);
+void gimp_image_item_list_flip (GimpImage *image,
+ GList *list,
+ GimpContext *context,
+ GimpOrientationType flip_type,
+ gdouble axis,
+ gboolean clip_result);
+void gimp_image_item_list_rotate (GimpImage *image,
+ GList *list,
+ GimpContext *context,
+ GimpRotationType rotate_type,
+ gdouble center_x,
+ gdouble center_y,
+ gboolean clip_result);
+void gimp_image_item_list_transform (GimpImage *image,
+ GList *list,
+ GimpContext *context,
+ const GimpMatrix3 *matrix,
+ GimpTransformDirection direction,
+ GimpInterpolationType interpolation_type,
+ GimpTransformResize clip_result,
+ GimpProgress *progress);
+
+GList * gimp_image_item_list_get_list (GimpImage *image,
+ GimpItemTypeMask type,
+ GimpItemSet set);
+
+GList * gimp_image_item_list_filter (GList *list);
+
+
+#endif /* __GIMP_IMAGE_ITEM_LIST_H__ */
diff --git a/app/core/gimpimage-merge.c b/app/core/gimpimage-merge.c
new file mode 100644
index 0000000..3d96860
--- /dev/null
+++ b/app/core/gimpimage-merge.c
@@ -0,0 +1,745 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <cairo.h>
+#include <gegl.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+#include "libgimpcolor/gimpcolor.h"
+#include "libgimpmath/gimpmath.h"
+#include "libgimpbase/gimpbase.h"
+
+#include "core-types.h"
+
+#include "gegl/gimp-babl-compat.h"
+#include "gegl/gimp-gegl-apply-operation.h"
+#include "gegl/gimp-gegl-nodes.h"
+#include "gegl/gimp-gegl-utils.h"
+
+#include "vectors/gimpvectors.h"
+
+#include "gimp.h"
+#include "gimpcontext.h"
+#include "gimperror.h"
+#include "gimpgrouplayer.h"
+#include "gimpimage.h"
+#include "gimpimage-merge.h"
+#include "gimpimage-undo.h"
+#include "gimpitemstack.h"
+#include "gimplayer-floating-selection.h"
+#include "gimplayer-new.h"
+#include "gimplayermask.h"
+#include "gimpmarshal.h"
+#include "gimpparasitelist.h"
+#include "gimppickable.h"
+#include "gimpprogress.h"
+#include "gimpprojectable.h"
+#include "gimpundostack.h"
+
+#include "gimp-intl.h"
+
+
+static GimpLayer * gimp_image_merge_layers (GimpImage *image,
+ GimpContainer *container,
+ GSList *merge_list,
+ GimpContext *context,
+ GimpMergeType merge_type,
+ const gchar *undo_desc,
+ GimpProgress *progress);
+
+
+/* public functions */
+
+GimpLayer *
+gimp_image_merge_visible_layers (GimpImage *image,
+ GimpContext *context,
+ GimpMergeType merge_type,
+ gboolean merge_active_group,
+ gboolean discard_invisible,
+ GimpProgress *progress)
+{
+ GimpContainer *container;
+ GList *list;
+ GSList *merge_list = NULL;
+ GSList *invisible_list = NULL;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+ g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL);
+ g_return_val_if_fail (progress == NULL || GIMP_IS_PROGRESS (progress), NULL);
+
+ if (merge_active_group)
+ {
+ GimpLayer *active_layer = gimp_image_get_active_layer (image);
+
+ /* if the active layer is the floating selection, get the
+ * underlying drawable, but only if it is a layer
+ */
+ if (active_layer && gimp_layer_is_floating_sel (active_layer))
+ {
+ GimpDrawable *fs_drawable;
+
+ fs_drawable = gimp_layer_get_floating_sel_drawable (active_layer);
+
+ if (GIMP_IS_LAYER (fs_drawable))
+ active_layer = GIMP_LAYER (fs_drawable);
+ }
+
+ if (active_layer)
+ container = gimp_item_get_container (GIMP_ITEM (active_layer));
+ else
+ container = gimp_image_get_layers (image);
+ }
+ else
+ {
+ container = gimp_image_get_layers (image);
+ }
+
+ for (list = gimp_item_stack_get_item_iter (GIMP_ITEM_STACK (container));
+ list;
+ list = g_list_next (list))
+ {
+ GimpLayer *layer = list->data;
+
+ if (gimp_layer_is_floating_sel (layer))
+ continue;
+
+ if (gimp_item_get_visible (GIMP_ITEM (layer)))
+ {
+ merge_list = g_slist_append (merge_list, layer);
+ }
+ else if (discard_invisible)
+ {
+ invisible_list = g_slist_append (invisible_list, layer);
+ }
+ }
+
+ if (merge_list)
+ {
+ GimpLayer *layer;
+ const gchar *undo_desc = C_("undo-type", "Merge Visible Layers");
+
+ gimp_set_busy (image->gimp);
+
+ gimp_image_undo_group_start (image,
+ GIMP_UNDO_GROUP_IMAGE_LAYERS_MERGE,
+ undo_desc);
+
+ /* if there's a floating selection, anchor it */
+ if (gimp_image_get_floating_selection (image))
+ floating_sel_anchor (gimp_image_get_floating_selection (image));
+
+ layer = gimp_image_merge_layers (image,
+ container,
+ merge_list, context, merge_type,
+ undo_desc, progress);
+ g_slist_free (merge_list);
+
+ if (invisible_list)
+ {
+ GSList *list;
+
+ for (list = invisible_list; list; list = g_slist_next (list))
+ gimp_image_remove_layer (image, list->data, TRUE, NULL);
+
+ g_slist_free (invisible_list);
+ }
+
+ gimp_image_undo_group_end (image);
+
+ gimp_unset_busy (image->gimp);
+
+ return layer;
+ }
+
+ return gimp_image_get_active_layer (image);
+}
+
+GimpLayer *
+gimp_image_flatten (GimpImage *image,
+ GimpContext *context,
+ GimpProgress *progress,
+ GError **error)
+{
+ GList *list;
+ GSList *merge_list = NULL;
+ GimpLayer *layer;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+ g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL);
+ g_return_val_if_fail (progress == NULL || GIMP_IS_PROGRESS (progress), NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ for (list = gimp_image_get_layer_iter (image);
+ list;
+ list = g_list_next (list))
+ {
+ layer = list->data;
+
+ if (gimp_layer_is_floating_sel (layer))
+ continue;
+
+ if (gimp_item_get_visible (GIMP_ITEM (layer)))
+ merge_list = g_slist_append (merge_list, layer);
+ }
+
+ if (merge_list)
+ {
+ const gchar *undo_desc = C_("undo-type", "Flatten Image");
+
+ gimp_set_busy (image->gimp);
+
+ gimp_image_undo_group_start (image,
+ GIMP_UNDO_GROUP_IMAGE_LAYERS_MERGE,
+ undo_desc);
+
+ /* if there's a floating selection, anchor it */
+ if (gimp_image_get_floating_selection (image))
+ floating_sel_anchor (gimp_image_get_floating_selection (image));
+
+ layer = gimp_image_merge_layers (image,
+ gimp_image_get_layers (image),
+ merge_list, context,
+ GIMP_FLATTEN_IMAGE,
+ undo_desc, progress);
+ g_slist_free (merge_list);
+
+ gimp_image_alpha_changed (image);
+
+ gimp_image_undo_group_end (image);
+
+ gimp_unset_busy (image->gimp);
+
+ return layer;
+ }
+
+ g_set_error_literal (error, GIMP_ERROR, GIMP_FAILED,
+ _("Cannot flatten an image without any visible layer."));
+ return NULL;
+}
+
+GimpLayer *
+gimp_image_merge_down (GimpImage *image,
+ GimpLayer *current_layer,
+ GimpContext *context,
+ GimpMergeType merge_type,
+ GimpProgress *progress,
+ GError **error)
+{
+ GimpLayer *layer;
+ GList *list;
+ GList *layer_list = NULL;
+ GSList *merge_list = NULL;
+ const gchar *undo_desc;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+ g_return_val_if_fail (GIMP_IS_LAYER (current_layer), NULL);
+ g_return_val_if_fail (gimp_item_is_attached (GIMP_ITEM (current_layer)), NULL);
+ g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL);
+ g_return_val_if_fail (progress == NULL || GIMP_IS_PROGRESS (progress), NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ if (gimp_layer_is_floating_sel (current_layer))
+ {
+ g_set_error_literal (error, GIMP_ERROR, GIMP_FAILED,
+ _("Cannot merge down a floating selection."));
+ return NULL;
+ }
+
+ if (! gimp_item_get_visible (GIMP_ITEM (current_layer)))
+ {
+ g_set_error_literal (error, GIMP_ERROR, GIMP_FAILED,
+ _("Cannot merge down an invisible layer."));
+ return NULL;
+ }
+
+ for (list = gimp_item_get_container_iter (GIMP_ITEM (current_layer));
+ list;
+ list = g_list_next (list))
+ {
+ layer = list->data;
+
+ if (layer == current_layer)
+ break;
+ }
+
+ for (layer_list = g_list_next (list);
+ layer_list;
+ layer_list = g_list_next (layer_list))
+ {
+ layer = layer_list->data;
+
+ if (gimp_item_get_visible (GIMP_ITEM (layer)))
+ {
+ if (gimp_viewable_get_children (GIMP_VIEWABLE (layer)))
+ {
+ g_set_error_literal (error, GIMP_ERROR, GIMP_FAILED,
+ _("Cannot merge down to a layer group."));
+ return NULL;
+ }
+
+ if (gimp_item_is_content_locked (GIMP_ITEM (layer)))
+ {
+ g_set_error_literal (error, GIMP_ERROR, GIMP_FAILED,
+ _("The layer to merge down to is locked."));
+ return NULL;
+ }
+
+ merge_list = g_slist_append (NULL, layer);
+ break;
+ }
+ }
+
+ if (! merge_list)
+ {
+ g_set_error_literal (error, GIMP_ERROR, GIMP_FAILED,
+ _("There is no visible layer to merge down to."));
+ return NULL;
+ }
+
+ merge_list = g_slist_prepend (merge_list, current_layer);
+
+ undo_desc = C_("undo-type", "Merge Down");
+
+ gimp_set_busy (image->gimp);
+
+ gimp_image_undo_group_start (image,
+ GIMP_UNDO_GROUP_IMAGE_LAYERS_MERGE,
+ undo_desc);
+
+ layer = gimp_image_merge_layers (image,
+ gimp_item_get_container (GIMP_ITEM (current_layer)),
+ merge_list, context, merge_type,
+ undo_desc, progress);
+ g_slist_free (merge_list);
+
+ gimp_image_undo_group_end (image);
+
+ gimp_unset_busy (image->gimp);
+
+ return layer;
+}
+
+GimpLayer *
+gimp_image_merge_group_layer (GimpImage *image,
+ GimpGroupLayer *group)
+{
+ GimpLayer *parent;
+ GimpLayer *layer;
+ gint index;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+ g_return_val_if_fail (GIMP_IS_GROUP_LAYER (group), NULL);
+ g_return_val_if_fail (gimp_item_is_attached (GIMP_ITEM (group)), NULL);
+ g_return_val_if_fail (gimp_item_get_image (GIMP_ITEM (group)) == image, NULL);
+
+ gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_IMAGE_LAYERS_MERGE,
+ C_("undo-type", "Merge Layer Group"));
+
+ parent = gimp_layer_get_parent (GIMP_LAYER (group));
+ index = gimp_item_get_index (GIMP_ITEM (group));
+
+ /* if this is a pass-through group, change its mode to NORMAL *before*
+ * duplicating it, since PASS_THROUGH mode is invalid for regular layers.
+ * see bug #793714.
+ */
+ if (gimp_layer_get_mode (GIMP_LAYER (group)) == GIMP_LAYER_MODE_PASS_THROUGH)
+ {
+ GimpLayerColorSpace blend_space;
+ GimpLayerColorSpace composite_space;
+ GimpLayerCompositeMode composite_mode;
+
+ /* keep the group's current blend space, composite space, and composite
+ * mode.
+ */
+ blend_space = gimp_layer_get_blend_space (GIMP_LAYER (group));
+ composite_space = gimp_layer_get_composite_space (GIMP_LAYER (group));
+ composite_mode = gimp_layer_get_composite_mode (GIMP_LAYER (group));
+
+ gimp_layer_set_mode (GIMP_LAYER (group), GIMP_LAYER_MODE_NORMAL, TRUE);
+ gimp_layer_set_blend_space (GIMP_LAYER (group), blend_space, TRUE);
+ gimp_layer_set_composite_space (GIMP_LAYER (group), composite_space, TRUE);
+ gimp_layer_set_composite_mode (GIMP_LAYER (group), composite_mode, TRUE);
+ }
+
+ layer = GIMP_LAYER (gimp_item_duplicate (GIMP_ITEM (group),
+ GIMP_TYPE_LAYER));
+
+ gimp_object_set_name (GIMP_OBJECT (layer), gimp_object_get_name (group));
+
+ gimp_image_remove_layer (image, GIMP_LAYER (group), TRUE, NULL);
+ gimp_image_add_layer (image, layer, parent, index, TRUE);
+
+ gimp_image_undo_group_end (image);
+
+ return layer;
+}
+
+
+/* merging vectors */
+
+GimpVectors *
+gimp_image_merge_visible_vectors (GimpImage *image,
+ GError **error)
+{
+ GList *list;
+ GList *merge_list = NULL;
+ GimpVectors *vectors;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ for (list = gimp_image_get_vectors_iter (image);
+ list;
+ list = g_list_next (list))
+ {
+ vectors = list->data;
+
+ if (gimp_item_get_visible (GIMP_ITEM (vectors)))
+ merge_list = g_list_prepend (merge_list, vectors);
+ }
+
+ merge_list = g_list_reverse (merge_list);
+
+ if (merge_list && merge_list->next)
+ {
+ GimpVectors *target_vectors;
+ gchar *name;
+ gint pos;
+
+ gimp_set_busy (image->gimp);
+
+ gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_IMAGE_VECTORS_MERGE,
+ C_("undo-type", "Merge Visible Paths"));
+
+ vectors = GIMP_VECTORS (merge_list->data);
+
+ name = g_strdup (gimp_object_get_name (vectors));
+ pos = gimp_item_get_index (GIMP_ITEM (vectors));
+
+ target_vectors = GIMP_VECTORS (gimp_item_duplicate (GIMP_ITEM (vectors),
+ GIMP_TYPE_VECTORS));
+ gimp_image_remove_vectors (image, vectors, TRUE, NULL);
+
+ for (list = g_list_next (merge_list);
+ list;
+ list = g_list_next (list))
+ {
+ vectors = list->data;
+
+ gimp_vectors_add_strokes (vectors, target_vectors);
+ gimp_image_remove_vectors (image, vectors, TRUE, NULL);
+ }
+
+ gimp_object_take_name (GIMP_OBJECT (target_vectors), name);
+
+ g_list_free (merge_list);
+
+ /* FIXME tree */
+ gimp_image_add_vectors (image, target_vectors, NULL, pos, TRUE);
+ gimp_unset_busy (image->gimp);
+
+ gimp_image_undo_group_end (image);
+
+ return target_vectors;
+ }
+ else
+ {
+ g_set_error_literal (error, GIMP_ERROR, GIMP_FAILED,
+ _("Not enough visible paths for a merge. "
+ "There must be at least two."));
+ return NULL;
+ }
+}
+
+
+/* private functions */
+
+static GimpLayer *
+gimp_image_merge_layers (GimpImage *image,
+ GimpContainer *container,
+ GSList *merge_list,
+ GimpContext *context,
+ GimpMergeType merge_type,
+ const gchar *undo_desc,
+ GimpProgress *progress)
+{
+ GimpLayer *parent;
+ gint x1, y1;
+ gint x2, y2;
+ GSList *layers;
+ GimpLayer *layer;
+ GimpLayer *top_layer;
+ GimpLayer *bottom_layer;
+ GimpLayer *merge_layer;
+ gint position;
+ GeglNode *node;
+ GeglNode *source_node;
+ GeglNode *flatten_node;
+ GeglNode *offset_node;
+ GeglNode *last_node;
+ GeglNode *last_node_source;
+ GimpParasiteList *parasites;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+ g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL);
+ g_return_val_if_fail (progress == NULL || GIMP_IS_PROGRESS (progress), NULL);
+
+ top_layer = merge_list->data;
+ parent = gimp_layer_get_parent (top_layer);
+
+ /* Make sure the image's graph is constructed, so that top-level layers have
+ * a parent node.
+ */
+ (void) gimp_projectable_get_graph (GIMP_PROJECTABLE (image));
+
+ /* Make sure the parent's graph is constructed, so that the top layer has a
+ * parent node, even if it is the child of a group layer (in particular, of
+ * an invisible group layer, whose graph may not have been constructed as a
+ * result of the above call. see issue #2095.)
+ */
+ if (parent)
+ (void) gimp_filter_get_node (GIMP_FILTER (parent));
+
+ /* Build our graph inside the top-layer's parent node */
+ source_node = gimp_filter_get_node (GIMP_FILTER (top_layer));
+ node = gegl_node_get_parent (source_node);
+
+ g_return_val_if_fail (node, NULL);
+
+ /* Get the layer extents */
+ x1 = y1 = 0;
+ x2 = y2 = 0;
+ for (layers = merge_list; layers; layers = g_slist_next (layers))
+ {
+ gint off_x, off_y;
+
+ layer = layers->data;
+
+ gimp_item_get_offset (GIMP_ITEM (layer), &off_x, &off_y);
+
+ switch (merge_type)
+ {
+ case GIMP_EXPAND_AS_NECESSARY:
+ case GIMP_CLIP_TO_IMAGE:
+ if (layers == merge_list)
+ {
+ x1 = off_x;
+ y1 = off_y;
+ x2 = off_x + gimp_item_get_width (GIMP_ITEM (layer));
+ y2 = off_y + gimp_item_get_height (GIMP_ITEM (layer));
+ }
+ else
+ {
+ if (off_x < x1)
+ x1 = off_x;
+ if (off_y < y1)
+ y1 = off_y;
+ if ((off_x + gimp_item_get_width (GIMP_ITEM (layer))) > x2)
+ x2 = (off_x + gimp_item_get_width (GIMP_ITEM (layer)));
+ if ((off_y + gimp_item_get_height (GIMP_ITEM (layer))) > y2)
+ y2 = (off_y + gimp_item_get_height (GIMP_ITEM (layer)));
+ }
+
+ if (merge_type == GIMP_CLIP_TO_IMAGE)
+ {
+ x1 = CLAMP (x1, 0, gimp_image_get_width (image));
+ y1 = CLAMP (y1, 0, gimp_image_get_height (image));
+ x2 = CLAMP (x2, 0, gimp_image_get_width (image));
+ y2 = CLAMP (y2, 0, gimp_image_get_height (image));
+ }
+ break;
+
+ case GIMP_CLIP_TO_BOTTOM_LAYER:
+ if (layers->next == NULL)
+ {
+ x1 = off_x;
+ y1 = off_y;
+ x2 = off_x + gimp_item_get_width (GIMP_ITEM (layer));
+ y2 = off_y + gimp_item_get_height (GIMP_ITEM (layer));
+ }
+ break;
+
+ case GIMP_FLATTEN_IMAGE:
+ if (layers->next == NULL)
+ {
+ x1 = 0;
+ y1 = 0;
+ x2 = gimp_image_get_width (image);
+ y2 = gimp_image_get_height (image);
+ }
+ break;
+ }
+ }
+
+ if ((x2 - x1) == 0 || (y2 - y1) == 0)
+ return NULL;
+
+ bottom_layer = layer;
+
+ flatten_node = NULL;
+
+ if (merge_type == GIMP_FLATTEN_IMAGE ||
+ (gimp_drawable_is_indexed (GIMP_DRAWABLE (layer)) &&
+ ! gimp_drawable_has_alpha (GIMP_DRAWABLE (layer))))
+ {
+ GimpRGB bg;
+
+ merge_layer = gimp_layer_new (image, (x2 - x1), (y2 - y1),
+ gimp_image_get_layer_format (image, FALSE),
+ gimp_object_get_name (bottom_layer),
+ GIMP_OPACITY_OPAQUE,
+ gimp_image_get_default_new_layer_mode (image));
+
+ if (! merge_layer)
+ {
+ g_warning ("%s: could not allocate merge layer", G_STRFUNC);
+
+ return NULL;
+ }
+
+ /* get the background for compositing */
+ gimp_context_get_background (context, &bg);
+ gimp_pickable_srgb_to_image_color (GIMP_PICKABLE (layer),
+ &bg, &bg);
+
+ flatten_node = gimp_gegl_create_flatten_node (
+ &bg, gimp_layer_get_real_composite_space (bottom_layer));
+ }
+ else
+ {
+ /* The final merged layer inherits the name of the bottom most layer
+ * and the resulting layer has an alpha channel whether or not the
+ * original did. Opacity is set to 100% and the MODE is set to normal.
+ */
+
+ merge_layer =
+ gimp_layer_new (image, (x2 - x1), (y2 - y1),
+ gimp_drawable_get_format_with_alpha (GIMP_DRAWABLE (bottom_layer)),
+ gimp_object_get_name (bottom_layer),
+ GIMP_OPACITY_OPAQUE,
+ gimp_image_get_default_new_layer_mode (image));
+
+ if (! merge_layer)
+ {
+ g_warning ("%s: could not allocate merge layer", G_STRFUNC);
+
+ return NULL;
+ }
+ }
+
+ if (merge_type == GIMP_FLATTEN_IMAGE)
+ {
+ position = 0;
+ }
+ else
+ {
+ /* Find the index in the layer list of the bottom layer--we need this
+ * in order to add the final, merged layer to the layer list correctly
+ */
+ position =
+ gimp_container_get_n_children (container) -
+ gimp_container_get_child_index (container, GIMP_OBJECT (bottom_layer));
+ }
+
+ gimp_item_set_offset (GIMP_ITEM (merge_layer), x1, y1);
+
+ offset_node = gegl_node_new_child (node,
+ "operation", "gegl:translate",
+ "x", (gdouble) -x1,
+ "y", (gdouble) -y1,
+ NULL);
+
+ if (flatten_node)
+ {
+ gegl_node_add_child (node, flatten_node);
+ g_object_unref (flatten_node);
+
+ gegl_node_link_many (source_node, flatten_node, offset_node, NULL);
+ }
+ else
+ {
+ gegl_node_link_many (source_node, offset_node, NULL);
+ }
+
+ /* Disconnect the bottom-layer node's input */
+ last_node = gimp_filter_get_node (GIMP_FILTER (bottom_layer));
+ last_node_source = gegl_node_get_producer (last_node, "input", NULL);
+
+ gegl_node_disconnect (last_node, "input");
+
+ /* Render the graph into the merge layer */
+ gimp_gegl_apply_operation (NULL, progress, undo_desc, offset_node,
+ gimp_drawable_get_buffer (
+ GIMP_DRAWABLE (merge_layer)),
+ NULL, FALSE);
+
+ /* Reconnect the bottom-layer node's input */
+ if (last_node_source)
+ gegl_node_link (last_node_source, last_node);
+
+ /* Clean up the graph */
+ gegl_node_remove_child (node, offset_node);
+
+ if (flatten_node)
+ gegl_node_remove_child (node, flatten_node);
+
+ /* Copy the tattoo and parasites of the bottom layer to the new layer */
+ gimp_item_set_tattoo (GIMP_ITEM (merge_layer),
+ gimp_item_get_tattoo (GIMP_ITEM (bottom_layer)));
+
+ parasites = gimp_item_get_parasites (GIMP_ITEM (bottom_layer));
+ parasites = gimp_parasite_list_copy (parasites);
+ gimp_item_set_parasites (GIMP_ITEM (merge_layer), parasites);
+ g_object_unref (parasites);
+
+ /* Remove the merged layers from the image */
+ for (layers = merge_list; layers; layers = g_slist_next (layers))
+ gimp_image_remove_layer (image, layers->data, TRUE, NULL);
+
+ gimp_item_set_visible (GIMP_ITEM (merge_layer), TRUE, FALSE);
+
+ /* if the type is flatten, remove all the remaining layers */
+ if (merge_type == GIMP_FLATTEN_IMAGE)
+ {
+ GList *list = gimp_image_get_layer_iter (image);
+
+ while (list)
+ {
+ layer = list->data;
+
+ list = g_list_next (list);
+ gimp_image_remove_layer (image, layer, TRUE, NULL);
+ }
+
+ gimp_image_add_layer (image, merge_layer, parent,
+ position, TRUE);
+ }
+ else
+ {
+ /* Add the layer to the image */
+ gimp_image_add_layer (image, merge_layer, parent,
+ gimp_container_get_n_children (container) -
+ position + 1,
+ TRUE);
+ }
+
+ gimp_drawable_update (GIMP_DRAWABLE (merge_layer), 0, 0, -1, -1);
+
+ return merge_layer;
+}
diff --git a/app/core/gimpimage-merge.h b/app/core/gimpimage-merge.h
new file mode 100644
index 0000000..93aab4e
--- /dev/null
+++ b/app/core/gimpimage-merge.h
@@ -0,0 +1,46 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_IMAGE_MERGE_H__
+#define __GIMP_IMAGE_MERGE_H__
+
+
+GimpLayer * gimp_image_merge_visible_layers (GimpImage *image,
+ GimpContext *context,
+ GimpMergeType merge_type,
+ gboolean merge_active_group,
+ gboolean discard_invisible,
+ GimpProgress *progress);
+GimpLayer * gimp_image_merge_down (GimpImage *image,
+ GimpLayer *current_layer,
+ GimpContext *context,
+ GimpMergeType merge_type,
+ GimpProgress *progress,
+ GError **error);
+GimpLayer * gimp_image_merge_group_layer (GimpImage *image,
+ GimpGroupLayer *group);
+
+GimpLayer * gimp_image_flatten (GimpImage *image,
+ GimpContext *context,
+ GimpProgress *progress,
+ GError **error);
+
+GimpVectors * gimp_image_merge_visible_vectors (GimpImage *image,
+ GError **error);
+
+
+#endif /* __GIMP_IMAGE_MERGE_H__ */
diff --git a/app/core/gimpimage-metadata.c b/app/core/gimpimage-metadata.c
new file mode 100644
index 0000000..ae0774f
--- /dev/null
+++ b/app/core/gimpimage-metadata.c
@@ -0,0 +1,184 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <cairo.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpbase/gimpbase.h"
+#include "libgimpcolor/gimpcolor.h"
+
+#include "core-types.h"
+
+#include "gimpimage.h"
+#include "gimpimage-color-profile.h"
+#include "gimpimage-metadata.h"
+#include "gimpimage-private.h"
+#include "gimpimage-undo-push.h"
+
+
+/* public functions */
+
+
+GimpMetadata *
+gimp_image_get_metadata (GimpImage *image)
+{
+ GimpImagePrivate *private;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+
+ private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ return private->metadata;
+}
+
+void
+gimp_image_set_metadata (GimpImage *image,
+ GimpMetadata *metadata,
+ gboolean push_undo)
+{
+ GimpImagePrivate *private;
+
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+
+ private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ if (metadata != private->metadata)
+ {
+ if (push_undo)
+ gimp_image_undo_push_image_metadata (image, NULL);
+
+ g_set_object (&private->metadata, metadata);
+
+ if (private->metadata)
+ {
+ gimp_image_metadata_update_pixel_size (image);
+ gimp_image_metadata_update_bits_per_sample (image);
+ gimp_image_metadata_update_resolution (image);
+ gimp_image_metadata_update_colorspace (image);
+ }
+
+ g_object_notify (G_OBJECT (image), "metadata");
+ }
+}
+
+void
+gimp_image_metadata_update_pixel_size (GimpImage *image)
+{
+ GimpMetadata *metadata;
+
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+
+ metadata = gimp_image_get_metadata (image);
+
+ if (metadata)
+ {
+ gimp_metadata_set_pixel_size (metadata,
+ gimp_image_get_width (image),
+ gimp_image_get_height (image));
+ }
+}
+
+void
+gimp_image_metadata_update_bits_per_sample (GimpImage *image)
+{
+ GimpMetadata *metadata;
+
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+
+ metadata = gimp_image_get_metadata (image);
+
+ if (metadata)
+ {
+ switch (gimp_image_get_component_type (image))
+ {
+ case GIMP_COMPONENT_TYPE_U8:
+ gimp_metadata_set_bits_per_sample (metadata, 8);
+ break;
+
+ case GIMP_COMPONENT_TYPE_U16:
+ case GIMP_COMPONENT_TYPE_HALF:
+ gimp_metadata_set_bits_per_sample (metadata, 16);
+ break;
+
+ case GIMP_COMPONENT_TYPE_U32:
+ case GIMP_COMPONENT_TYPE_FLOAT:
+ gimp_metadata_set_bits_per_sample (metadata, 32);
+ break;
+
+ case GIMP_COMPONENT_TYPE_DOUBLE:
+ gimp_metadata_set_bits_per_sample (metadata, 64);
+ break;
+ }
+ }
+}
+
+void
+gimp_image_metadata_update_resolution (GimpImage *image)
+{
+ GimpMetadata *metadata;
+
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+
+ metadata = gimp_image_get_metadata (image);
+
+ if (metadata)
+ {
+ gdouble xres, yres;
+
+ gimp_image_get_resolution (image, &xres, &yres);
+ gimp_metadata_set_resolution (metadata, xres, yres,
+ gimp_image_get_unit (image));
+ }
+}
+
+void
+gimp_image_metadata_update_colorspace (GimpImage *image)
+{
+ GimpMetadata *metadata;
+
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+
+ metadata = gimp_image_get_metadata (image);
+
+ if (metadata)
+ {
+ /* See the discussions in issue #3532 and issue #301 */
+
+ GimpColorProfile *profile = gimp_image_get_color_profile (image);
+ GimpMetadataColorspace space = GIMP_METADATA_COLORSPACE_UNSPECIFIED;
+
+ if (profile)
+ {
+ static GimpColorProfile *adobe = NULL;
+
+ if (! adobe)
+ adobe = gimp_color_profile_new_rgb_adobe ();
+
+ if (gimp_color_profile_is_equal (profile, adobe))
+ space = GIMP_METADATA_COLORSPACE_ADOBERGB;
+ }
+ else
+ {
+ space = GIMP_METADATA_COLORSPACE_SRGB;
+ }
+
+ gimp_metadata_set_colorspace (metadata, space);
+ }
+}
diff --git a/app/core/gimpimage-metadata.h b/app/core/gimpimage-metadata.h
new file mode 100644
index 0000000..2a74be8
--- /dev/null
+++ b/app/core/gimpimage-metadata.h
@@ -0,0 +1,33 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_IMAGE_METADATA_H__
+#define __GIMP_IMAGE_METADATA_H__
+
+
+GimpMetadata * gimp_image_get_metadata (GimpImage *image);
+void gimp_image_set_metadata (GimpImage *image,
+ GimpMetadata *metadata,
+ gboolean push_undo);
+
+void gimp_image_metadata_update_pixel_size (GimpImage *image);
+void gimp_image_metadata_update_bits_per_sample (GimpImage *image);
+void gimp_image_metadata_update_resolution (GimpImage *image);
+void gimp_image_metadata_update_colorspace (GimpImage *image);
+
+
+#endif /* __GIMP_IMAGE_METADATA_H__ */
diff --git a/app/core/gimpimage-new.c b/app/core/gimpimage-new.c
new file mode 100644
index 0000000..4d0fa7b
--- /dev/null
+++ b/app/core/gimpimage-new.c
@@ -0,0 +1,393 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-1999 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <cairo.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpbase/gimpbase.h"
+#include "libgimpcolor/gimpcolor.h"
+#include "libgimpconfig/gimpconfig.h"
+
+#include "core-types.h"
+
+#include "config/gimpcoreconfig.h"
+
+#include "gegl/gimp-babl.h"
+
+#include "gimp.h"
+#include "gimpbuffer.h"
+#include "gimpchannel.h"
+#include "gimpcontext.h"
+#include "gimpdrawable-fill.h"
+#include "gimpimage.h"
+#include "gimpimage-color-profile.h"
+#include "gimpimage-colormap.h"
+#include "gimpimage-new.h"
+#include "gimpimage-undo.h"
+#include "gimplayer.h"
+#include "gimplayer-new.h"
+#include "gimptemplate.h"
+
+#include "gimp-intl.h"
+
+
+GimpTemplate *
+gimp_image_new_get_last_template (Gimp *gimp,
+ GimpImage *image)
+{
+ GimpTemplate *template;
+
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
+ g_return_val_if_fail (image == NULL || GIMP_IS_IMAGE (image), NULL);
+
+ template = gimp_template_new ("image new values");
+
+ if (image)
+ {
+ gimp_config_sync (G_OBJECT (gimp->config->default_image),
+ G_OBJECT (template), 0);
+ gimp_template_set_from_image (template, image);
+ }
+ else
+ {
+ gimp_config_sync (G_OBJECT (gimp->image_new_last_template),
+ G_OBJECT (template), 0);
+ }
+
+ return template;
+}
+
+void
+gimp_image_new_set_last_template (Gimp *gimp,
+ GimpTemplate *template)
+{
+ g_return_if_fail (GIMP_IS_GIMP (gimp));
+ g_return_if_fail (GIMP_IS_TEMPLATE (template));
+
+ gimp_config_sync (G_OBJECT (template),
+ G_OBJECT (gimp->image_new_last_template), 0);
+}
+
+GimpImage *
+gimp_image_new_from_template (Gimp *gimp,
+ GimpTemplate *template,
+ GimpContext *context)
+{
+ GimpImage *image;
+ GimpLayer *layer;
+ GimpColorProfile *profile;
+ gint width, height;
+ gboolean has_alpha;
+ const gchar *comment;
+
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
+ g_return_val_if_fail (GIMP_IS_TEMPLATE (template), NULL);
+ g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL);
+
+ image = gimp_create_image (gimp,
+ gimp_template_get_width (template),
+ gimp_template_get_height (template),
+ gimp_template_get_base_type (template),
+ gimp_template_get_precision (template),
+ FALSE);
+
+ gimp_image_undo_disable (image);
+
+ comment = gimp_template_get_comment (template);
+
+ if (comment)
+ {
+ GimpParasite *parasite;
+
+ parasite = gimp_parasite_new ("gimp-comment",
+ GIMP_PARASITE_PERSISTENT,
+ strlen (comment) + 1,
+ comment);
+ gimp_image_parasite_attach (image, parasite, FALSE);
+ gimp_parasite_free (parasite);
+ }
+
+ gimp_image_set_resolution (image,
+ gimp_template_get_resolution_x (template),
+ gimp_template_get_resolution_y (template));
+ gimp_image_set_unit (image, gimp_template_get_resolution_unit (template));
+
+ gimp_image_set_is_color_managed (image,
+ gimp_template_get_color_managed (template),
+ FALSE);
+ profile = gimp_template_get_color_profile (template);
+ gimp_image_set_color_profile (image, profile, NULL);
+ if (profile)
+ g_object_unref (profile);
+
+ width = gimp_image_get_width (image);
+ height = gimp_image_get_height (image);
+
+ if (gimp_template_get_fill_type (template) == GIMP_FILL_TRANSPARENT)
+ has_alpha = TRUE;
+ else
+ has_alpha = FALSE;
+
+ layer = gimp_layer_new (image, width, height,
+ gimp_image_get_layer_format (image, has_alpha),
+ _("Background"),
+ GIMP_OPACITY_OPAQUE,
+ gimp_image_get_default_new_layer_mode (image));
+
+ gimp_drawable_fill (GIMP_DRAWABLE (layer),
+ context, gimp_template_get_fill_type (template));
+
+ gimp_image_add_layer (image, layer, NULL, 0, FALSE);
+
+ gimp_image_undo_enable (image);
+ gimp_image_clean_all (image);
+
+ return image;
+}
+
+GimpImage *
+gimp_image_new_from_drawable (Gimp *gimp,
+ GimpDrawable *drawable)
+{
+ GimpItem *item;
+ GimpImage *image;
+ GimpImage *new_image;
+ GimpLayer *new_layer;
+ GType new_type;
+ gint off_x, off_y;
+ GimpImageBaseType type;
+ gdouble xres;
+ gdouble yres;
+ GimpColorProfile *profile;
+
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), NULL);
+
+ item = GIMP_ITEM (drawable);
+ image = gimp_item_get_image (item);
+
+ type = gimp_drawable_get_base_type (drawable);
+
+ new_image = gimp_create_image (gimp,
+ gimp_item_get_width (item),
+ gimp_item_get_height (item),
+ type,
+ gimp_drawable_get_precision (drawable),
+ TRUE);
+ gimp_image_undo_disable (new_image);
+
+ if (type == GIMP_INDEXED)
+ gimp_image_set_colormap (new_image,
+ gimp_image_get_colormap (image),
+ gimp_image_get_colormap_size (image),
+ FALSE);
+
+ gimp_image_get_resolution (image, &xres, &yres);
+ gimp_image_set_resolution (new_image, xres, yres);
+ gimp_image_set_unit (new_image, gimp_image_get_unit (image));
+
+ gimp_image_set_is_color_managed (new_image,
+ gimp_image_get_is_color_managed (image),
+ FALSE);
+ profile = gimp_color_managed_get_color_profile (GIMP_COLOR_MANAGED (drawable));
+ gimp_image_set_color_profile (new_image, profile, NULL);
+
+ if (GIMP_IS_LAYER (drawable))
+ new_type = G_TYPE_FROM_INSTANCE (drawable);
+ else
+ new_type = GIMP_TYPE_LAYER;
+
+ new_layer = GIMP_LAYER (gimp_item_convert (GIMP_ITEM (drawable),
+ new_image, new_type));
+
+ gimp_object_set_name (GIMP_OBJECT (new_layer),
+ gimp_object_get_name (drawable));
+
+ gimp_item_get_offset (GIMP_ITEM (new_layer), &off_x, &off_y);
+ gimp_item_translate (GIMP_ITEM (new_layer), -off_x, -off_y, FALSE);
+ gimp_item_set_visible (GIMP_ITEM (new_layer), TRUE, FALSE);
+ gimp_item_set_linked (GIMP_ITEM (new_layer), FALSE, FALSE);
+ gimp_layer_set_mode (new_layer,
+ gimp_image_get_default_new_layer_mode (new_image),
+ FALSE);
+ gimp_layer_set_opacity (new_layer, GIMP_OPACITY_OPAQUE, FALSE);
+ if (gimp_layer_can_lock_alpha (new_layer))
+ gimp_layer_set_lock_alpha (new_layer, FALSE, FALSE);
+
+ gimp_image_add_layer (new_image, new_layer, NULL, 0, TRUE);
+
+ gimp_image_undo_enable (new_image);
+
+ return new_image;
+}
+
+GimpImage *
+gimp_image_new_from_component (Gimp *gimp,
+ GimpImage *image,
+ GimpChannelType component)
+{
+ GimpImage *new_image;
+ GimpChannel *channel;
+ GimpLayer *layer;
+ const gchar *desc;
+ gdouble xres;
+ gdouble yres;
+
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+
+ new_image = gimp_create_image (gimp,
+ gimp_image_get_width (image),
+ gimp_image_get_height (image),
+ GIMP_GRAY,
+ gimp_image_get_precision (image),
+ TRUE);
+
+ gimp_image_undo_disable (new_image);
+
+ gimp_image_get_resolution (image, &xres, &yres);
+ gimp_image_set_resolution (new_image, xres, yres);
+ gimp_image_set_unit (new_image, gimp_image_get_unit (image));
+
+ channel = gimp_channel_new_from_component (image, component, NULL, NULL);
+
+ layer = GIMP_LAYER (gimp_item_convert (GIMP_ITEM (channel),
+ new_image, GIMP_TYPE_LAYER));
+ g_object_unref (channel);
+
+ gimp_enum_get_value (GIMP_TYPE_CHANNEL_TYPE, component,
+ NULL, NULL, &desc, NULL);
+ gimp_object_take_name (GIMP_OBJECT (layer),
+ g_strdup_printf (_("%s Channel Copy"), desc));
+
+ gimp_image_add_layer (new_image, layer, NULL, 0, TRUE);
+
+ gimp_image_undo_enable (new_image);
+
+ return new_image;
+}
+
+GimpImage *
+gimp_image_new_from_buffer (Gimp *gimp,
+ GimpBuffer *buffer)
+{
+ GimpImage *image;
+ GimpLayer *layer;
+ const Babl *format;
+ gboolean has_alpha;
+ gdouble res_x;
+ gdouble res_y;
+ GimpColorProfile *profile;
+
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
+ g_return_val_if_fail (GIMP_IS_BUFFER (buffer), NULL);
+
+ format = gimp_buffer_get_format (buffer);
+ has_alpha = babl_format_has_alpha (format);
+
+ image = gimp_create_image (gimp,
+ gimp_buffer_get_width (buffer),
+ gimp_buffer_get_height (buffer),
+ gimp_babl_format_get_base_type (format),
+ gimp_babl_format_get_precision (format),
+ TRUE);
+ gimp_image_undo_disable (image);
+
+ if (gimp_buffer_get_resolution (buffer, &res_x, &res_y))
+ {
+ gimp_image_set_resolution (image, res_x, res_y);
+ gimp_image_set_unit (image, gimp_buffer_get_unit (buffer));
+ }
+
+ profile = gimp_buffer_get_color_profile (buffer);
+ gimp_image_set_color_profile (image, profile, NULL);
+
+ layer = gimp_layer_new_from_buffer (buffer, image,
+ gimp_image_get_layer_format (image,
+ has_alpha),
+ _("Pasted Layer"),
+ GIMP_OPACITY_OPAQUE,
+ gimp_image_get_default_new_layer_mode (image));
+
+ gimp_image_add_layer (image, layer, NULL, 0, TRUE);
+
+ gimp_image_undo_enable (image);
+
+ return image;
+}
+
+GimpImage *
+gimp_image_new_from_pixbuf (Gimp *gimp,
+ GdkPixbuf *pixbuf,
+ const gchar *layer_name)
+{
+ GimpImage *new_image;
+ GimpLayer *layer;
+ GimpImageBaseType base_type;
+ gboolean has_alpha = FALSE;
+ guint8 *icc_data;
+ gsize icc_len;
+
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
+ g_return_val_if_fail (GDK_IS_PIXBUF (pixbuf), NULL);
+
+ switch (gdk_pixbuf_get_n_channels (pixbuf))
+ {
+ case 2: has_alpha = TRUE;
+ case 1: base_type = GIMP_GRAY;
+ break;
+
+ case 4: has_alpha = TRUE;
+ case 3: base_type = GIMP_RGB;
+ break;
+
+ default:
+ g_return_val_if_reached (NULL);
+ }
+
+ new_image = gimp_create_image (gimp,
+ gdk_pixbuf_get_width (pixbuf),
+ gdk_pixbuf_get_height (pixbuf),
+ base_type,
+ GIMP_PRECISION_U8_GAMMA,
+ FALSE);
+
+ gimp_image_undo_disable (new_image);
+
+ icc_data = gimp_pixbuf_get_icc_profile (pixbuf, &icc_len);
+ if (icc_data)
+ {
+ gimp_image_set_icc_profile (new_image, icc_data, icc_len, NULL);
+ g_free (icc_data);
+ }
+
+ layer = gimp_layer_new_from_pixbuf (pixbuf, new_image,
+ gimp_image_get_layer_format (new_image,
+ has_alpha),
+ layer_name,
+ GIMP_OPACITY_OPAQUE,
+ gimp_image_get_default_new_layer_mode (new_image));
+
+ gimp_image_add_layer (new_image, layer, NULL, 0, TRUE);
+
+ gimp_image_undo_enable (new_image);
+
+ return new_image;
+}
diff --git a/app/core/gimpimage-new.h b/app/core/gimpimage-new.h
new file mode 100644
index 0000000..6efd3cc
--- /dev/null
+++ b/app/core/gimpimage-new.h
@@ -0,0 +1,42 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-1999 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_IMAGE_NEW_H__
+#define __GIMP_IMAGE_NEW_H__
+
+
+GimpTemplate * gimp_image_new_get_last_template (Gimp *gimp,
+ GimpImage *image);
+void gimp_image_new_set_last_template (Gimp *gimp,
+ GimpTemplate *template);
+
+GimpImage * gimp_image_new_from_template (Gimp *gimp,
+ GimpTemplate *template,
+ GimpContext *context);
+GimpImage * gimp_image_new_from_drawable (Gimp *gimp,
+ GimpDrawable *drawable);
+GimpImage * gimp_image_new_from_component (Gimp *gimp,
+ GimpImage *image,
+ GimpChannelType component);
+GimpImage * gimp_image_new_from_buffer (Gimp *gimp,
+ GimpBuffer *buffer);
+GimpImage * gimp_image_new_from_pixbuf (Gimp *gimp,
+ GdkPixbuf *pixbuf,
+ const gchar *layer_name);
+
+
+#endif /* __GIMP_IMAGE_NEW__ */
diff --git a/app/core/gimpimage-pick-color.c b/app/core/gimpimage-pick-color.c
new file mode 100644
index 0000000..f8168cb
--- /dev/null
+++ b/app/core/gimpimage-pick-color.c
@@ -0,0 +1,147 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-2001 Spencer Kimball, Peter Mattis, and others
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpmath/gimpmath.h"
+
+#include "core-types.h"
+
+#include "gegl/gimp-gegl-loops.h"
+
+#include "gimpchannel.h"
+#include "gimpdrawable.h"
+#include "gimpimage.h"
+#include "gimpimage-pick-color.h"
+#include "gimplayer.h"
+#include "gimppickable.h"
+
+
+gboolean
+gimp_image_pick_color (GimpImage *image,
+ GimpDrawable *drawable,
+ gint x,
+ gint y,
+ gboolean show_all,
+ gboolean sample_merged,
+ gboolean sample_average,
+ gdouble average_radius,
+ const Babl **sample_format,
+ gpointer pixel,
+ GimpRGB *color)
+{
+ GimpPickable *pickable;
+ gboolean result;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);
+ g_return_val_if_fail (drawable == NULL || GIMP_IS_DRAWABLE (drawable), FALSE);
+ g_return_val_if_fail (drawable == NULL ||
+ gimp_item_get_image (GIMP_ITEM (drawable)) == image,
+ FALSE);
+
+ if (sample_merged && drawable)
+ {
+ if ((GIMP_IS_LAYER (drawable) &&
+ gimp_image_get_n_layers (image) == 1) ||
+ (GIMP_IS_CHANNEL (drawable) &&
+ gimp_image_get_n_channels (image) == 1))
+ {
+ /* Let's add a special exception when an image has only one
+ * layer. This was useful in particular for indexed image as
+ * it allows to pick the right index value even when "Sample
+ * merged" is checked. There are more possible exceptions, but
+ * we can't just take them all in considerations unless we
+ * want to make code extra-complicated).
+ * See #3041.
+ */
+ sample_merged = FALSE;
+ }
+ }
+
+ if (! sample_merged)
+ {
+ if (! drawable)
+ drawable = gimp_image_get_active_drawable (image);
+
+ if (! drawable)
+ return FALSE;
+ }
+
+ if (sample_merged)
+ {
+ if (! show_all)
+ pickable = GIMP_PICKABLE (image);
+ else
+ pickable = GIMP_PICKABLE (gimp_image_get_projection (image));
+ }
+ else
+ {
+ gint off_x, off_y;
+
+ gimp_item_get_offset (GIMP_ITEM (drawable), &off_x, &off_y);
+ x -= off_x;
+ y -= off_y;
+
+ pickable = GIMP_PICKABLE (drawable);
+ }
+
+ /* Do *not* call gimp_pickable_flush() here because it's too expensive
+ * to call it unconditionally each time e.g. the cursor view is updated.
+ * Instead, call gimp_pickable_flush() in the callers if needed.
+ */
+
+ if (sample_format)
+ *sample_format = gimp_pickable_get_format (pickable);
+
+ result = gimp_pickable_pick_color (pickable, x, y,
+ sample_average &&
+ ! (show_all && sample_merged),
+ average_radius,
+ pixel, color);
+
+ if (show_all && sample_merged)
+ {
+ const Babl *format = babl_format ("RaGaBaA double");
+ gdouble sample[4] = {};
+
+ if (! result)
+ memset (pixel, 0, babl_format_get_bytes_per_pixel (*sample_format));
+
+ if (sample_average)
+ {
+ GeglBuffer *buffer = gimp_pickable_get_buffer (pickable);
+ gint radius = floor (average_radius);
+
+ gimp_gegl_average_color (buffer,
+ GEGL_RECTANGLE (x - radius,
+ y - radius,
+ 2 * radius + 1,
+ 2 * radius + 1),
+ FALSE, GEGL_ABYSS_NONE, format, sample);
+ }
+
+ if (! result || sample_average)
+ gimp_pickable_pixel_to_srgb (pickable, format, sample, color);
+
+ result = TRUE;
+ }
+
+ return result;
+}
diff --git a/app/core/gimpimage-pick-color.h b/app/core/gimpimage-pick-color.h
new file mode 100644
index 0000000..1ca2947
--- /dev/null
+++ b/app/core/gimpimage-pick-color.h
@@ -0,0 +1,35 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_IMAGE_PICK_COLOR_H__
+#define __GIMP_IMAGE_PICK_COLOR_H__
+
+
+gboolean gimp_image_pick_color (GimpImage *image,
+ GimpDrawable *drawable,
+ gint x,
+ gint y,
+ gboolean show_all,
+ gboolean sample_merged,
+ gboolean sample_average,
+ gdouble average_radius,
+ const Babl **sample_format,
+ gpointer pixel,
+ GimpRGB *color);
+
+
+#endif /* __GIMP_IMAGE_PICK_COLOR_H__ */
diff --git a/app/core/gimpimage-pick-item.c b/app/core/gimpimage-pick-item.c
new file mode 100644
index 0000000..b2257f3
--- /dev/null
+++ b/app/core/gimpimage-pick-item.c
@@ -0,0 +1,381 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpmath/gimpmath.h"
+
+#include "core-types.h"
+
+#include "gimpgrouplayer.h"
+#include "gimpguide.h"
+#include "gimpimage.h"
+#include "gimpimage-pick-item.h"
+#include "gimpimage-private.h"
+#include "gimppickable.h"
+#include "gimpsamplepoint.h"
+
+#include "text/gimptextlayer.h"
+
+#include "vectors/gimpstroke.h"
+#include "vectors/gimpvectors.h"
+
+
+GimpLayer *
+gimp_image_pick_layer (GimpImage *image,
+ gint x,
+ gint y,
+ GimpLayer *previously_picked)
+{
+ GList *all_layers;
+ GList *list;
+ gint off_x, off_y;
+ gint tries = 1;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+
+ all_layers = gimp_image_get_layer_list (image);
+
+ if (previously_picked)
+ {
+ gimp_item_get_offset (GIMP_ITEM (previously_picked), &off_x, &off_y);
+ if (gimp_pickable_get_opacity_at (GIMP_PICKABLE (previously_picked),
+ x - off_x, y - off_y) <= 0.25)
+ previously_picked = NULL;
+ else
+ tries++;
+ }
+
+ while (tries)
+ {
+ for (list = all_layers; list; list = g_list_next (list))
+ {
+ GimpLayer *layer = list->data;
+
+ if (previously_picked)
+ {
+ /* Take the first layer with a pixel at given coordinates
+ * after the previously picked one.
+ */
+ if (layer == previously_picked)
+ previously_picked = NULL;
+ continue;
+ }
+
+ gimp_item_get_offset (GIMP_ITEM (layer), &off_x, &off_y);
+
+ if (gimp_pickable_get_opacity_at (GIMP_PICKABLE (layer),
+ x - off_x, y - off_y) > 0.25)
+ {
+ g_list_free (all_layers);
+
+ return layer;
+ }
+ }
+ tries--;
+ }
+
+ g_list_free (all_layers);
+
+ return NULL;
+}
+
+GimpLayer *
+gimp_image_pick_layer_by_bounds (GimpImage *image,
+ gint x,
+ gint y)
+{
+ GList *all_layers;
+ GList *list;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+
+ all_layers = gimp_image_get_layer_list (image);
+
+ for (list = all_layers; list; list = g_list_next (list))
+ {
+ GimpLayer *layer = list->data;
+
+ if (gimp_item_is_visible (GIMP_ITEM (layer)))
+ {
+ gint off_x, off_y;
+ gint width, height;
+
+ gimp_item_get_offset (GIMP_ITEM (layer), &off_x, &off_y);
+ width = gimp_item_get_width (GIMP_ITEM (layer));
+ height = gimp_item_get_height (GIMP_ITEM (layer));
+
+ if (x >= off_x &&
+ y >= off_y &&
+ x < off_x + width &&
+ y < off_y + height)
+ {
+ g_list_free (all_layers);
+
+ return layer;
+ }
+ }
+ }
+
+ g_list_free (all_layers);
+
+ return NULL;
+}
+
+GimpTextLayer *
+gimp_image_pick_text_layer (GimpImage *image,
+ gint x,
+ gint y)
+{
+ GList *all_layers;
+ GList *list;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+
+ all_layers = gimp_image_get_layer_list (image);
+
+ for (list = all_layers; list; list = g_list_next (list))
+ {
+ GimpLayer *layer = list->data;
+ gint off_x, off_y;
+
+ gimp_item_get_offset (GIMP_ITEM (layer), &off_x, &off_y);
+
+ if (GIMP_IS_TEXT_LAYER (layer) &&
+ x >= off_x &&
+ y >= off_y &&
+ x < off_x + gimp_item_get_width (GIMP_ITEM (layer)) &&
+ y < off_y + gimp_item_get_height (GIMP_ITEM (layer)) &&
+ gimp_item_is_visible (GIMP_ITEM (layer)))
+ {
+ g_list_free (all_layers);
+
+ return GIMP_TEXT_LAYER (layer);
+ }
+ else if (gimp_pickable_get_opacity_at (GIMP_PICKABLE (layer),
+ x - off_x, y - off_y) > 0.25)
+ {
+ /* a normal layer covers any possible text layers below,
+ * bail out
+ */
+
+ break;
+ }
+ }
+
+ g_list_free (all_layers);
+
+ return NULL;
+}
+
+GimpVectors *
+gimp_image_pick_vectors (GimpImage *image,
+ gdouble x,
+ gdouble y,
+ gdouble epsilon_x,
+ gdouble epsilon_y)
+{
+ GimpVectors *ret = NULL;
+ GList *all_vectors;
+ GList *list;
+ gdouble mindist = G_MAXDOUBLE;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+
+ all_vectors = gimp_image_get_vectors_list (image);
+
+ for (list = all_vectors; list; list = g_list_next (list))
+ {
+ GimpVectors *vectors = list->data;
+
+ if (gimp_item_is_visible (GIMP_ITEM (vectors)))
+ {
+ GimpStroke *stroke = NULL;
+ GimpCoords coords = GIMP_COORDS_DEFAULT_VALUES;
+
+ while ((stroke = gimp_vectors_stroke_get_next (vectors, stroke)))
+ {
+ gdouble dist;
+
+ coords.x = x;
+ coords.y = y;
+
+ dist = gimp_stroke_nearest_point_get (stroke, &coords, 1.0,
+ NULL, NULL, NULL, NULL);
+
+ if (dist >= 0.0 &&
+ dist < MIN (epsilon_y, mindist))
+ {
+ mindist = dist;
+ ret = vectors;
+ }
+ }
+ }
+ }
+
+ g_list_free (all_vectors);
+
+ return ret;
+}
+
+static GimpGuide *
+gimp_image_pick_guide_internal (GimpImage *image,
+ gdouble x,
+ gdouble y,
+ gdouble epsilon_x,
+ gdouble epsilon_y,
+ GimpOrientationType orientation)
+{
+ GList *list;
+ GimpGuide *ret = NULL;
+ gdouble mindist = G_MAXDOUBLE;
+
+ for (list = GIMP_IMAGE_GET_PRIVATE (image)->guides;
+ list;
+ list = g_list_next (list))
+ {
+ GimpGuide *guide = list->data;
+ gint position = gimp_guide_get_position (guide);
+ gdouble dist;
+
+ switch (gimp_guide_get_orientation (guide))
+ {
+ case GIMP_ORIENTATION_HORIZONTAL:
+ if (orientation == GIMP_ORIENTATION_HORIZONTAL ||
+ orientation == GIMP_ORIENTATION_UNKNOWN)
+ {
+ dist = ABS (position - y);
+ if (dist < MIN (epsilon_y, mindist))
+ {
+ mindist = dist;
+ ret = guide;
+ }
+ }
+ break;
+
+ /* mindist always is in vertical resolution to make it comparable */
+ case GIMP_ORIENTATION_VERTICAL:
+ if (orientation == GIMP_ORIENTATION_VERTICAL ||
+ orientation == GIMP_ORIENTATION_UNKNOWN)
+ {
+ dist = ABS (position - x);
+ if (dist < MIN (epsilon_x, mindist / epsilon_y * epsilon_x))
+ {
+ mindist = dist * epsilon_y / epsilon_x;
+ ret = guide;
+ }
+ }
+ break;
+
+ default:
+ continue;
+ }
+ }
+
+ return ret;
+}
+
+GimpGuide *
+gimp_image_pick_guide (GimpImage *image,
+ gdouble x,
+ gdouble y,
+ gdouble epsilon_x,
+ gdouble epsilon_y)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+ g_return_val_if_fail (epsilon_x > 0 && epsilon_y > 0, NULL);
+
+ return gimp_image_pick_guide_internal (image, x, y, epsilon_x, epsilon_y,
+ GIMP_ORIENTATION_UNKNOWN);
+}
+
+GList *
+gimp_image_pick_guides (GimpImage *image,
+ gdouble x,
+ gdouble y,
+ gdouble epsilon_x,
+ gdouble epsilon_y)
+{
+ GimpGuide *guide;
+ GList *result = NULL;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+ g_return_val_if_fail (epsilon_x > 0 && epsilon_y > 0, NULL);
+
+ guide = gimp_image_pick_guide_internal (image, x, y, epsilon_x, epsilon_y,
+ GIMP_ORIENTATION_HORIZONTAL);
+
+ if (guide)
+ result = g_list_append (result, guide);
+
+ guide = gimp_image_pick_guide_internal (image, x, y, epsilon_x, epsilon_y,
+ GIMP_ORIENTATION_VERTICAL);
+
+ if (guide)
+ result = g_list_append (result, guide);
+
+ return result;
+}
+
+GimpSamplePoint *
+gimp_image_pick_sample_point (GimpImage *image,
+ gdouble x,
+ gdouble y,
+ gdouble epsilon_x,
+ gdouble epsilon_y)
+{
+ GList *list;
+ GimpSamplePoint *ret = NULL;
+ gdouble mindist = G_MAXDOUBLE;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+ g_return_val_if_fail (epsilon_x > 0 && epsilon_y > 0, NULL);
+
+ if (x < 0 || x >= gimp_image_get_width (image) ||
+ y < 0 || y >= gimp_image_get_height (image))
+ {
+ return NULL;
+ }
+
+ for (list = GIMP_IMAGE_GET_PRIVATE (image)->sample_points;
+ list;
+ list = g_list_next (list))
+ {
+ GimpSamplePoint *sample_point = list->data;
+ gint sp_x;
+ gint sp_y;
+ gdouble dist;
+
+ gimp_sample_point_get_position (sample_point, &sp_x, &sp_y);
+
+ if (sp_x < 0 || sp_y < 0)
+ continue;
+
+ dist = hypot ((sp_x + 0.5) - x,
+ (sp_y + 0.5) - y);
+ if (dist < MIN (epsilon_y, mindist))
+ {
+ mindist = dist;
+ ret = sample_point;
+ }
+ }
+
+ return ret;
+}
diff --git a/app/core/gimpimage-pick-item.h b/app/core/gimpimage-pick-item.h
new file mode 100644
index 0000000..46da4ef
--- /dev/null
+++ b/app/core/gimpimage-pick-item.h
@@ -0,0 +1,57 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattisbvf
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_IMAGE_PICK_ITEM_H__
+#define __GIMP_IMAGE_PICK_ITEM_H__
+
+
+GimpLayer * gimp_image_pick_layer (GimpImage *image,
+ gint x,
+ gint y,
+ GimpLayer *previously_picked);
+GimpLayer * gimp_image_pick_layer_by_bounds (GimpImage *image,
+ gint x,
+ gint y);
+GimpTextLayer * gimp_image_pick_text_layer (GimpImage *image,
+ gint x,
+ gint y);
+
+GimpVectors * gimp_image_pick_vectors (GimpImage *image,
+ gdouble x,
+ gdouble y,
+ gdouble epsilon_x,
+ gdouble epsilon_y);
+
+GimpGuide * gimp_image_pick_guide (GimpImage *image,
+ gdouble x,
+ gdouble y,
+ gdouble epsilon_x,
+ gdouble epsilon_y);
+GList * gimp_image_pick_guides (GimpImage *image,
+ gdouble x,
+ gdouble y,
+ gdouble epsilon_x,
+ gdouble epsilon_y);
+
+GimpSamplePoint * gimp_image_pick_sample_point (GimpImage *image,
+ gdouble x,
+ gdouble y,
+ gdouble epsilon_x,
+ gdouble epsilon_y);
+
+
+#endif /* __GIMP_IMAGE_PICK_ITEM_H__ */
diff --git a/app/core/gimpimage-preview.c b/app/core/gimpimage-preview.c
new file mode 100644
index 0000000..e28865e
--- /dev/null
+++ b/app/core/gimpimage-preview.c
@@ -0,0 +1,214 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <cairo.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpcolor/gimpcolor.h"
+
+#include "core-types.h"
+
+#include "gegl/gimp-babl.h"
+#include "gegl/gimp-gegl-loops.h"
+
+#include "gimpimage.h"
+#include "gimpimage-color-profile.h"
+#include "gimpimage-preview.h"
+#include "gimppickable.h"
+#include "gimpprojectable.h"
+#include "gimpprojection.h"
+#include "gimptempbuf.h"
+
+
+const Babl *
+gimp_image_get_preview_format (GimpImage *image)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+
+ switch (gimp_image_get_base_type (image))
+ {
+ case GIMP_RGB:
+ case GIMP_GRAY:
+ return gimp_babl_format_change_component_type (
+ gimp_projectable_get_format (GIMP_PROJECTABLE (image)),
+ GIMP_COMPONENT_TYPE_U8);
+
+ case GIMP_INDEXED:
+ return babl_format ("R'G'B'A u8");
+ }
+
+ g_return_val_if_reached (NULL);
+}
+
+void
+gimp_image_get_preview_size (GimpViewable *viewable,
+ gint size,
+ gboolean is_popup,
+ gboolean dot_for_dot,
+ gint *width,
+ gint *height)
+{
+ GimpImage *image = GIMP_IMAGE (viewable);
+ gdouble xres;
+ gdouble yres;
+
+ gimp_image_get_resolution (image, &xres, &yres);
+
+ gimp_viewable_calc_preview_size (gimp_image_get_width (image),
+ gimp_image_get_height (image),
+ size,
+ size,
+ dot_for_dot,
+ xres,
+ yres,
+ width,
+ height,
+ NULL);
+}
+
+gboolean
+gimp_image_get_popup_size (GimpViewable *viewable,
+ gint width,
+ gint height,
+ gboolean dot_for_dot,
+ gint *popup_width,
+ gint *popup_height)
+{
+ GimpImage *image = GIMP_IMAGE (viewable);
+
+ if (gimp_image_get_width (image) > width ||
+ gimp_image_get_height (image) > height)
+ {
+ gboolean scaling_up;
+
+ gimp_viewable_calc_preview_size (gimp_image_get_width (image),
+ gimp_image_get_height (image),
+ width * 2,
+ height * 2,
+ dot_for_dot, 1.0, 1.0,
+ popup_width,
+ popup_height,
+ &scaling_up);
+
+ if (scaling_up)
+ {
+ *popup_width = gimp_image_get_width (image);
+ *popup_height = gimp_image_get_height (image);
+ }
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+GimpTempBuf *
+gimp_image_get_new_preview (GimpViewable *viewable,
+ GimpContext *context,
+ gint width,
+ gint height)
+{
+ GimpImage *image = GIMP_IMAGE (viewable);
+ const Babl *format;
+ GimpTempBuf *buf;
+ gdouble scale_x;
+ gdouble scale_y;
+
+ scale_x = (gdouble) width / (gdouble) gimp_image_get_width (image);
+ scale_y = (gdouble) height / (gdouble) gimp_image_get_height (image);
+
+ format = gimp_image_get_preview_format (image);
+
+ buf = gimp_temp_buf_new (width, height, format);
+
+ gegl_buffer_get (gimp_pickable_get_buffer (GIMP_PICKABLE (image)),
+ GEGL_RECTANGLE (0, 0, width, height),
+ MIN (scale_x, scale_y),
+ gimp_temp_buf_get_format (buf),
+ gimp_temp_buf_get_data (buf),
+ GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_CLAMP);
+
+ return buf;
+}
+
+GdkPixbuf *
+gimp_image_get_new_pixbuf (GimpViewable *viewable,
+ GimpContext *context,
+ gint width,
+ gint height)
+{
+ GimpImage *image = GIMP_IMAGE (viewable);
+ GdkPixbuf *pixbuf;
+ gdouble scale_x;
+ gdouble scale_y;
+ GimpColorTransform *transform;
+
+ scale_x = (gdouble) width / (gdouble) gimp_image_get_width (image);
+ scale_y = (gdouble) height / (gdouble) gimp_image_get_height (image);
+
+ pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8,
+ width, height);
+
+ transform = gimp_image_get_color_transform_to_srgb_u8 (image);
+
+ if (transform)
+ {
+ GimpTempBuf *temp_buf;
+ GeglBuffer *src_buf;
+ GeglBuffer *dest_buf;
+
+ temp_buf = gimp_temp_buf_new (width, height,
+ gimp_pickable_get_format (GIMP_PICKABLE (image)));
+
+ gegl_buffer_get (gimp_pickable_get_buffer (GIMP_PICKABLE (image)),
+ GEGL_RECTANGLE (0, 0, width, height),
+ MIN (scale_x, scale_y),
+ gimp_temp_buf_get_format (temp_buf),
+ gimp_temp_buf_get_data (temp_buf),
+ GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_CLAMP);
+
+ src_buf = gimp_temp_buf_create_buffer (temp_buf);
+ dest_buf = gimp_pixbuf_create_buffer (pixbuf);
+
+ gimp_temp_buf_unref (temp_buf);
+
+ gimp_color_transform_process_buffer (transform,
+ src_buf,
+ GEGL_RECTANGLE (0, 0,
+ width, height),
+ dest_buf,
+ GEGL_RECTANGLE (0, 0, 0, 0));
+
+ g_object_unref (src_buf);
+ g_object_unref (dest_buf);
+ }
+ else
+ {
+ gegl_buffer_get (gimp_pickable_get_buffer (GIMP_PICKABLE (image)),
+ GEGL_RECTANGLE (0, 0, width, height),
+ MIN (scale_x, scale_y),
+ gimp_pixbuf_get_format (pixbuf),
+ gdk_pixbuf_get_pixels (pixbuf),
+ gdk_pixbuf_get_rowstride (pixbuf),
+ GEGL_ABYSS_CLAMP);
+ }
+
+ return pixbuf;
+}
diff --git a/app/core/gimpimage-preview.h b/app/core/gimpimage-preview.h
new file mode 100644
index 0000000..f8b22c0
--- /dev/null
+++ b/app/core/gimpimage-preview.h
@@ -0,0 +1,51 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_IMAGE_PREVIEW_H__
+#define __GIMP_IMAGE_PREVIEW_H__
+
+
+const Babl * gimp_image_get_preview_format (GimpImage *image);
+
+
+/*
+ * virtual functions of GimpImage -- don't call directly
+ */
+
+void gimp_image_get_preview_size (GimpViewable *viewable,
+ gint size,
+ gboolean is_popup,
+ gboolean dot_for_dot,
+ gint *width,
+ gint *height);
+gboolean gimp_image_get_popup_size (GimpViewable *viewable,
+ gint width,
+ gint height,
+ gboolean dot_for_dot,
+ gint *popup_width,
+ gint *popup_height);
+GimpTempBuf * gimp_image_get_new_preview (GimpViewable *viewable,
+ GimpContext *context,
+ gint width,
+ gint height);
+GdkPixbuf * gimp_image_get_new_pixbuf (GimpViewable *viewable,
+ GimpContext *context,
+ gint width,
+ gint height);
+
+
+#endif /* __GIMP_IMAGE_PREVIEW_H__ */
diff --git a/app/core/gimpimage-private.h b/app/core/gimpimage-private.h
new file mode 100644
index 0000000..77687f0
--- /dev/null
+++ b/app/core/gimpimage-private.h
@@ -0,0 +1,149 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_IMAGE_PRIVATE_H__
+#define __GIMP_IMAGE_PRIVATE_H__
+
+
+typedef struct _GimpImageFlushAccumulator GimpImageFlushAccumulator;
+
+struct _GimpImageFlushAccumulator
+{
+ gboolean alpha_changed;
+ gboolean mask_changed;
+ gboolean floating_selection_changed;
+ gboolean preview_invalidated;
+};
+
+
+struct _GimpImagePrivate
+{
+ gint ID; /* provides a unique ID */
+
+ GimpPlugInProcedure *load_proc; /* procedure used for loading */
+ GimpPlugInProcedure *save_proc; /* last save procedure used */
+ GimpPlugInProcedure *export_proc; /* last export procedure used */
+
+ gchar *display_name; /* display basename */
+ gchar *display_path; /* display full path */
+ gint width; /* width in pixels */
+ gint height; /* height in pixels */
+ gdouble xresolution; /* image x-res, in dpi */
+ gdouble yresolution; /* image y-res, in dpi */
+ GimpUnit resolution_unit; /* resolution unit */
+ gboolean resolution_set; /* resolution explicitly set */
+ GimpImageBaseType base_type; /* base gimp_image type */
+ GimpPrecision precision; /* image's precision */
+ GimpLayerMode new_layer_mode; /* default mode of new layers */
+
+ gint show_all; /* render full image content */
+ GeglRectangle bounding_box; /* image content bounding box */
+ gint bounding_box_freeze_count;
+ gboolean bounding_box_update_pending;
+ GeglBuffer *pickable_buffer;
+
+ guchar *colormap; /* colormap (for indexed) */
+ gint n_colors; /* # of colors (for indexed) */
+ GimpPalette *palette; /* palette of colormap */
+ const Babl *babl_palette_rgb; /* palette's RGB Babl format */
+ const Babl *babl_palette_rgba; /* palette's RGBA Babl format */
+
+ gboolean is_color_managed; /* is this image color managed */
+ GimpColorProfile *color_profile; /* image's color profile */
+ gboolean converting; /* color model or profile in middle of conversion? */
+
+ /* Cached color transforms: from layer to sRGB u8 and double, and back */
+ gboolean color_transforms_created;
+ GimpColorTransform *transform_to_srgb_u8;
+ GimpColorTransform *transform_from_srgb_u8;
+ GimpColorTransform *transform_to_srgb_double;
+ GimpColorTransform *transform_from_srgb_double;
+
+ GimpMetadata *metadata; /* image's metadata */
+
+ GFile *file; /* the image's XCF file */
+ GFile *imported_file; /* the image's source file */
+ GFile *exported_file; /* the image's export file */
+ GFile *save_a_copy_file; /* the image's save-a-copy file */
+ GFile *untitled_file; /* a file saying "Untitled" */
+
+ gboolean xcf_compression; /* XCF compression enabled? */
+
+ gint dirty; /* dirty flag -- # of ops */
+ gint64 dirty_time; /* time when image became dirty */
+ gint export_dirty; /* 'dirty' but for export */
+
+ gint undo_freeze_count; /* counts the _freeze's */
+
+ gint instance_count; /* number of instances */
+ gint disp_count; /* number of displays */
+
+ GimpTattoo tattoo_state; /* the last used tattoo */
+
+ GimpProjection *projection; /* projection layers & channels */
+ GeglNode *graph; /* GEGL projection graph */
+ GeglNode *visible_mask; /* component visibility node */
+
+ GList *symmetries; /* Painting symmetries */
+ GimpSymmetry *active_symmetry; /* Active symmetry */
+
+ GList *guides; /* guides */
+ GimpGrid *grid; /* grid */
+ GList *sample_points; /* color sample points */
+
+ /* Layer/Channel attributes */
+ GimpItemTree *layers; /* the tree of layers */
+ GimpItemTree *channels; /* the tree of masks */
+ GimpItemTree *vectors; /* the tree of vectors */
+ GSList *layer_stack; /* the layers in MRU order */
+
+ GQuark layer_offset_x_handler;
+ GQuark layer_offset_y_handler;
+ GQuark layer_bounding_box_handler;
+ GQuark layer_alpha_handler;
+ GQuark channel_name_changed_handler;
+ GQuark channel_color_changed_handler;
+
+ GimpLayer *floating_sel; /* the FS layer */
+ GimpChannel *selection_mask; /* the selection mask channel */
+
+ GimpParasiteList *parasites; /* Plug-in parasite data */
+
+ gboolean visible[MAX_CHANNELS]; /* visible channels */
+ gboolean active[MAX_CHANNELS]; /* active channels */
+
+ gboolean quick_mask_state; /* TRUE if quick mask is on */
+ gboolean quick_mask_inverted; /* TRUE if quick mask is inverted */
+ GimpRGB quick_mask_color; /* rgba triplet of the color */
+
+ /* Undo apparatus */
+ GimpUndoStack *undo_stack; /* stack for undo operations */
+ GimpUndoStack *redo_stack; /* stack for redo operations */
+ gint group_count; /* nested undo groups */
+ GimpUndoType pushing_undo_group; /* undo group status flag */
+
+ /* Signal emission accumulator */
+ GimpImageFlushAccumulator flush_accum;
+};
+
+#define GIMP_IMAGE_GET_PRIVATE(image) (((GimpImage *) (image))->priv)
+
+void gimp_image_take_mask (GimpImage *image,
+ GimpChannel *mask);
+
+
+#endif /* __GIMP_IMAGE_PRIVATE_H__ */
diff --git a/app/core/gimpimage-quick-mask.c b/app/core/gimpimage-quick-mask.c
new file mode 100644
index 0000000..9473bde
--- /dev/null
+++ b/app/core/gimpimage-quick-mask.c
@@ -0,0 +1,212 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <cairo.h>
+#include <gegl.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+#include "libgimpcolor/gimpcolor.h"
+
+#include "core-types.h"
+
+#include "gimp.h"
+#include "gimpchannel.h"
+#include "gimpimage.h"
+#include "gimpimage-private.h"
+#include "gimpimage-quick-mask.h"
+#include "gimpimage-undo.h"
+#include "gimpimage-undo-push.h"
+#include "gimplayer.h"
+#include "gimplayer-floating-selection.h"
+
+#include "gimp-intl.h"
+
+
+#define CHANNEL_WAS_ACTIVE (0x2)
+
+
+/* public functions */
+
+void
+gimp_image_set_quick_mask_state (GimpImage *image,
+ gboolean active)
+{
+ GimpImagePrivate *private;
+ GimpChannel *selection;
+ GimpChannel *mask;
+ gboolean channel_was_active;
+
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+
+ if (active == gimp_image_get_quick_mask_state (image))
+ return;
+
+ private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ /* Keep track of the state so that we can make the right drawable
+ * active again when deactiviting quick mask (see bug #134371).
+ */
+ if (private->quick_mask_state)
+ channel_was_active = (private->quick_mask_state & CHANNEL_WAS_ACTIVE) != 0;
+ else
+ channel_was_active = gimp_image_get_active_channel (image) != NULL;
+
+ /* Set private->quick_mask_state early so we can return early when
+ * being called recursively.
+ */
+ private->quick_mask_state = (active
+ ? TRUE | (channel_was_active ?
+ CHANNEL_WAS_ACTIVE : 0)
+ : FALSE);
+
+ selection = gimp_image_get_mask (image);
+ mask = gimp_image_get_quick_mask (image);
+
+ if (active)
+ {
+ if (! mask)
+ {
+ GimpLayer *floating_sel;
+
+ gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_IMAGE_QUICK_MASK,
+ C_("undo-type", "Enable Quick Mask"));
+
+ floating_sel = gimp_image_get_floating_selection (image);
+
+ if (floating_sel)
+ floating_sel_to_layer (floating_sel, NULL);
+
+ mask = GIMP_CHANNEL (gimp_item_duplicate (GIMP_ITEM (selection),
+ GIMP_TYPE_CHANNEL));
+
+ if (! gimp_channel_is_empty (selection))
+ gimp_channel_clear (selection, NULL, TRUE);
+
+ gimp_channel_set_color (mask, &private->quick_mask_color, FALSE);
+ gimp_item_rename (GIMP_ITEM (mask), GIMP_IMAGE_QUICK_MASK_NAME,
+ NULL);
+
+ if (private->quick_mask_inverted)
+ gimp_channel_invert (mask, FALSE);
+
+ gimp_image_add_channel (image, mask, NULL, 0, TRUE);
+
+ gimp_image_undo_group_end (image);
+ }
+ }
+ else
+ {
+ if (mask)
+ {
+ GimpLayer *floating_sel = gimp_image_get_floating_selection (image);
+
+ gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_IMAGE_QUICK_MASK,
+ C_("undo-type", "Disable Quick Mask"));
+
+ if (private->quick_mask_inverted)
+ gimp_channel_invert (mask, TRUE);
+
+ if (floating_sel &&
+ gimp_layer_get_floating_sel_drawable (floating_sel) == GIMP_DRAWABLE (mask))
+ floating_sel_anchor (floating_sel);
+
+ gimp_item_to_selection (GIMP_ITEM (mask),
+ GIMP_CHANNEL_OP_REPLACE,
+ TRUE, FALSE, 0.0, 0.0);
+ gimp_image_remove_channel (image, mask, TRUE, NULL);
+
+ if (! channel_was_active)
+ gimp_image_unset_active_channel (image);
+
+ gimp_image_undo_group_end (image);
+ }
+ }
+
+ gimp_image_quick_mask_changed (image);
+}
+
+gboolean
+gimp_image_get_quick_mask_state (GimpImage *image)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);
+
+ return GIMP_IMAGE_GET_PRIVATE (image)->quick_mask_state;
+}
+
+void
+gimp_image_set_quick_mask_color (GimpImage *image,
+ const GimpRGB *color)
+{
+ GimpChannel *quick_mask;
+
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+ g_return_if_fail (color != NULL);
+
+ GIMP_IMAGE_GET_PRIVATE (image)->quick_mask_color = *color;
+
+ quick_mask = gimp_image_get_quick_mask (image);
+ if (quick_mask)
+ gimp_channel_set_color (quick_mask, color, TRUE);
+}
+
+void
+gimp_image_get_quick_mask_color (GimpImage *image,
+ GimpRGB *color)
+{
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+ g_return_if_fail (color != NULL);
+
+ *color = GIMP_IMAGE_GET_PRIVATE (image)->quick_mask_color;
+}
+
+GimpChannel *
+gimp_image_get_quick_mask (GimpImage *image)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+
+ return gimp_image_get_channel_by_name (image, GIMP_IMAGE_QUICK_MASK_NAME);
+}
+
+void
+gimp_image_quick_mask_invert (GimpImage *image)
+{
+ GimpImagePrivate *private;
+
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+
+ private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ if (private->quick_mask_state)
+ {
+ GimpChannel *quick_mask = gimp_image_get_quick_mask (image);
+
+ if (quick_mask)
+ gimp_channel_invert (quick_mask, TRUE);
+ }
+
+ private->quick_mask_inverted = ! private->quick_mask_inverted;
+}
+
+gboolean
+gimp_image_get_quick_mask_inverted (GimpImage *image)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);
+
+ return GIMP_IMAGE_GET_PRIVATE (image)->quick_mask_inverted;
+}
diff --git a/app/core/gimpimage-quick-mask.h b/app/core/gimpimage-quick-mask.h
new file mode 100644
index 0000000..0eca62e
--- /dev/null
+++ b/app/core/gimpimage-quick-mask.h
@@ -0,0 +1,43 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_IMAGE_QUICK_MASK_H__
+#define __GIMP_IMAGE_QUICK_MASK_H__
+
+
+/* don't change this string, it's used to identify the Quick Mask
+ * when opening files.
+ */
+#define GIMP_IMAGE_QUICK_MASK_NAME "Qmask"
+
+
+void gimp_image_set_quick_mask_state (GimpImage *image,
+ gboolean active);
+gboolean gimp_image_get_quick_mask_state (GimpImage *image);
+
+void gimp_image_set_quick_mask_color (GimpImage *image,
+ const GimpRGB *color);
+void gimp_image_get_quick_mask_color (GimpImage *image,
+ GimpRGB *color);
+
+GimpChannel * gimp_image_get_quick_mask (GimpImage *image);
+
+void gimp_image_quick_mask_invert (GimpImage *image);
+gboolean gimp_image_get_quick_mask_inverted (GimpImage *image);
+
+
+#endif /* __GIMP_IMAGE_QUICK_MASK_H__ */
diff --git a/app/core/gimpimage-resize.c b/app/core/gimpimage-resize.c
new file mode 100644
index 0000000..e1c46ca
--- /dev/null
+++ b/app/core/gimpimage-resize.c
@@ -0,0 +1,327 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpbase/gimpbase.h"
+
+#include "core-types.h"
+
+#include "gimp.h"
+#include "gimpcontainer.h"
+#include "gimpcontext.h"
+#include "gimpguide.h"
+#include "gimpimage.h"
+#include "gimpimage-guides.h"
+#include "gimpimage-item-list.h"
+#include "gimpimage-resize.h"
+#include "gimpimage-sample-points.h"
+#include "gimpimage-undo.h"
+#include "gimpimage-undo-push.h"
+#include "gimplayer.h"
+#include "gimpobjectqueue.h"
+#include "gimpprogress.h"
+#include "gimpsamplepoint.h"
+
+#include "text/gimptextlayer.h"
+
+#include "gimp-intl.h"
+
+
+void
+gimp_image_resize (GimpImage *image,
+ GimpContext *context,
+ gint new_width,
+ gint new_height,
+ gint offset_x,
+ gint offset_y,
+ GimpProgress *progress)
+{
+ gimp_image_resize_with_layers (image, context, GIMP_FILL_TRANSPARENT,
+ new_width, new_height, offset_x, offset_y,
+ GIMP_ITEM_SET_NONE, TRUE,
+ progress);
+}
+
+void
+gimp_image_resize_with_layers (GimpImage *image,
+ GimpContext *context,
+ GimpFillType fill_type,
+ gint new_width,
+ gint new_height,
+ gint offset_x,
+ gint offset_y,
+ GimpItemSet layer_set,
+ gboolean resize_text_layers,
+ GimpProgress *progress)
+{
+ GimpObjectQueue *queue;
+ GList *resize_layers;
+ GList *list;
+ GimpItem *item;
+ gint old_width, old_height;
+
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+ g_return_if_fail (GIMP_IS_CONTEXT (context));
+ g_return_if_fail (new_width > 0 && new_height > 0);
+ g_return_if_fail (progress == NULL || GIMP_IS_PROGRESS (progress));
+
+ gimp_set_busy (image->gimp);
+
+ g_object_freeze_notify (G_OBJECT (image));
+
+ gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_IMAGE_RESIZE,
+ C_("undo-type", "Resize Image"));
+
+ resize_layers = gimp_image_item_list_get_list (image,
+ GIMP_ITEM_TYPE_LAYERS,
+ layer_set);
+
+ old_width = gimp_image_get_width (image);
+ old_height = gimp_image_get_height (image);
+
+ /* Push the image size to the stack */
+ gimp_image_undo_push_image_size (image, NULL,
+ -offset_x, -offset_y,
+ new_width, new_height);
+
+ /* Set the new width and height */
+ g_object_set (image,
+ "width", new_width,
+ "height", new_height,
+ NULL);
+
+ /* Reposition all layers */
+ for (list = gimp_image_get_layer_iter (image);
+ list;
+ list = g_list_next (list))
+ {
+ GimpItem *item = list->data;
+
+ gimp_item_translate (item, offset_x, offset_y, TRUE);
+ }
+
+ queue = gimp_object_queue_new (progress);
+ progress = GIMP_PROGRESS (queue);
+
+ for (list = resize_layers; list; list = g_list_next (list))
+ {
+ GimpItem *item = list->data;
+
+ /* group layers can't be resized here */
+ if (gimp_viewable_get_children (GIMP_VIEWABLE (item)))
+ continue;
+
+ if (! resize_text_layers && gimp_item_is_text_layer (item))
+ continue;
+
+ /* note that we call gimp_item_start_move(), and not
+ * gimp_item_start_transform(). see the comment in gimp_item_resize()
+ * for more information.
+ */
+ gimp_item_start_move (item, TRUE);
+
+ gimp_object_queue_push (queue, item);
+ }
+
+ g_list_free (resize_layers);
+
+ gimp_object_queue_push (queue, gimp_image_get_mask (image));
+ gimp_object_queue_push_container (queue, gimp_image_get_channels (image));
+ gimp_object_queue_push_container (queue, gimp_image_get_vectors (image));
+
+ /* Resize all resize_layers, channels (including selection mask), and
+ * vectors
+ */
+ while ((item = gimp_object_queue_pop (queue)))
+ {
+ if (GIMP_IS_LAYER (item))
+ {
+ gint old_offset_x;
+ gint old_offset_y;
+
+ gimp_item_get_offset (item, &old_offset_x, &old_offset_y);
+
+ gimp_item_resize (item, context, fill_type,
+ new_width, new_height,
+ old_offset_x, old_offset_y);
+
+ gimp_item_end_move (item, TRUE);
+ }
+ else
+ {
+ gimp_item_resize (item, context, GIMP_FILL_TRANSPARENT,
+ new_width, new_height, offset_x, offset_y);
+ }
+ }
+
+ /* Reposition or remove all guides */
+ list = gimp_image_get_guides (image);
+
+ while (list)
+ {
+ GimpGuide *guide = list->data;
+ gboolean remove_guide = FALSE;
+ gint new_position = gimp_guide_get_position (guide);
+
+ list = g_list_next (list);
+
+ switch (gimp_guide_get_orientation (guide))
+ {
+ case GIMP_ORIENTATION_HORIZONTAL:
+ new_position += offset_y;
+ if (new_position < 0 || new_position > new_height)
+ remove_guide = TRUE;
+ break;
+
+ case GIMP_ORIENTATION_VERTICAL:
+ new_position += offset_x;
+ if (new_position < 0 || new_position > new_width)
+ remove_guide = TRUE;
+ break;
+
+ default:
+ break;
+ }
+
+ if (remove_guide)
+ gimp_image_remove_guide (image, guide, TRUE);
+ else if (new_position != gimp_guide_get_position (guide))
+ gimp_image_move_guide (image, guide, new_position, TRUE);
+ }
+
+ /* Reposition or remove sample points */
+ list = gimp_image_get_sample_points (image);
+
+ while (list)
+ {
+ GimpSamplePoint *sample_point = list->data;
+ gboolean remove_sample_point = FALSE;
+ gint old_x;
+ gint old_y;
+ gint new_x;
+ gint new_y;
+
+ list = g_list_next (list);
+
+ gimp_sample_point_get_position (sample_point, &old_x, &old_y);
+
+ new_y = old_y + offset_y;
+ if ((new_y < 0) || (new_y >= new_height))
+ remove_sample_point = TRUE;
+
+ new_x = old_x + offset_x;
+ if ((new_x < 0) || (new_x >= new_width))
+ remove_sample_point = TRUE;
+
+ if (remove_sample_point)
+ gimp_image_remove_sample_point (image, sample_point, TRUE);
+ else if (new_x != old_x || new_y != old_y)
+ gimp_image_move_sample_point (image, sample_point,
+ new_x, new_y, TRUE);
+ }
+
+ g_object_unref (queue);
+
+ gimp_image_undo_group_end (image);
+
+ gimp_image_size_changed_detailed (image,
+ offset_x, offset_y,
+ old_width, old_height);
+
+ g_object_thaw_notify (G_OBJECT (image));
+
+ gimp_unset_busy (image->gimp);
+}
+
+void
+gimp_image_resize_to_layers (GimpImage *image,
+ GimpContext *context,
+ gint *offset_x,
+ gint *offset_y,
+ gint *new_width,
+ gint *new_height,
+ GimpProgress *progress)
+{
+ GList *list;
+ GimpItem *item;
+ gint x, y;
+ gint width, height;
+
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+ g_return_if_fail (GIMP_IS_CONTEXT (context));
+ g_return_if_fail (progress == NULL || GIMP_IS_PROGRESS (progress));
+
+ list = gimp_image_get_layer_iter (image);
+
+ if (! list)
+ return;
+
+ /* figure out starting values */
+ item = list->data;
+
+ x = gimp_item_get_offset_x (item);
+ y = gimp_item_get_offset_y (item);
+ width = gimp_item_get_width (item);
+ height = gimp_item_get_height (item);
+
+ /* Respect all layers */
+ for (list = g_list_next (list); list; list = g_list_next (list))
+ {
+ item = list->data;
+
+ gimp_rectangle_union (x, y,
+ width, height,
+ gimp_item_get_offset_x (item),
+ gimp_item_get_offset_y (item),
+ gimp_item_get_width (item),
+ gimp_item_get_height (item),
+ &x, &y,
+ &width, &height);
+ }
+
+ gimp_image_resize (image, context,
+ width, height, -x, -y,
+ progress);
+ if (offset_x)
+ *offset_x = -x;
+ if (offset_y)
+ *offset_y = -y;
+ if (new_width)
+ *new_width = width;
+ if (new_height)
+ *new_height = height;
+}
+
+void
+gimp_image_resize_to_selection (GimpImage *image,
+ GimpContext *context,
+ GimpProgress *progress)
+{
+ GimpChannel *selection = gimp_image_get_mask (image);
+ gint x, y, w, h;
+
+ if (gimp_item_bounds (GIMP_ITEM (selection), &x, &y, &w, &h))
+ {
+ gimp_image_resize (image, context,
+ w, h, -x, -y,
+ progress);
+ }
+}
diff --git a/app/core/gimpimage-resize.h b/app/core/gimpimage-resize.h
new file mode 100644
index 0000000..edcc64c
--- /dev/null
+++ b/app/core/gimpimage-resize.h
@@ -0,0 +1,53 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattisbvf
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_IMAGE_RESIZE_H__
+#define __GIMP_IMAGE_RESIZE_H__
+
+
+void gimp_image_resize (GimpImage *image,
+ GimpContext *context,
+ gint new_width,
+ gint new_height,
+ gint offset_x,
+ gint offset_y,
+ GimpProgress *progress);
+
+void gimp_image_resize_with_layers (GimpImage *image,
+ GimpContext *context,
+ GimpFillType fill_type,
+ gint new_width,
+ gint new_height,
+ gint offset_x,
+ gint offset_y,
+ GimpItemSet layer_set,
+ gboolean resize_text_layers,
+ GimpProgress *progress);
+
+void gimp_image_resize_to_layers (GimpImage *image,
+ GimpContext *context,
+ gint *offset_x,
+ gint *offset_y,
+ gint *new_width,
+ gint *new_height,
+ GimpProgress *progress);
+void gimp_image_resize_to_selection (GimpImage *image,
+ GimpContext *context,
+ GimpProgress *progress);
+
+
+#endif /* __GIMP_IMAGE_RESIZE_H__ */
diff --git a/app/core/gimpimage-rotate.c b/app/core/gimpimage-rotate.c
new file mode 100644
index 0000000..682319f
--- /dev/null
+++ b/app/core/gimpimage-rotate.c
@@ -0,0 +1,377 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "core-types.h"
+
+#include "vectors/gimpvectors.h"
+
+#include "gimp.h"
+#include "gimpcontainer.h"
+#include "gimpcontext.h"
+#include "gimpguide.h"
+#include "gimpimage.h"
+#include "gimpimage-rotate.h"
+#include "gimpimage-guides.h"
+#include "gimpimage-sample-points.h"
+#include "gimpimage-undo.h"
+#include "gimpimage-undo-push.h"
+#include "gimpitem.h"
+#include "gimplayer.h"
+#include "gimpobjectqueue.h"
+#include "gimpprogress.h"
+#include "gimpsamplepoint.h"
+
+
+static void gimp_image_rotate_item_offset (GimpImage *image,
+ GimpRotationType rotate_type,
+ GimpItem *item,
+ gint off_x,
+ gint off_y);
+static void gimp_image_rotate_guides (GimpImage *image,
+ GimpRotationType rotate_type);
+static void gimp_image_rotate_sample_points (GimpImage *image,
+ GimpRotationType rotate_type);
+
+
+void
+gimp_image_rotate (GimpImage *image,
+ GimpContext *context,
+ GimpRotationType rotate_type,
+ GimpProgress *progress)
+{
+ GimpObjectQueue *queue;
+ GimpItem *item;
+ GList *list;
+ gdouble center_x;
+ gdouble center_y;
+ gint new_image_width;
+ gint new_image_height;
+ gint previous_image_width;
+ gint previous_image_height;
+ gint offset_x;
+ gint offset_y;
+ gboolean size_changed;
+
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+ g_return_if_fail (GIMP_IS_CONTEXT (context));
+ g_return_if_fail (progress == NULL || GIMP_IS_PROGRESS (progress));
+
+ previous_image_width = gimp_image_get_width (image);
+ previous_image_height = gimp_image_get_height (image);
+
+ center_x = previous_image_width / 2.0;
+ center_y = previous_image_height / 2.0;
+
+ /* Resize the image (if needed) */
+ switch (rotate_type)
+ {
+ case GIMP_ROTATE_90:
+ case GIMP_ROTATE_270:
+ new_image_width = gimp_image_get_height (image);
+ new_image_height = gimp_image_get_width (image);
+ size_changed = TRUE;
+ offset_x = (gimp_image_get_width (image) - new_image_width) / 2;
+ offset_y = (gimp_image_get_height (image) - new_image_height) / 2;
+ break;
+
+ case GIMP_ROTATE_180:
+ new_image_width = gimp_image_get_width (image);
+ new_image_height = gimp_image_get_height (image);
+ size_changed = FALSE;
+ offset_x = 0;
+ offset_y = 0;
+ break;
+
+ default:
+ g_return_if_reached ();
+ return;
+ }
+
+ gimp_set_busy (image->gimp);
+
+ queue = gimp_object_queue_new (progress);
+ progress = GIMP_PROGRESS (queue);
+
+ gimp_object_queue_push_container (queue, gimp_image_get_layers (image));
+ gimp_object_queue_push (queue, gimp_image_get_mask (image));
+ gimp_object_queue_push_container (queue, gimp_image_get_channels (image));
+ gimp_object_queue_push_container (queue, gimp_image_get_vectors (image));
+
+ g_object_freeze_notify (G_OBJECT (image));
+
+ gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_IMAGE_ROTATE, NULL);
+
+ /* Rotate all layers, channels (including selection mask), and vectors */
+ while ((item = gimp_object_queue_pop (queue)))
+ {
+ gint off_x;
+ gint off_y;
+
+ gimp_item_get_offset (item, &off_x, &off_y);
+
+ gimp_item_rotate (item, context, rotate_type, center_x, center_y, FALSE);
+
+ if (GIMP_IS_LAYER (item))
+ {
+ gimp_image_rotate_item_offset (image, rotate_type, item, off_x, off_y);
+ }
+ else
+ {
+ gimp_item_set_offset (item, 0, 0);
+
+ if (GIMP_IS_VECTORS (item))
+ {
+ gimp_item_set_size (item, new_image_width, new_image_height);
+
+ gimp_item_translate (item,
+ (new_image_width - gimp_image_get_width (image)) / 2,
+ (new_image_height - gimp_image_get_height (image)) / 2,
+ FALSE);
+ }
+ }
+
+ gimp_progress_set_value (progress, 1.0);
+ }
+
+ /* Rotate all Guides */
+ gimp_image_rotate_guides (image, rotate_type);
+
+ /* Rotate all sample points */
+ gimp_image_rotate_sample_points (image, rotate_type);
+
+ /* Resize the image (if needed) */
+ if (size_changed)
+ {
+ gdouble xres;
+ gdouble yres;
+
+ gimp_image_undo_push_image_size (image,
+ NULL,
+ offset_x,
+ offset_y,
+ new_image_width,
+ new_image_height);
+
+ g_object_set (image,
+ "width", new_image_width,
+ "height", new_image_height,
+ NULL);
+
+ gimp_image_get_resolution (image, &xres, &yres);
+
+ if (xres != yres)
+ gimp_image_set_resolution (image, yres, xres);
+ }
+
+ /* Notify guide movements */
+ for (list = gimp_image_get_guides (image);
+ list;
+ list = g_list_next (list))
+ {
+ gimp_image_guide_moved (image, list->data);
+ }
+
+ /* Notify sample point movements */
+ for (list = gimp_image_get_sample_points (image);
+ list;
+ list = g_list_next (list))
+ {
+ gimp_image_sample_point_moved (image, list->data);
+ }
+
+ gimp_image_undo_group_end (image);
+
+ g_object_unref (queue);
+
+ if (size_changed)
+ gimp_image_size_changed_detailed (image,
+ -offset_x,
+ -offset_y,
+ previous_image_width,
+ previous_image_height);
+
+ g_object_thaw_notify (G_OBJECT (image));
+
+ gimp_unset_busy (image->gimp);
+}
+
+
+static void
+gimp_image_rotate_item_offset (GimpImage *image,
+ GimpRotationType rotate_type,
+ GimpItem *item,
+ gint off_x,
+ gint off_y)
+{
+ gint x = 0;
+ gint y = 0;
+
+ switch (rotate_type)
+ {
+ case GIMP_ROTATE_90:
+ x = gimp_image_get_height (image) - off_y - gimp_item_get_width (item);
+ y = off_x;
+ break;
+
+ case GIMP_ROTATE_270:
+ x = off_y;
+ y = gimp_image_get_width (image) - off_x - gimp_item_get_height (item);
+ break;
+
+ case GIMP_ROTATE_180:
+ return;
+
+ default:
+ g_return_if_reached ();
+ }
+
+ gimp_item_get_offset (item, &off_x, &off_y);
+
+ x -= off_x;
+ y -= off_y;
+
+ if (x || y)
+ gimp_item_translate (item, x, y, FALSE);
+}
+
+static void
+gimp_image_rotate_guides (GimpImage *image,
+ GimpRotationType rotate_type)
+{
+ GList *list;
+
+ /* Rotate all Guides */
+ for (list = gimp_image_get_guides (image);
+ list;
+ list = g_list_next (list))
+ {
+ GimpGuide *guide = list->data;
+ GimpOrientationType orientation = gimp_guide_get_orientation (guide);
+ gint position = gimp_guide_get_position (guide);
+
+ switch (rotate_type)
+ {
+ case GIMP_ROTATE_90:
+ switch (orientation)
+ {
+ case GIMP_ORIENTATION_HORIZONTAL:
+ gimp_image_undo_push_guide (image, NULL, guide);
+ gimp_guide_set_orientation (guide, GIMP_ORIENTATION_VERTICAL);
+ gimp_guide_set_position (guide,
+ gimp_image_get_height (image) - position);
+ break;
+
+ case GIMP_ORIENTATION_VERTICAL:
+ gimp_image_undo_push_guide (image, NULL, guide);
+ gimp_guide_set_orientation (guide, GIMP_ORIENTATION_HORIZONTAL);
+ break;
+
+ default:
+ break;
+ }
+ break;
+
+ case GIMP_ROTATE_180:
+ switch (orientation)
+ {
+ case GIMP_ORIENTATION_HORIZONTAL:
+ gimp_image_move_guide (image, guide,
+ gimp_image_get_height (image) - position,
+ TRUE);
+ break;
+
+ case GIMP_ORIENTATION_VERTICAL:
+ gimp_image_move_guide (image, guide,
+ gimp_image_get_width (image) - position,
+ TRUE);
+ break;
+
+ default:
+ break;
+ }
+ break;
+
+ case GIMP_ROTATE_270:
+ switch (orientation)
+ {
+ case GIMP_ORIENTATION_HORIZONTAL:
+ gimp_image_undo_push_guide (image, NULL, guide);
+ gimp_guide_set_orientation (guide, GIMP_ORIENTATION_VERTICAL);
+ break;
+
+ case GIMP_ORIENTATION_VERTICAL:
+ gimp_image_undo_push_guide (image, NULL, guide);
+ gimp_guide_set_orientation (guide, GIMP_ORIENTATION_HORIZONTAL);
+ gimp_guide_set_position (guide,
+ gimp_image_get_width (image) - position);
+ break;
+
+ default:
+ break;
+ }
+ break;
+ }
+ }
+}
+
+
+static void
+gimp_image_rotate_sample_points (GimpImage *image,
+ GimpRotationType rotate_type)
+{
+ GList *list;
+
+ /* Rotate all sample points */
+ for (list = gimp_image_get_sample_points (image);
+ list;
+ list = g_list_next (list))
+ {
+ GimpSamplePoint *sample_point = list->data;
+ gint old_x;
+ gint old_y;
+
+ gimp_image_undo_push_sample_point (image, NULL, sample_point);
+
+ gimp_sample_point_get_position (sample_point, &old_x, &old_y);
+
+ switch (rotate_type)
+ {
+ case GIMP_ROTATE_90:
+ gimp_sample_point_set_position (sample_point,
+ gimp_image_get_height (image) - old_y,
+ old_x);
+ break;
+
+ case GIMP_ROTATE_180:
+ gimp_sample_point_set_position (sample_point,
+ gimp_image_get_width (image) - old_x,
+ gimp_image_get_height (image) - old_y);
+ break;
+
+ case GIMP_ROTATE_270:
+ gimp_sample_point_set_position (sample_point,
+ old_y,
+ gimp_image_get_width (image) - old_x);
+ break;
+ }
+ }
+}
diff --git a/app/core/gimpimage-rotate.h b/app/core/gimpimage-rotate.h
new file mode 100644
index 0000000..cad0de8
--- /dev/null
+++ b/app/core/gimpimage-rotate.h
@@ -0,0 +1,28 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattisbvf
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_IMAGE_ROTATE_H__
+#define __GIMP_IMAGE_ROTATE_H__
+
+
+void gimp_image_rotate (GimpImage *image,
+ GimpContext *context,
+ GimpRotationType rotate_type,
+ GimpProgress *progress);
+
+
+#endif /* __GIMP_IMAGE_ROTATE_H__ */
diff --git a/app/core/gimpimage-sample-points.c b/app/core/gimpimage-sample-points.c
new file mode 100644
index 0000000..24fc66f
--- /dev/null
+++ b/app/core/gimpimage-sample-points.c
@@ -0,0 +1,213 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "core-types.h"
+
+#include "gimp.h"
+#include "gimpimage.h"
+#include "gimpimage-private.h"
+#include "gimpimage-sample-points.h"
+#include "gimpimage-undo-push.h"
+#include "gimpsamplepoint.h"
+
+#include "gimp-intl.h"
+
+
+/* public functions */
+
+GimpSamplePoint *
+gimp_image_add_sample_point_at_pos (GimpImage *image,
+ gint x,
+ gint y,
+ gboolean push_undo)
+{
+ GimpSamplePoint *sample_point;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+ g_return_val_if_fail (x >= 0 && x < gimp_image_get_width (image), NULL);
+ g_return_val_if_fail (y >= 0 && y < gimp_image_get_height (image), NULL);
+
+ sample_point = gimp_sample_point_new (image->gimp->next_sample_point_ID++);
+
+ if (push_undo)
+ gimp_image_undo_push_sample_point (image, C_("undo-type", "Add Sample Point"),
+ sample_point);
+
+ gimp_image_add_sample_point (image, sample_point, x, y);
+ g_object_unref (sample_point);
+
+ return sample_point;
+}
+
+void
+gimp_image_add_sample_point (GimpImage *image,
+ GimpSamplePoint *sample_point,
+ gint x,
+ gint y)
+{
+ GimpImagePrivate *private;
+
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+ g_return_if_fail (GIMP_IS_SAMPLE_POINT (sample_point));
+
+ private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ private->sample_points = g_list_append (private->sample_points, sample_point);
+
+ gimp_sample_point_set_position (sample_point, x, y);
+ g_object_ref (sample_point);
+
+ gimp_image_sample_point_added (image, sample_point);
+}
+
+void
+gimp_image_remove_sample_point (GimpImage *image,
+ GimpSamplePoint *sample_point,
+ gboolean push_undo)
+{
+ GimpImagePrivate *private;
+
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+ g_return_if_fail (GIMP_IS_SAMPLE_POINT (sample_point));
+
+ private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ if (push_undo)
+ gimp_image_undo_push_sample_point (image,
+ C_("undo-type", "Remove Sample Point"),
+ sample_point);
+
+ private->sample_points = g_list_remove (private->sample_points, sample_point);
+ gimp_aux_item_removed (GIMP_AUX_ITEM (sample_point));
+
+ gimp_image_sample_point_removed (image, sample_point);
+
+ gimp_sample_point_set_position (sample_point,
+ GIMP_SAMPLE_POINT_POSITION_UNDEFINED,
+ GIMP_SAMPLE_POINT_POSITION_UNDEFINED);
+ g_object_unref (sample_point);
+}
+
+void
+gimp_image_move_sample_point (GimpImage *image,
+ GimpSamplePoint *sample_point,
+ gint x,
+ gint y,
+ gboolean push_undo)
+{
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+ g_return_if_fail (GIMP_IS_SAMPLE_POINT (sample_point));
+ g_return_if_fail (x >= 0);
+ g_return_if_fail (y >= 0);
+ g_return_if_fail (x < gimp_image_get_width (image));
+ g_return_if_fail (y < gimp_image_get_height (image));
+
+ if (push_undo)
+ gimp_image_undo_push_sample_point (image,
+ C_("undo-type", "Move Sample Point"),
+ sample_point);
+
+ gimp_sample_point_set_position (sample_point, x, y);
+
+ gimp_image_sample_point_moved (image, sample_point);
+}
+
+void
+gimp_image_set_sample_point_pick_mode (GimpImage *image,
+ GimpSamplePoint *sample_point,
+ GimpColorPickMode pick_mode,
+ gboolean push_undo)
+{
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+ g_return_if_fail (GIMP_IS_SAMPLE_POINT (sample_point));
+
+ if (push_undo)
+ gimp_image_undo_push_sample_point (image,
+ C_("undo-type",
+ "Set Sample Point Pick Mode"),
+ sample_point);
+
+ gimp_sample_point_set_pick_mode (sample_point, pick_mode);
+
+ /* well... */
+ gimp_image_sample_point_moved (image, sample_point);
+}
+
+GList *
+gimp_image_get_sample_points (GimpImage *image)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+
+ return GIMP_IMAGE_GET_PRIVATE (image)->sample_points;
+}
+
+GimpSamplePoint *
+gimp_image_get_sample_point (GimpImage *image,
+ guint32 id)
+{
+ GList *sample_points;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+
+ for (sample_points = GIMP_IMAGE_GET_PRIVATE (image)->sample_points;
+ sample_points;
+ sample_points = g_list_next (sample_points))
+ {
+ GimpSamplePoint *sample_point = sample_points->data;
+
+ if (gimp_aux_item_get_ID (GIMP_AUX_ITEM (sample_point)) == id)
+ return sample_point;
+ }
+
+ return NULL;
+}
+
+GimpSamplePoint *
+gimp_image_get_next_sample_point (GimpImage *image,
+ guint32 id,
+ gboolean *sample_point_found)
+{
+ GList *sample_points;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+ g_return_val_if_fail (sample_point_found != NULL, NULL);
+
+ if (id == 0)
+ *sample_point_found = TRUE;
+ else
+ *sample_point_found = FALSE;
+
+ for (sample_points = GIMP_IMAGE_GET_PRIVATE (image)->sample_points;
+ sample_points;
+ sample_points = g_list_next (sample_points))
+ {
+ GimpSamplePoint *sample_point = sample_points->data;
+
+ if (*sample_point_found) /* this is the first guide after the found one */
+ return sample_point;
+
+ if (gimp_aux_item_get_ID (GIMP_AUX_ITEM (sample_point)) == id) /* found it, next one will be returned */
+ *sample_point_found = TRUE;
+ }
+
+ return NULL;
+}
diff --git a/app/core/gimpimage-sample-points.h b/app/core/gimpimage-sample-points.h
new file mode 100644
index 0000000..c84a02b
--- /dev/null
+++ b/app/core/gimpimage-sample-points.h
@@ -0,0 +1,60 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_IMAGE_SAMPLE_POINTS_H__
+#define __GIMP_IMAGE_SAMPLE_POINTS_H__
+
+
+/* public sample point adding API
+ */
+GimpSamplePoint * gimp_image_add_sample_point_at_pos (GimpImage *image,
+ gint x,
+ gint y,
+ gboolean push_undo);
+
+/* internal sample point adding API, does not check the sample
+ * point's position and is publicly declared only to be used from
+ * undo
+ */
+void gimp_image_add_sample_point (GimpImage *image,
+ GimpSamplePoint *sample_point,
+ gint x,
+ gint y);
+
+void gimp_image_remove_sample_point (GimpImage *image,
+ GimpSamplePoint *sample_point,
+ gboolean push_undo);
+void gimp_image_move_sample_point (GimpImage *image,
+ GimpSamplePoint *sample_point,
+ gint x,
+ gint y,
+ gboolean push_undo);
+void gimp_image_set_sample_point_pick_mode
+ (GimpImage *image,
+ GimpSamplePoint *sample_point,
+ GimpColorPickMode pick_mode,
+ gboolean push_undo);
+
+GList * gimp_image_get_sample_points (GimpImage *image);
+GimpSamplePoint * gimp_image_get_sample_point (GimpImage *image,
+ guint32 id);
+GimpSamplePoint * gimp_image_get_next_sample_point (GimpImage *image,
+ guint32 id,
+ gboolean *sample_point_found);
+
+
+#endif /* __GIMP_IMAGE_SAMPLE_POINTS_H__ */
diff --git a/app/core/gimpimage-scale.c b/app/core/gimpimage-scale.c
new file mode 100644
index 0000000..04ae6fb
--- /dev/null
+++ b/app/core/gimpimage-scale.c
@@ -0,0 +1,260 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "core-types.h"
+
+#include "gimp.h"
+#include "gimpcontainer.h"
+#include "gimpguide.h"
+#include "gimpgrouplayer.h"
+#include "gimpimage.h"
+#include "gimpimage-guides.h"
+#include "gimpimage-sample-points.h"
+#include "gimpimage-scale.h"
+#include "gimpimage-undo.h"
+#include "gimpimage-undo-push.h"
+#include "gimplayer.h"
+#include "gimpobjectqueue.h"
+#include "gimpprogress.h"
+#include "gimpprojection.h"
+#include "gimpsamplepoint.h"
+
+#include "gimp-log.h"
+#include "gimp-intl.h"
+
+
+void
+gimp_image_scale (GimpImage *image,
+ gint new_width,
+ gint new_height,
+ GimpInterpolationType interpolation_type,
+ GimpProgress *progress)
+{
+ GimpObjectQueue *queue;
+ GimpItem *item;
+ GList *list;
+ gint old_width;
+ gint old_height;
+ gint offset_x;
+ gint offset_y;
+ gdouble img_scale_w = 1.0;
+ gdouble img_scale_h = 1.0;
+
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+ g_return_if_fail (new_width > 0 && new_height > 0);
+ g_return_if_fail (progress == NULL || GIMP_IS_PROGRESS (progress));
+
+ gimp_set_busy (image->gimp);
+
+ queue = gimp_object_queue_new (progress);
+ progress = GIMP_PROGRESS (queue);
+
+ gimp_object_queue_push_container (queue, gimp_image_get_layers (image));
+ gimp_object_queue_push (queue, gimp_image_get_mask (image));
+ gimp_object_queue_push_container (queue, gimp_image_get_channels (image));
+ gimp_object_queue_push_container (queue, gimp_image_get_vectors (image));
+
+ g_object_freeze_notify (G_OBJECT (image));
+
+ gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_IMAGE_SCALE,
+ C_("undo-type", "Scale Image"));
+
+ old_width = gimp_image_get_width (image);
+ old_height = gimp_image_get_height (image);
+ img_scale_w = (gdouble) new_width / (gdouble) old_width;
+ img_scale_h = (gdouble) new_height / (gdouble) old_height;
+
+ offset_x = (old_width - new_width) / 2;
+ offset_y = (old_height - new_height) / 2;
+
+ /* Push the image size to the stack */
+ gimp_image_undo_push_image_size (image,
+ NULL,
+ offset_x,
+ offset_y,
+ new_width,
+ new_height);
+
+ /* Set the new width and height early, so below image item setters
+ * (esp. guides and sample points) don't choke about moving stuff
+ * out of the image
+ */
+ g_object_set (image,
+ "width", new_width,
+ "height", new_height,
+ NULL);
+
+ /* Scale all layers, channels (including selection mask), and vectors */
+ while ((item = gimp_object_queue_pop (queue)))
+ {
+ if (! gimp_item_scale_by_factors (item,
+ img_scale_w, img_scale_h,
+ interpolation_type, progress))
+ {
+ /* Since 0 < img_scale_w, img_scale_h, failure due to one or more
+ * vanishing scaled layer dimensions. Implicit delete implemented
+ * here. Upstream warning implemented in resize_check_layer_scaling(),
+ * which offers the user the chance to bail out.
+ */
+ g_return_if_fail (GIMP_IS_LAYER (item));
+ gimp_image_remove_layer (image, GIMP_LAYER (item), TRUE, NULL);
+ }
+ }
+
+ /* Scale all Guides */
+ for (list = gimp_image_get_guides (image);
+ list;
+ list = g_list_next (list))
+ {
+ GimpGuide *guide = list->data;
+ gint position = gimp_guide_get_position (guide);
+
+ switch (gimp_guide_get_orientation (guide))
+ {
+ case GIMP_ORIENTATION_HORIZONTAL:
+ gimp_image_move_guide (image, guide,
+ (position * new_height) / old_height,
+ TRUE);
+ break;
+
+ case GIMP_ORIENTATION_VERTICAL:
+ gimp_image_move_guide (image, guide,
+ (position * new_width) / old_width,
+ TRUE);
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ /* Scale all sample points */
+ for (list = gimp_image_get_sample_points (image);
+ list;
+ list = g_list_next (list))
+ {
+ GimpSamplePoint *sample_point = list->data;
+ gint x;
+ gint y;
+
+ gimp_sample_point_get_position (sample_point, &x, &y);
+
+ gimp_image_move_sample_point (image, sample_point,
+ x * new_width / old_width,
+ y * new_height / old_height,
+ TRUE);
+ }
+
+ gimp_image_undo_group_end (image);
+
+ g_object_unref (queue);
+
+ gimp_image_size_changed_detailed (image,
+ -offset_x,
+ -offset_y,
+ old_width,
+ old_height);
+
+ g_object_thaw_notify (G_OBJECT (image));
+
+ gimp_unset_busy (image->gimp);
+}
+
+/**
+ * gimp_image_scale_check:
+ * @image: A #GimpImage.
+ * @new_width: The new width.
+ * @new_height: The new height.
+ * @max_memsize: The maximum new memory size.
+ * @new_memsize: The new memory size.
+ *
+ * Inventory the layer list in image and check that it may be
+ * scaled to @new_height and @new_width without problems.
+ *
+ * Return value: #GIMP_IMAGE_SCALE_OK if scaling the image will shrink none
+ * of its layers completely away, and the new image size
+ * is smaller than @max_memsize.
+ * #GIMP_IMAGE_SCALE_TOO_SMALL if scaling would remove some
+ * existing layers.
+ * #GIMP_IMAGE_SCALE_TOO_BIG if the new image size would
+ * exceed the maximum specified in the preferences.
+ **/
+GimpImageScaleCheckType
+gimp_image_scale_check (GimpImage *image,
+ gint new_width,
+ gint new_height,
+ gint64 max_memsize,
+ gint64 *new_memsize)
+{
+ GList *all_layers;
+ GList *list;
+ gint64 current_size;
+ gint64 undo_size;
+ gint64 redo_size;
+ gint64 new_size;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), GIMP_IMAGE_SCALE_TOO_SMALL);
+ g_return_val_if_fail (new_memsize != NULL, GIMP_IMAGE_SCALE_TOO_SMALL);
+
+ current_size = gimp_object_get_memsize (GIMP_OBJECT (image), NULL);
+
+ new_size = gimp_image_estimate_memsize (image,
+ gimp_image_get_component_type (image),
+ new_width, new_height);
+
+ undo_size = gimp_object_get_memsize (GIMP_OBJECT (gimp_image_get_undo_stack (image)), NULL);
+ redo_size = gimp_object_get_memsize (GIMP_OBJECT (gimp_image_get_redo_stack (image)), NULL);
+
+ current_size -= undo_size + redo_size;
+ new_size -= undo_size + redo_size;
+
+ GIMP_LOG (IMAGE_SCALE,
+ "old_size = %"G_GINT64_FORMAT" new_size = %"G_GINT64_FORMAT,
+ current_size, new_size);
+
+ *new_memsize = new_size;
+
+ if (new_size > current_size && new_size > max_memsize)
+ return GIMP_IMAGE_SCALE_TOO_BIG;
+
+ all_layers = gimp_image_get_layer_list (image);
+
+ for (list = all_layers; list; list = g_list_next (list))
+ {
+ GimpItem *item = list->data;
+
+ /* group layers are updated automatically */
+ if (gimp_viewable_get_children (GIMP_VIEWABLE (item)))
+ continue;
+
+ if (! gimp_item_check_scaling (item, new_width, new_height))
+ {
+ g_list_free (all_layers);
+
+ return GIMP_IMAGE_SCALE_TOO_SMALL;
+ }
+ }
+
+ g_list_free (all_layers);
+
+ return GIMP_IMAGE_SCALE_OK;
+}
diff --git a/app/core/gimpimage-scale.h b/app/core/gimpimage-scale.h
new file mode 100644
index 0000000..1073e38
--- /dev/null
+++ b/app/core/gimpimage-scale.h
@@ -0,0 +1,36 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattisbvf
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_IMAGE_SCALE_H__
+#define __GIMP_IMAGE_SCALE_H__
+
+
+void gimp_image_scale (GimpImage *image,
+ gint new_width,
+ gint new_height,
+ GimpInterpolationType interpolation_type,
+ GimpProgress *progress);
+
+GimpImageScaleCheckType
+ gimp_image_scale_check (GimpImage *image,
+ gint new_width,
+ gint new_height,
+ gint64 max_memsize,
+ gint64 *new_memsize);
+
+
+#endif /* __GIMP_IMAGE_SCALE_H__ */
diff --git a/app/core/gimpimage-snap.c b/app/core/gimpimage-snap.c
new file mode 100644
index 0000000..e35b646
--- /dev/null
+++ b/app/core/gimpimage-snap.c
@@ -0,0 +1,719 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpmath/gimpmath.h"
+
+#include "core-types.h"
+
+#include "gimp.h"
+#include "gimpgrid.h"
+#include "gimpguide.h"
+#include "gimpimage.h"
+#include "gimpimage-grid.h"
+#include "gimpimage-guides.h"
+#include "gimpimage-snap.h"
+
+#include "vectors/gimpstroke.h"
+#include "vectors/gimpvectors.h"
+
+#include "gimp-intl.h"
+
+
+static gboolean gimp_image_snap_distance (const gdouble unsnapped,
+ const gdouble nearest,
+ const gdouble epsilon,
+ gdouble *mindist,
+ gdouble *target);
+
+
+
+/* public functions */
+
+gboolean
+gimp_image_snap_x (GimpImage *image,
+ gdouble x,
+ gdouble *tx,
+ gdouble epsilon_x,
+ gboolean snap_to_guides,
+ gboolean snap_to_grid,
+ gboolean snap_to_canvas)
+{
+ gdouble mindist = G_MAXDOUBLE;
+ gboolean snapped = FALSE;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);
+ g_return_val_if_fail (tx != NULL, FALSE);
+
+ *tx = x;
+
+ if (! gimp_image_get_guides (image)) snap_to_guides = FALSE;
+ if (! gimp_image_get_grid (image)) snap_to_grid = FALSE;
+
+ if (! (snap_to_guides || snap_to_grid || snap_to_canvas))
+ return FALSE;
+
+ if (x < -epsilon_x || x >= (gimp_image_get_width (image) + epsilon_x))
+ return FALSE;
+
+ if (snap_to_guides)
+ {
+ GList *list;
+
+ for (list = gimp_image_get_guides (image); list; list = g_list_next (list))
+ {
+ GimpGuide *guide = list->data;
+ gint position = gimp_guide_get_position (guide);
+
+ if (gimp_guide_is_custom (guide))
+ continue;
+
+ if (gimp_guide_get_orientation (guide) == GIMP_ORIENTATION_VERTICAL)
+ {
+ snapped |= gimp_image_snap_distance (x, position,
+ epsilon_x,
+ &mindist, tx);
+ }
+ }
+ }
+
+ if (snap_to_grid)
+ {
+ GimpGrid *grid = gimp_image_get_grid (image);
+ gdouble xspacing;
+ gdouble xoffset;
+
+ gimp_grid_get_spacing (grid, &xspacing, NULL);
+ gimp_grid_get_offset (grid, &xoffset, NULL);
+
+ if (xspacing > 0.0)
+ {
+ gdouble nearest;
+
+ nearest = xoffset + RINT ((x - xoffset) / xspacing) * xspacing;
+
+ snapped |= gimp_image_snap_distance (x, nearest,
+ epsilon_x,
+ &mindist, tx);
+ }
+ }
+
+ if (snap_to_canvas)
+ {
+ snapped |= gimp_image_snap_distance (x, 0,
+ epsilon_x,
+ &mindist, tx);
+ snapped |= gimp_image_snap_distance (x, gimp_image_get_width (image),
+ epsilon_x,
+ &mindist, tx);
+ }
+
+ return snapped;
+}
+
+gboolean
+gimp_image_snap_y (GimpImage *image,
+ gdouble y,
+ gdouble *ty,
+ gdouble epsilon_y,
+ gboolean snap_to_guides,
+ gboolean snap_to_grid,
+ gboolean snap_to_canvas)
+{
+ gdouble mindist = G_MAXDOUBLE;
+ gboolean snapped = FALSE;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);
+ g_return_val_if_fail (ty != NULL, FALSE);
+
+ *ty = y;
+
+ if (! gimp_image_get_guides (image)) snap_to_guides = FALSE;
+ if (! gimp_image_get_grid (image)) snap_to_grid = FALSE;
+
+ if (! (snap_to_guides || snap_to_grid || snap_to_canvas))
+ return FALSE;
+
+ if (y < -epsilon_y || y >= (gimp_image_get_height (image) + epsilon_y))
+ return FALSE;
+
+ if (snap_to_guides)
+ {
+ GList *list;
+
+ for (list = gimp_image_get_guides (image); list; list = g_list_next (list))
+ {
+ GimpGuide *guide = list->data;
+ gint position = gimp_guide_get_position (guide);
+
+ if (gimp_guide_is_custom (guide))
+ continue;
+
+ if (gimp_guide_get_orientation (guide) == GIMP_ORIENTATION_HORIZONTAL)
+ {
+ snapped |= gimp_image_snap_distance (y, position,
+ epsilon_y,
+ &mindist, ty);
+ }
+ }
+ }
+
+ if (snap_to_grid)
+ {
+ GimpGrid *grid = gimp_image_get_grid (image);
+ gdouble yspacing;
+ gdouble yoffset;
+
+ gimp_grid_get_spacing (grid, NULL, &yspacing);
+ gimp_grid_get_offset (grid, NULL, &yoffset);
+
+ if (yspacing > 0.0)
+ {
+ gdouble nearest;
+
+ nearest = yoffset + RINT ((y - yoffset) / yspacing) * yspacing;
+
+ snapped |= gimp_image_snap_distance (y, nearest,
+ epsilon_y,
+ &mindist, ty);
+ }
+ }
+
+ if (snap_to_canvas)
+ {
+ snapped |= gimp_image_snap_distance (y, 0,
+ epsilon_y,
+ &mindist, ty);
+ snapped |= gimp_image_snap_distance (y, gimp_image_get_height (image),
+ epsilon_y,
+ &mindist, ty);
+ }
+
+ return snapped;
+}
+
+gboolean
+gimp_image_snap_point (GimpImage *image,
+ gdouble x,
+ gdouble y,
+ gdouble *tx,
+ gdouble *ty,
+ gdouble epsilon_x,
+ gdouble epsilon_y,
+ gboolean snap_to_guides,
+ gboolean snap_to_grid,
+ gboolean snap_to_canvas,
+ gboolean snap_to_vectors,
+ gboolean show_all)
+{
+ gdouble mindist_x = G_MAXDOUBLE;
+ gdouble mindist_y = G_MAXDOUBLE;
+ gboolean snapped = FALSE;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);
+ g_return_val_if_fail (tx != NULL, FALSE);
+ g_return_val_if_fail (ty != NULL, FALSE);
+
+ *tx = x;
+ *ty = y;
+
+ if (! gimp_image_get_guides (image)) snap_to_guides = FALSE;
+ if (! gimp_image_get_grid (image)) snap_to_grid = FALSE;
+ if (! gimp_image_get_active_vectors (image)) snap_to_vectors = FALSE;
+
+ if (! (snap_to_guides || snap_to_grid || snap_to_canvas || snap_to_vectors))
+ return FALSE;
+
+ if (! show_all &&
+ (x < -epsilon_x || x >= (gimp_image_get_width (image) + epsilon_x) ||
+ y < -epsilon_y || y >= (gimp_image_get_height (image) + epsilon_y)))
+ {
+ /* Off-canvas grid is invisible unless "show all" option is
+ * enabled. So let's not snap to the invisible grid.
+ */
+ snap_to_grid = FALSE;
+ snap_to_canvas = FALSE;
+ }
+
+ if (snap_to_guides)
+ {
+ GList *list;
+
+ for (list = gimp_image_get_guides (image); list; list = g_list_next (list))
+ {
+ GimpGuide *guide = list->data;
+ gint position = gimp_guide_get_position (guide);
+
+ if (gimp_guide_is_custom (guide))
+ continue;
+
+ switch (gimp_guide_get_orientation (guide))
+ {
+ case GIMP_ORIENTATION_HORIZONTAL:
+ snapped |= gimp_image_snap_distance (y, position,
+ epsilon_y,
+ &mindist_y, ty);
+ break;
+
+ case GIMP_ORIENTATION_VERTICAL:
+ snapped |= gimp_image_snap_distance (x, position,
+ epsilon_x,
+ &mindist_x, tx);
+ break;
+
+ default:
+ break;
+ }
+ }
+ }
+
+ if (snap_to_grid)
+ {
+ GimpGrid *grid = gimp_image_get_grid (image);
+ gdouble xspacing, yspacing;
+ gdouble xoffset, yoffset;
+
+ gimp_grid_get_spacing (grid, &xspacing, &yspacing);
+ gimp_grid_get_offset (grid, &xoffset, &yoffset);
+
+ if (xspacing > 0.0)
+ {
+ gdouble nearest;
+
+ nearest = xoffset + RINT ((x - xoffset) / xspacing) * xspacing;
+
+ snapped |= gimp_image_snap_distance (x, nearest,
+ epsilon_x,
+ &mindist_x, tx);
+ }
+
+ if (yspacing > 0.0)
+ {
+ gdouble nearest;
+
+ nearest = yoffset + RINT ((y - yoffset) / yspacing) * yspacing;
+
+ snapped |= gimp_image_snap_distance (y, nearest,
+ epsilon_y,
+ &mindist_y, ty);
+ }
+ }
+
+ if (snap_to_canvas)
+ {
+ snapped |= gimp_image_snap_distance (x, 0,
+ epsilon_x,
+ &mindist_x, tx);
+ snapped |= gimp_image_snap_distance (x, gimp_image_get_width (image),
+ epsilon_x,
+ &mindist_x, tx);
+
+ snapped |= gimp_image_snap_distance (y, 0,
+ epsilon_y,
+ &mindist_y, ty);
+ snapped |= gimp_image_snap_distance (y, gimp_image_get_height (image),
+ epsilon_y,
+ &mindist_y, ty);
+ }
+
+ if (snap_to_vectors)
+ {
+ GimpVectors *vectors = gimp_image_get_active_vectors (image);
+ GimpStroke *stroke = NULL;
+ GimpCoords coords = { 0, 0, 0, 0, 0 };
+
+ coords.x = x;
+ coords.y = y;
+
+ while ((stroke = gimp_vectors_stroke_get_next (vectors, stroke)))
+ {
+ GimpCoords nearest;
+
+ if (gimp_stroke_nearest_point_get (stroke, &coords, 1.0,
+ &nearest,
+ NULL, NULL, NULL) >= 0)
+ {
+ snapped |= gimp_image_snap_distance (x, nearest.x,
+ epsilon_x,
+ &mindist_x, tx);
+ snapped |= gimp_image_snap_distance (y, nearest.y,
+ epsilon_y,
+ &mindist_y, ty);
+ }
+ }
+ }
+
+ return snapped;
+}
+
+gboolean
+gimp_image_snap_rectangle (GimpImage *image,
+ gdouble x1,
+ gdouble y1,
+ gdouble x2,
+ gdouble y2,
+ gdouble *tx1,
+ gdouble *ty1,
+ gdouble epsilon_x,
+ gdouble epsilon_y,
+ gboolean snap_to_guides,
+ gboolean snap_to_grid,
+ gboolean snap_to_canvas,
+ gboolean snap_to_vectors)
+{
+ gdouble nx, ny;
+ gdouble mindist_x = G_MAXDOUBLE;
+ gdouble mindist_y = G_MAXDOUBLE;
+ gdouble x_center = (x1 + x2) / 2.0;
+ gdouble y_center = (y1 + y2) / 2.0;
+ gboolean snapped = FALSE;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);
+ g_return_val_if_fail (tx1 != NULL, FALSE);
+ g_return_val_if_fail (ty1 != NULL, FALSE);
+
+ *tx1 = x1;
+ *ty1 = y1;
+
+ if (! gimp_image_get_guides (image)) snap_to_guides = FALSE;
+ if (! gimp_image_get_grid (image)) snap_to_grid = FALSE;
+ if (! gimp_image_get_active_vectors (image)) snap_to_vectors = FALSE;
+
+ if (! (snap_to_guides || snap_to_grid || snap_to_canvas || snap_to_vectors))
+ return FALSE;
+
+ /* left edge */
+ if (gimp_image_snap_x (image, x1, &nx,
+ MIN (epsilon_x, mindist_x),
+ snap_to_guides,
+ snap_to_grid,
+ snap_to_canvas))
+ {
+ mindist_x = ABS (nx - x1);
+ *tx1 = nx;
+ snapped = TRUE;
+ }
+
+ /* right edge */
+ if (gimp_image_snap_x (image, x2, &nx,
+ MIN (epsilon_x, mindist_x),
+ snap_to_guides,
+ snap_to_grid,
+ snap_to_canvas))
+ {
+ mindist_x = ABS (nx - x2);
+ *tx1 = RINT (x1 + (nx - x2));
+ snapped = TRUE;
+ }
+
+ /* center, vertical */
+ if (gimp_image_snap_x (image, x_center, &nx,
+ MIN (epsilon_x, mindist_x),
+ snap_to_guides,
+ snap_to_grid,
+ snap_to_canvas))
+ {
+ mindist_x = ABS (nx - x_center);
+ *tx1 = RINT (x1 + (nx - x_center));
+ snapped = TRUE;
+ }
+
+ /* top edge */
+ if (gimp_image_snap_y (image, y1, &ny,
+ MIN (epsilon_y, mindist_y),
+ snap_to_guides,
+ snap_to_grid,
+ snap_to_canvas))
+ {
+ mindist_y = ABS (ny - y1);
+ *ty1 = ny;
+ snapped = TRUE;
+ }
+
+ /* bottom edge */
+ if (gimp_image_snap_y (image, y2, &ny,
+ MIN (epsilon_y, mindist_y),
+ snap_to_guides,
+ snap_to_grid,
+ snap_to_canvas))
+ {
+ mindist_y = ABS (ny - y2);
+ *ty1 = RINT (y1 + (ny - y2));
+ snapped = TRUE;
+ }
+
+ /* center, horizontal */
+ if (gimp_image_snap_y (image, y_center, &ny,
+ MIN (epsilon_y, mindist_y),
+ snap_to_guides,
+ snap_to_grid,
+ snap_to_canvas))
+ {
+ mindist_y = ABS (ny - y_center);
+ *ty1 = RINT (y1 + (ny - y_center));
+ snapped = TRUE;
+ }
+
+ if (snap_to_vectors)
+ {
+ GimpVectors *vectors = gimp_image_get_active_vectors (image);
+ GimpStroke *stroke = NULL;
+ GimpCoords coords1 = GIMP_COORDS_DEFAULT_VALUES;
+ GimpCoords coords2 = GIMP_COORDS_DEFAULT_VALUES;
+
+ while ((stroke = gimp_vectors_stroke_get_next (vectors, stroke)))
+ {
+ GimpCoords nearest;
+ gdouble dist;
+
+ /* top edge */
+
+ coords1.x = x1;
+ coords1.y = y1;
+ coords2.x = x2;
+ coords2.y = y1;
+
+ if (gimp_stroke_nearest_tangent_get (stroke, &coords1, &coords2,
+ 1.0, &nearest,
+ NULL, NULL, NULL) >= 0)
+ {
+ snapped |= gimp_image_snap_distance (y1, nearest.y,
+ epsilon_y,
+ &mindist_y, ty1);
+ }
+
+ if (gimp_stroke_nearest_intersection_get (stroke, &coords1, &coords2,
+ 1.0, &nearest,
+ NULL, NULL, NULL) >= 0)
+ {
+ snapped |= gimp_image_snap_distance (x1, nearest.x,
+ epsilon_x,
+ &mindist_x, tx1);
+ }
+
+ if (gimp_stroke_nearest_intersection_get (stroke, &coords2, &coords1,
+ 1.0, &nearest,
+ NULL, NULL, NULL) >= 0)
+ {
+ dist = ABS (nearest.x - x2);
+
+ if (dist < MIN (epsilon_x, mindist_x))
+ {
+ mindist_x = dist;
+ *tx1 = RINT (x1 + (nearest.x - x2));
+ snapped = TRUE;
+ }
+ }
+
+ /* bottom edge */
+
+ coords1.x = x1;
+ coords1.y = y2;
+ coords2.x = x2;
+ coords2.y = y2;
+
+ if (gimp_stroke_nearest_tangent_get (stroke, &coords1, &coords2,
+ 1.0, &nearest,
+ NULL, NULL, NULL) >= 0)
+ {
+ dist = ABS (nearest.y - y2);
+
+ if (dist < MIN (epsilon_y, mindist_y))
+ {
+ mindist_y = dist;
+ *ty1 = RINT (y1 + (nearest.y - y2));
+ snapped = TRUE;
+ }
+ }
+
+ if (gimp_stroke_nearest_intersection_get (stroke, &coords1, &coords2,
+ 1.0, &nearest,
+ NULL, NULL, NULL) >= 0)
+ {
+ snapped |= gimp_image_snap_distance (x1, nearest.x,
+ epsilon_x,
+ &mindist_x, tx1);
+ }
+
+ if (gimp_stroke_nearest_intersection_get (stroke, &coords2, &coords1,
+ 1.0, &nearest,
+ NULL, NULL, NULL) >= 0)
+ {
+ dist = ABS (nearest.x - x2);
+
+ if (dist < MIN (epsilon_x, mindist_x))
+ {
+ mindist_x = dist;
+ *tx1 = RINT (x1 + (nearest.x - x2));
+ snapped = TRUE;
+ }
+ }
+
+ /* left edge */
+
+ coords1.x = x1;
+ coords1.y = y1;
+ coords2.x = x1;
+ coords2.y = y2;
+
+ if (gimp_stroke_nearest_tangent_get (stroke, &coords1, &coords2,
+ 1.0, &nearest,
+ NULL, NULL, NULL) >= 0)
+ {
+ snapped |= gimp_image_snap_distance (x1, nearest.x,
+ epsilon_x,
+ &mindist_x, tx1);
+ }
+
+ if (gimp_stroke_nearest_intersection_get (stroke, &coords1, &coords2,
+ 1.0, &nearest,
+ NULL, NULL, NULL) >= 0)
+ {
+ snapped |= gimp_image_snap_distance (y1, nearest.y,
+ epsilon_y,
+ &mindist_y, ty1);
+ }
+
+ if (gimp_stroke_nearest_intersection_get (stroke, &coords2, &coords1,
+ 1.0, &nearest,
+ NULL, NULL, NULL) >= 0)
+ {
+ dist = ABS (nearest.y - y2);
+
+ if (dist < MIN (epsilon_y, mindist_y))
+ {
+ mindist_y = dist;
+ *ty1 = RINT (y1 + (nearest.y - y2));
+ snapped = TRUE;
+ }
+ }
+
+ /* right edge */
+
+ coords1.x = x2;
+ coords1.y = y1;
+ coords2.x = x2;
+ coords2.y = y2;
+
+ if (gimp_stroke_nearest_tangent_get (stroke, &coords1, &coords2,
+ 1.0, &nearest,
+ NULL, NULL, NULL) >= 0)
+ {
+ dist = ABS (nearest.x - x2);
+
+ if (dist < MIN (epsilon_x, mindist_x))
+ {
+ mindist_x = dist;
+ *tx1 = RINT (x1 + (nearest.x - x2));
+ snapped = TRUE;
+ }
+ }
+
+ if (gimp_stroke_nearest_intersection_get (stroke, &coords1, &coords2,
+ 1.0, &nearest,
+ NULL, NULL, NULL) >= 0)
+ {
+ snapped |= gimp_image_snap_distance (y1, nearest.y,
+ epsilon_y,
+ &mindist_y, ty1);
+ }
+
+ if (gimp_stroke_nearest_intersection_get (stroke, &coords2, &coords1,
+ 1.0, &nearest,
+ NULL, NULL, NULL) >= 0)
+ {
+ dist = ABS (nearest.y - y2);
+
+ if (dist < MIN (epsilon_y, mindist_y))
+ {
+ mindist_y = dist;
+ *ty1 = RINT (y1 + (nearest.y - y2));
+ snapped = TRUE;
+ }
+ }
+
+ /* center */
+
+ coords1.x = x_center;
+ coords1.y = y_center;
+
+ if (gimp_stroke_nearest_point_get (stroke, &coords1, 1.0,
+ &nearest,
+ NULL, NULL, NULL) >= 0)
+ {
+ if (gimp_image_snap_distance (x_center, nearest.x,
+ epsilon_x,
+ &mindist_x, &nx))
+ {
+ mindist_x = ABS (nx - x_center);
+ *tx1 = RINT (x1 + (nx - x_center));
+ snapped = TRUE;
+ }
+
+ if (gimp_image_snap_distance (y_center, nearest.y,
+ epsilon_y,
+ &mindist_y, &ny))
+ {
+ mindist_y = ABS (ny - y_center);
+ *ty1 = RINT (y1 + (ny - y_center));
+ snapped = TRUE;
+ }
+ }
+ }
+ }
+
+ return snapped;
+}
+
+/* private functions */
+
+/**
+ * gimp_image_snap_distance:
+ * @unsnapped: One coordinate of the unsnapped position
+ * @nearest: One coordinate of a snapping position candidate
+ * @epsilon: The snapping threshold
+ * @mindist: The distance to the currently closest snapping target
+ * @target: The currently closest snapping target
+ *
+ * Finds out if snapping occurs from position to a snapping candidate
+ * and sets the target accordingly.
+ *
+ * Return value: %TRUE if snapping occurred, %FALSE otherwise
+ */
+static gboolean
+gimp_image_snap_distance (const gdouble unsnapped,
+ const gdouble nearest,
+ const gdouble epsilon,
+ gdouble *mindist,
+ gdouble *target)
+{
+ const gdouble dist = ABS (nearest - unsnapped);
+
+ if (dist < MIN (epsilon, *mindist))
+ {
+ *mindist = dist;
+ *target = nearest;
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
diff --git a/app/core/gimpimage-snap.h b/app/core/gimpimage-snap.h
new file mode 100644
index 0000000..b0d5e4b
--- /dev/null
+++ b/app/core/gimpimage-snap.h
@@ -0,0 +1,63 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattisbvf
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_IMAGE_SNAP_H__
+#define __GIMP_IMAGE_SNAP_H__
+
+
+gboolean gimp_image_snap_x (GimpImage *image,
+ gdouble x,
+ gdouble *tx,
+ gdouble epsilon_x,
+ gboolean snap_to_guides,
+ gboolean snap_to_grid,
+ gboolean snap_to_canvas);
+gboolean gimp_image_snap_y (GimpImage *image,
+ gdouble y,
+ gdouble *ty,
+ gdouble epsilon_y,
+ gboolean snap_to_guides,
+ gboolean snap_to_grid,
+ gboolean snap_to_canvas);
+gboolean gimp_image_snap_point (GimpImage *image,
+ gdouble x,
+ gdouble y,
+ gdouble *tx,
+ gdouble *ty,
+ gdouble epsilon_x,
+ gdouble epsilon_y,
+ gboolean snap_to_guides,
+ gboolean snap_to_grid,
+ gboolean snap_to_canvas,
+ gboolean snap_to_vectors,
+ gboolean show_all);
+gboolean gimp_image_snap_rectangle (GimpImage *image,
+ gdouble x1,
+ gdouble y1,
+ gdouble x2,
+ gdouble y2,
+ gdouble *tx1,
+ gdouble *ty1,
+ gdouble epsilon_x,
+ gdouble epsilon_y,
+ gboolean snap_to_guides,
+ gboolean snap_to_grid,
+ gboolean snap_to_canvas,
+ gboolean snap_to_vectors);
+
+
+#endif /* __GIMP_IMAGE_SNAP_H__ */
diff --git a/app/core/gimpimage-symmetry.c b/app/core/gimpimage-symmetry.c
new file mode 100644
index 0000000..366f63e
--- /dev/null
+++ b/app/core/gimpimage-symmetry.c
@@ -0,0 +1,189 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpimage-symmetry.c
+ * Copyright (C) 2015 Jehan <jehan@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+#include "core-types.h"
+
+#include "gimpsymmetry.h"
+#include "gimpimage.h"
+#include "gimpimage-private.h"
+#include "gimpimage-symmetry.h"
+#include "gimpsymmetry-mandala.h"
+#include "gimpsymmetry-mirror.h"
+#include "gimpsymmetry-tiling.h"
+
+
+/**
+ * gimp_image_symmetry_list:
+ *
+ * Returns a list of #GType of all existing symmetries.
+ **/
+GList *
+gimp_image_symmetry_list (void)
+{
+ GList *list = NULL;
+
+ list = g_list_prepend (list, GINT_TO_POINTER (GIMP_TYPE_MIRROR));
+ list = g_list_prepend (list, GINT_TO_POINTER (GIMP_TYPE_TILING));
+ list = g_list_prepend (list, GINT_TO_POINTER (GIMP_TYPE_MANDALA));
+
+ return list;
+}
+
+/**
+ * gimp_image_symmetry_new:
+ * @image: the #GimpImage
+ * @type: the #GType of the symmetry
+ *
+ * Creates a new #GimpSymmetry of @type attached to @image.
+ * @type must be a subtype of `GIMP_TYPE_SYMMETRY`.
+ * Note that using the base @type `GIMP_TYPE_SYMMETRY` creates an
+ * identity transformation.
+ *
+ * Returns: the new #GimpSymmetry.
+ **/
+GimpSymmetry *
+gimp_image_symmetry_new (GimpImage *image,
+ GType type)
+{
+ GimpSymmetry *sym = NULL;
+
+ g_return_val_if_fail (g_type_is_a (type, GIMP_TYPE_SYMMETRY), NULL);
+
+ sym = g_object_new (type,
+ "image", image,
+ NULL);
+
+ return sym;
+}
+
+/**
+ * gimp_image_symmetry_add:
+ * @image: the #GimpImage
+ * @type: the #GType of the symmetry
+ *
+ * Add a symmetry of type @type to @image and make it the
+ * active transformation.
+ **/
+void
+gimp_image_symmetry_add (GimpImage *image,
+ GimpSymmetry *sym)
+{
+ GimpImagePrivate *private;
+
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+ g_return_if_fail (GIMP_IS_SYMMETRY (sym));
+
+ private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ private->symmetries = g_list_prepend (private->symmetries,
+ g_object_ref (sym));
+}
+
+/**
+ * gimp_image_symmetry_remove:
+ * @image: the #GimpImage
+ * @sym: the #GimpSymmetry
+ *
+ * Remove @sym from the list of symmetries of @image.
+ * If it was the active transformation, unselect it first.
+ **/
+void
+gimp_image_symmetry_remove (GimpImage *image,
+ GimpSymmetry *sym)
+{
+ GimpImagePrivate *private;
+
+ g_return_if_fail (GIMP_IS_SYMMETRY (sym));
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+
+ private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ if (private->active_symmetry == sym)
+ gimp_image_set_active_symmetry (image, GIMP_TYPE_SYMMETRY);
+
+ private->symmetries = g_list_remove (private->symmetries, sym);
+ g_object_unref (sym);
+}
+
+/**
+ * gimp_image_symmetry_get:
+ * @image: the #GimpImage
+ *
+ * Returns: the list of #GimpSymmetry set on @image.
+ * The returned list belongs to @image and should not be freed.
+ **/
+GList *
+gimp_image_symmetry_get (GimpImage *image)
+{
+ GimpImagePrivate *private;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);
+
+ private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ return private->symmetries;
+}
+
+/**
+ * gimp_image_set_active_symmetry:
+ * @image: the #GimpImage
+ * @type: the #GType of the symmetry
+ *
+ * Select the symmetry of type @type.
+ * Using the GType allows to select a transformation without
+ * knowing whether one of the same @type was already created.
+ *
+ * Returns TRUE on success, FALSE if no such symmetry was found.
+ **/
+gboolean
+gimp_image_set_active_symmetry (GimpImage *image,
+ GType type)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);
+
+ g_object_set (image,
+ "symmetry", type,
+ NULL);
+
+ return TRUE;
+}
+
+/**
+ * gimp_image_get_active_symmetry:
+ * @image: the #GimpImage
+ *
+ * Returns the #GimpSymmetry transformation active on @image.
+ **/
+GimpSymmetry *
+gimp_image_get_active_symmetry (GimpImage *image)
+{
+ GimpImagePrivate *private;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);
+
+ private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ return private->active_symmetry;
+}
diff --git a/app/core/gimpimage-symmetry.h b/app/core/gimpimage-symmetry.h
new file mode 100644
index 0000000..9211382
--- /dev/null
+++ b/app/core/gimpimage-symmetry.h
@@ -0,0 +1,40 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpimage-symmetry.h
+ * Copyright (C) 2015 Jehan <jehan@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_IMAGE_SYMMETRY_H__
+#define __GIMP_IMAGE_SYMMETRY_H__
+
+
+GList * gimp_image_symmetry_list (void);
+
+GimpSymmetry * gimp_image_symmetry_new (GimpImage *image,
+ GType type);
+void gimp_image_symmetry_add (GimpImage *image,
+ GimpSymmetry *sym);
+void gimp_image_symmetry_remove (GimpImage *image,
+ GimpSymmetry *sym);
+GList * gimp_image_symmetry_get (GimpImage *image);
+
+gboolean gimp_image_set_active_symmetry (GimpImage *image,
+ GType type);
+GimpSymmetry * gimp_image_get_active_symmetry (GimpImage *image);
+
+
+#endif /* __GIMP_IMAGE_SYMMETRY_H__ */
diff --git a/app/core/gimpimage-transform.c b/app/core/gimpimage-transform.c
new file mode 100644
index 0000000..afe4cfc
--- /dev/null
+++ b/app/core/gimpimage-transform.c
@@ -0,0 +1,338 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpimage-transform.c
+ * Copyright (C) 2019 Ell
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpmath/gimpmath.h"
+
+#include "core-types.h"
+
+#include "vectors/gimpvectors.h"
+
+#include "gimp.h"
+#include "gimp-transform-resize.h"
+#include "gimp-transform-utils.h"
+#include "gimpchannel.h"
+#include "gimpcontext.h"
+#include "gimpguide.h"
+#include "gimpimage.h"
+#include "gimpimage-guides.h"
+#include "gimpimage-sample-points.h"
+#include "gimpimage-transform.h"
+#include "gimpimage-undo.h"
+#include "gimpimage-undo-push.h"
+#include "gimpitem.h"
+#include "gimpobjectqueue.h"
+#include "gimpprogress.h"
+#include "gimpsamplepoint.h"
+
+
+#define EPSILON 1e-6
+
+
+/* local function prototypes */
+
+static void gimp_image_transform_guides (GimpImage *image,
+ const GimpMatrix3 *matrix,
+ const GeglRectangle *old_bounds);
+static void gimp_image_transform_sample_points (GimpImage *image,
+ const GimpMatrix3 *matrix,
+ const GeglRectangle *old_bounds);
+
+
+/* private functions */
+
+static void
+gimp_image_transform_guides (GimpImage *image,
+ const GimpMatrix3 *matrix,
+ const GeglRectangle *old_bounds)
+{
+ GList *iter;
+
+ for (iter = gimp_image_get_guides (image); iter;)
+ {
+ GimpGuide *guide = iter->data;
+ GimpOrientationType old_orientation = gimp_guide_get_orientation (guide);
+ gint old_position = gimp_guide_get_position (guide);
+ GimpOrientationType new_orientation;
+ gint new_position;
+ GimpVector2 vertices[2];
+ gint n_vertices;
+ GimpVector2 diff;
+
+ iter = g_list_next (iter);
+
+ switch (old_orientation)
+ {
+ case GIMP_ORIENTATION_HORIZONTAL:
+ vertices[0].x = old_bounds->x;
+ vertices[0].y = old_bounds->y + old_position;
+
+ vertices[1].x = old_bounds->x + old_bounds->width / 2.0;
+ vertices[1].y = old_bounds->y + old_position;
+ break;
+
+ case GIMP_ORIENTATION_VERTICAL:
+ vertices[0].x = old_bounds->x + old_position;
+ vertices[0].y = old_bounds->y;
+
+ vertices[1].x = old_bounds->x + old_position;
+ vertices[1].y = old_bounds->y + old_bounds->height / 2.0;
+ break;
+
+ case GIMP_ORIENTATION_UNKNOWN:
+ g_return_if_reached ();
+ }
+
+ gimp_transform_polygon (matrix,
+ vertices, 2, FALSE,
+ vertices, &n_vertices);
+
+ if (n_vertices < 2)
+ {
+ gimp_image_remove_guide (image, guide, TRUE);
+
+ continue;
+ }
+
+ gimp_vector2_sub (&diff, &vertices[1], &vertices[0]);
+
+ if (gimp_vector2_length (&diff) <= EPSILON)
+ {
+ gimp_image_remove_guide (image, guide, TRUE);
+
+ continue;
+ }
+
+ if (fabs (diff.x) >= fabs (diff.y))
+ {
+ new_orientation = GIMP_ORIENTATION_HORIZONTAL;
+ new_position = SIGNED_ROUND (vertices[1].y);
+
+ if (new_position < 0 || new_position > gimp_image_get_height (image))
+ {
+ gimp_image_remove_guide (image, guide, TRUE);
+
+ continue;
+ }
+ }
+ else
+ {
+ new_orientation = GIMP_ORIENTATION_VERTICAL;
+ new_position = SIGNED_ROUND (vertices[1].x);
+
+ if (new_position < 0 || new_position > gimp_image_get_width (image))
+ {
+ gimp_image_remove_guide (image, guide, TRUE);
+
+ continue;
+ }
+ }
+
+ if (new_orientation != old_orientation ||
+ new_position != old_position)
+ {
+ gimp_image_undo_push_guide (image, NULL, guide);
+
+ gimp_guide_set_orientation (guide, new_orientation);
+ gimp_guide_set_position (guide, new_position);
+
+ gimp_image_guide_moved (image, guide);
+ }
+ }
+}
+
+static void
+gimp_image_transform_sample_points (GimpImage *image,
+ const GimpMatrix3 *matrix,
+ const GeglRectangle *old_bounds)
+{
+ GList *iter;
+
+ for (iter = gimp_image_get_sample_points (image); iter;)
+ {
+ GimpSamplePoint *sample_point = iter->data;
+ gint old_x;
+ gint old_y;
+ gint new_x;
+ gint new_y;
+ GimpVector2 vertices[1];
+ gint n_vertices;
+
+ iter = g_list_next (iter);
+
+ gimp_sample_point_get_position (sample_point, &old_x, &old_y);
+
+ vertices[0].x = old_x;
+ vertices[0].y = old_y;
+
+ gimp_transform_polygon (matrix,
+ vertices, 1, FALSE,
+ vertices, &n_vertices);
+
+ if (n_vertices < 1)
+ {
+ gimp_image_remove_sample_point (image, sample_point, TRUE);
+
+ continue;
+ }
+
+ new_x = SIGNED_ROUND (vertices[0].x);
+ new_y = SIGNED_ROUND (vertices[0].y);
+
+ if (new_x < 0 || new_x >= gimp_image_get_width (image) ||
+ new_y < 0 || new_y >= gimp_image_get_height (image))
+ {
+ gimp_image_remove_sample_point (image, sample_point, TRUE);
+
+ continue;
+ }
+
+ if (new_x != old_x || new_y != old_y)
+ gimp_image_move_sample_point (image, sample_point, new_x, new_y, TRUE);
+ }
+}
+
+
+/* public functions */
+
+void
+gimp_image_transform (GimpImage *image,
+ GimpContext *context,
+ const GimpMatrix3 *matrix,
+ GimpTransformDirection direction,
+ GimpInterpolationType interpolation_type,
+ GimpTransformResize clip_result,
+ GimpProgress *progress)
+{
+ GimpObjectQueue *queue;
+ GimpItem *item;
+ GimpMatrix3 transform;
+ GeglRectangle old_bounds;
+ GeglRectangle new_bounds;
+
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+ g_return_if_fail (GIMP_IS_CONTEXT (context));
+ g_return_if_fail (matrix != NULL);
+ g_return_if_fail (progress == NULL || GIMP_IS_PROGRESS (progress));
+
+ gimp_set_busy (image->gimp);
+
+ old_bounds.x = 0;
+ old_bounds.y = 0;
+ old_bounds.width = gimp_image_get_width (image);
+ old_bounds.height = gimp_image_get_height (image);
+
+ transform = *matrix;
+
+ if (direction == GIMP_TRANSFORM_BACKWARD)
+ gimp_matrix3_invert (&transform);
+
+ gimp_transform_resize_boundary (&transform, clip_result,
+
+ old_bounds.x,
+ old_bounds.y,
+ old_bounds.x + old_bounds.width,
+ old_bounds.y + old_bounds.height,
+
+ &new_bounds.x,
+ &new_bounds.y,
+ &new_bounds.width,
+ &new_bounds.height);
+
+ new_bounds.width -= new_bounds.x;
+ new_bounds.height -= new_bounds.y;
+
+ gimp_matrix3_translate (&transform,
+ old_bounds.x - new_bounds.x,
+ old_bounds.y - new_bounds.y);
+
+ queue = gimp_object_queue_new (progress);
+ progress = GIMP_PROGRESS (queue);
+
+ gimp_object_queue_push_container (queue, gimp_image_get_layers (image));
+ gimp_object_queue_push (queue, gimp_image_get_mask (image));
+ gimp_object_queue_push_container (queue, gimp_image_get_channels (image));
+ gimp_object_queue_push_container (queue, gimp_image_get_vectors (image));
+
+ g_object_freeze_notify (G_OBJECT (image));
+
+ gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_IMAGE_TRANSFORM, NULL);
+
+ /* Transform all layers, channels (including selection mask), and vectors */
+ while ((item = gimp_object_queue_pop (queue)))
+ {
+ GimpTransformResize clip = GIMP_TRANSFORM_RESIZE_ADJUST;
+
+ if (GIMP_IS_CHANNEL (item))
+ clip = clip_result;
+
+ gimp_item_transform (item,
+ context,
+ &transform, direction,
+ interpolation_type, clip,
+ progress);
+
+ if (GIMP_IS_VECTORS (item))
+ gimp_item_set_size (item, new_bounds.width, new_bounds.height);
+ }
+
+ /* Resize the image (if needed) */
+ if (! gegl_rectangle_equal (&new_bounds, &old_bounds))
+ {
+ gimp_image_undo_push_image_size (image,
+ NULL,
+ new_bounds.x,
+ new_bounds.y,
+ new_bounds.width,
+ new_bounds.height);
+
+ g_object_set (image,
+ "width", new_bounds.width,
+ "height", new_bounds.height,
+ NULL);
+ }
+
+ /* Transform all Guides */
+ gimp_image_transform_guides (image, &transform, &old_bounds);
+
+ /* Transform all sample points */
+ gimp_image_transform_sample_points (image, &transform, &old_bounds);
+
+ gimp_image_undo_group_end (image);
+
+ g_object_unref (queue);
+
+ if (! gegl_rectangle_equal (&new_bounds, &old_bounds))
+ {
+ gimp_image_size_changed_detailed (image,
+ old_bounds.x - new_bounds.x,
+ old_bounds.y - new_bounds.y,
+ old_bounds.width,
+ old_bounds.height);
+ }
+
+ g_object_thaw_notify (G_OBJECT (image));
+
+ gimp_unset_busy (image->gimp);
+}
diff --git a/app/core/gimpimage-transform.h b/app/core/gimpimage-transform.h
new file mode 100644
index 0000000..63a851d
--- /dev/null
+++ b/app/core/gimpimage-transform.h
@@ -0,0 +1,34 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattisbvf
+ *
+ * gimpimage-transform.h
+ * Copyright (C) 2019 Ell
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_IMAGE_TRANSFORM_H__
+#define __GIMP_IMAGE_TRANSFORM_H__
+
+
+void gimp_image_transform (GimpImage *image,
+ GimpContext *context,
+ const GimpMatrix3 *matrix,
+ GimpTransformDirection direction,
+ GimpInterpolationType interpolation_type,
+ GimpTransformResize clip_result,
+ GimpProgress *progress);
+
+
+#endif /* __GIMP_IMAGE_TRANSFORM_H__ */
diff --git a/app/core/gimpimage-undo-push.c b/app/core/gimpimage-undo-push.c
new file mode 100644
index 0000000..b96c4b4
--- /dev/null
+++ b/app/core/gimpimage-undo-push.c
@@ -0,0 +1,1060 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpbase/gimpbase.h"
+
+#include "core-types.h"
+
+#include "gimp.h"
+#include "gimpchannelpropundo.h"
+#include "gimpchannelundo.h"
+#include "gimpdrawablemodundo.h"
+#include "gimpdrawableundo.h"
+#include "gimpfloatingselectionundo.h"
+#include "gimpgrid.h"
+#include "gimpgrouplayer.h"
+#include "gimpgrouplayerundo.h"
+#include "gimpguide.h"
+#include "gimpguideundo.h"
+#include "gimpimage.h"
+#include "gimpimage-undo.h"
+#include "gimpimage-undo-push.h"
+#include "gimpimageundo.h"
+#include "gimpitempropundo.h"
+#include "gimplayermask.h"
+#include "gimplayermaskpropundo.h"
+#include "gimplayermaskundo.h"
+#include "gimplayerpropundo.h"
+#include "gimplayerundo.h"
+#include "gimpmaskundo.h"
+#include "gimpsamplepoint.h"
+#include "gimpsamplepointundo.h"
+#include "gimpselection.h"
+
+#include "text/gimptextlayer.h"
+#include "text/gimptextundo.h"
+
+#include "vectors/gimpvectors.h"
+#include "vectors/gimpvectorsmodundo.h"
+#include "vectors/gimpvectorspropundo.h"
+#include "vectors/gimpvectorsundo.h"
+
+#include "gimp-intl.h"
+
+
+/**************************/
+/* Image Property Undos */
+/**************************/
+
+GimpUndo *
+gimp_image_undo_push_image_type (GimpImage *image,
+ const gchar *undo_desc)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+
+ return gimp_image_undo_push (image, GIMP_TYPE_IMAGE_UNDO,
+ GIMP_UNDO_IMAGE_TYPE, undo_desc,
+ GIMP_DIRTY_IMAGE,
+ NULL);
+}
+
+GimpUndo *
+gimp_image_undo_push_image_precision (GimpImage *image,
+ const gchar *undo_desc)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+
+ return gimp_image_undo_push (image, GIMP_TYPE_IMAGE_UNDO,
+ GIMP_UNDO_IMAGE_PRECISION, undo_desc,
+ GIMP_DIRTY_IMAGE,
+ NULL);
+}
+
+GimpUndo *
+gimp_image_undo_push_image_size (GimpImage *image,
+ const gchar *undo_desc,
+ gint previous_origin_x,
+ gint previous_origin_y,
+ gint previous_width,
+ gint previous_height)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+
+ return gimp_image_undo_push (image, GIMP_TYPE_IMAGE_UNDO,
+ GIMP_UNDO_IMAGE_SIZE, undo_desc,
+ GIMP_DIRTY_IMAGE | GIMP_DIRTY_IMAGE_SIZE,
+ "previous-origin-x", previous_origin_x,
+ "previous-origin-y", previous_origin_y,
+ "previous-width", previous_width,
+ "previous-height", previous_height,
+ NULL);
+}
+
+GimpUndo *
+gimp_image_undo_push_image_resolution (GimpImage *image,
+ const gchar *undo_desc)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+
+ return gimp_image_undo_push (image, GIMP_TYPE_IMAGE_UNDO,
+ GIMP_UNDO_IMAGE_RESOLUTION, undo_desc,
+ GIMP_DIRTY_IMAGE,
+ NULL);
+}
+
+GimpUndo *
+gimp_image_undo_push_image_grid (GimpImage *image,
+ const gchar *undo_desc,
+ GimpGrid *grid)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+ g_return_val_if_fail (GIMP_IS_GRID (grid), NULL);
+
+ return gimp_image_undo_push (image, GIMP_TYPE_IMAGE_UNDO,
+ GIMP_UNDO_IMAGE_GRID, undo_desc,
+ GIMP_DIRTY_IMAGE_META,
+ "grid", grid,
+ NULL);
+}
+
+GimpUndo *
+gimp_image_undo_push_image_colormap (GimpImage *image,
+ const gchar *undo_desc)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+
+ return gimp_image_undo_push (image, GIMP_TYPE_IMAGE_UNDO,
+ GIMP_UNDO_IMAGE_COLORMAP, undo_desc,
+ GIMP_DIRTY_IMAGE,
+ NULL);
+}
+
+GimpUndo *
+gimp_image_undo_push_image_color_managed (GimpImage *image,
+ const gchar *undo_desc)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+
+ return gimp_image_undo_push (image, GIMP_TYPE_IMAGE_UNDO,
+ GIMP_UNDO_IMAGE_COLOR_MANAGED, undo_desc,
+ GIMP_DIRTY_IMAGE,
+ NULL);
+}
+
+GimpUndo *
+gimp_image_undo_push_image_metadata (GimpImage *image,
+ const gchar *undo_desc)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+
+ return gimp_image_undo_push (image, GIMP_TYPE_IMAGE_UNDO,
+ GIMP_UNDO_IMAGE_METADATA, undo_desc,
+ GIMP_DIRTY_IMAGE_META,
+ NULL);
+}
+
+GimpUndo *
+gimp_image_undo_push_image_parasite (GimpImage *image,
+ const gchar *undo_desc,
+ const GimpParasite *parasite)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+ g_return_val_if_fail (parasite != NULL, NULL);
+
+ return gimp_image_undo_push (image, GIMP_TYPE_IMAGE_UNDO,
+ GIMP_UNDO_PARASITE_ATTACH, undo_desc,
+ GIMP_DIRTY_IMAGE_META,
+ "parasite-name", gimp_parasite_name (parasite),
+ NULL);
+}
+
+GimpUndo *
+gimp_image_undo_push_image_parasite_remove (GimpImage *image,
+ const gchar *undo_desc,
+ const gchar *name)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+ g_return_val_if_fail (name != NULL, NULL);
+
+ return gimp_image_undo_push (image, GIMP_TYPE_IMAGE_UNDO,
+ GIMP_UNDO_PARASITE_REMOVE, undo_desc,
+ GIMP_DIRTY_IMAGE_META,
+ "parasite-name", name,
+ NULL);
+}
+
+
+/********************************/
+/* Guide & Sample Point Undos */
+/********************************/
+
+GimpUndo *
+gimp_image_undo_push_guide (GimpImage *image,
+ const gchar *undo_desc,
+ GimpGuide *guide)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+ g_return_val_if_fail (GIMP_IS_GUIDE (guide), NULL);
+
+ return gimp_image_undo_push (image, GIMP_TYPE_GUIDE_UNDO,
+ GIMP_UNDO_GUIDE, undo_desc,
+ GIMP_DIRTY_IMAGE_META,
+ "aux-item", guide,
+ NULL);
+}
+
+GimpUndo *
+gimp_image_undo_push_sample_point (GimpImage *image,
+ const gchar *undo_desc,
+ GimpSamplePoint *sample_point)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+ g_return_val_if_fail (GIMP_IS_SAMPLE_POINT (sample_point), NULL);
+
+ return gimp_image_undo_push (image, GIMP_TYPE_SAMPLE_POINT_UNDO,
+ GIMP_UNDO_SAMPLE_POINT, undo_desc,
+ GIMP_DIRTY_IMAGE_META,
+ "aux-item", sample_point,
+ NULL);
+}
+
+
+/********************/
+/* Drawable Undos */
+/********************/
+
+GimpUndo *
+gimp_image_undo_push_drawable (GimpImage *image,
+ const gchar *undo_desc,
+ GimpDrawable *drawable,
+ GeglBuffer *buffer,
+ gint x,
+ gint y)
+{
+ GimpItem *item;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), NULL);
+ g_return_val_if_fail (GEGL_IS_BUFFER (buffer), NULL);
+
+ item = GIMP_ITEM (drawable);
+
+ g_return_val_if_fail (gimp_item_is_attached (item), NULL);
+
+ return gimp_image_undo_push (image, GIMP_TYPE_DRAWABLE_UNDO,
+ GIMP_UNDO_DRAWABLE, undo_desc,
+ GIMP_DIRTY_ITEM | GIMP_DIRTY_DRAWABLE,
+ "item", item,
+ "buffer", buffer,
+ "x", x,
+ "y", y,
+ NULL);
+}
+
+GimpUndo *
+gimp_image_undo_push_drawable_mod (GimpImage *image,
+ const gchar *undo_desc,
+ GimpDrawable *drawable,
+ gboolean copy_buffer)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), NULL);
+ g_return_val_if_fail (gimp_item_is_attached (GIMP_ITEM (drawable)), NULL);
+
+ return gimp_image_undo_push (image, GIMP_TYPE_DRAWABLE_MOD_UNDO,
+ GIMP_UNDO_DRAWABLE_MOD, undo_desc,
+ GIMP_DIRTY_ITEM | GIMP_DIRTY_DRAWABLE,
+ "item", drawable,
+ "copy-buffer", copy_buffer,
+ NULL);
+}
+
+
+/****************/
+/* Mask Undos */
+/****************/
+
+GimpUndo *
+gimp_image_undo_push_mask (GimpImage *image,
+ const gchar *undo_desc,
+ GimpChannel *mask)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+ g_return_val_if_fail (GIMP_IS_CHANNEL (mask), NULL);
+ g_return_val_if_fail (gimp_item_is_attached (GIMP_ITEM (mask)), NULL);
+
+ return gimp_image_undo_push (image, GIMP_TYPE_MASK_UNDO,
+ GIMP_UNDO_MASK, undo_desc,
+ GIMP_IS_SELECTION (mask) ?
+ GIMP_DIRTY_SELECTION :
+ GIMP_DIRTY_ITEM | GIMP_DIRTY_DRAWABLE,
+ "item", mask,
+ NULL);
+}
+
+GimpUndo *
+gimp_image_undo_push_mask_precision (GimpImage *image,
+ const gchar *undo_desc,
+ GimpChannel *mask)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+ g_return_val_if_fail (GIMP_IS_CHANNEL (mask), NULL);
+ g_return_val_if_fail (gimp_item_is_attached (GIMP_ITEM (mask)), NULL);
+
+ return gimp_image_undo_push (image, GIMP_TYPE_MASK_UNDO,
+ GIMP_UNDO_MASK, undo_desc,
+ GIMP_IS_SELECTION (mask) ?
+ GIMP_DIRTY_SELECTION :
+ GIMP_DIRTY_ITEM | GIMP_DIRTY_DRAWABLE,
+ "item", mask,
+ "convert-format", TRUE,
+ NULL);
+}
+
+
+/****************/
+/* Item Undos */
+/****************/
+
+GimpUndo *
+gimp_image_undo_push_item_reorder (GimpImage *image,
+ const gchar *undo_desc,
+ GimpItem *item)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+ g_return_val_if_fail (GIMP_IS_ITEM (item), NULL);
+ g_return_val_if_fail (gimp_item_is_attached (item), NULL);
+
+ return gimp_image_undo_push (image, GIMP_TYPE_ITEM_PROP_UNDO,
+ GIMP_UNDO_ITEM_REORDER, undo_desc,
+ GIMP_DIRTY_IMAGE_STRUCTURE,
+ "item", item,
+ NULL);
+}
+
+GimpUndo *
+gimp_image_undo_push_item_rename (GimpImage *image,
+ const gchar *undo_desc,
+ GimpItem *item)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+ g_return_val_if_fail (GIMP_IS_ITEM (item), NULL);
+ g_return_val_if_fail (gimp_item_is_attached (item), NULL);
+
+ return gimp_image_undo_push (image, GIMP_TYPE_ITEM_PROP_UNDO,
+ GIMP_UNDO_ITEM_RENAME, undo_desc,
+ GIMP_DIRTY_ITEM_META,
+ "item", item,
+ NULL);
+}
+
+GimpUndo *
+gimp_image_undo_push_item_displace (GimpImage *image,
+ const gchar *undo_desc,
+ GimpItem *item)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+ g_return_val_if_fail (GIMP_IS_ITEM (item), NULL);
+ g_return_val_if_fail (gimp_item_is_attached (item), NULL);
+
+ return gimp_image_undo_push (image, GIMP_TYPE_ITEM_PROP_UNDO,
+ GIMP_UNDO_ITEM_DISPLACE, undo_desc,
+ GIMP_IS_DRAWABLE (item) ?
+ GIMP_DIRTY_ITEM | GIMP_DIRTY_DRAWABLE :
+ GIMP_DIRTY_ITEM | GIMP_DIRTY_VECTORS,
+ "item", item,
+ NULL);
+}
+
+GimpUndo *
+gimp_image_undo_push_item_visibility (GimpImage *image,
+ const gchar *undo_desc,
+ GimpItem *item)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+ g_return_val_if_fail (GIMP_IS_ITEM (item), NULL);
+ g_return_val_if_fail (gimp_item_is_attached (item), NULL);
+
+ return gimp_image_undo_push (image, GIMP_TYPE_ITEM_PROP_UNDO,
+ GIMP_UNDO_ITEM_VISIBILITY, undo_desc,
+ GIMP_DIRTY_ITEM_META,
+ "item", item,
+ NULL);
+}
+
+GimpUndo *
+gimp_image_undo_push_item_linked (GimpImage *image,
+ const gchar *undo_desc,
+ GimpItem *item)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+ g_return_val_if_fail (GIMP_IS_ITEM (item), NULL);
+ g_return_val_if_fail (gimp_item_is_attached (item), NULL);
+
+ return gimp_image_undo_push (image, GIMP_TYPE_ITEM_PROP_UNDO,
+ GIMP_UNDO_ITEM_LINKED, undo_desc,
+ GIMP_DIRTY_ITEM_META,
+ "item", item,
+ NULL);
+}
+
+GimpUndo *
+gimp_image_undo_push_item_color_tag (GimpImage *image,
+ const gchar *undo_desc,
+ GimpItem *item)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+ g_return_val_if_fail (GIMP_IS_ITEM (item), NULL);
+ g_return_val_if_fail (gimp_item_is_attached (item), NULL);
+
+ return gimp_image_undo_push (image, GIMP_TYPE_ITEM_PROP_UNDO,
+ GIMP_UNDO_ITEM_COLOR_TAG, undo_desc,
+ GIMP_DIRTY_ITEM_META,
+ "item", item,
+ NULL);
+}
+
+GimpUndo *
+gimp_image_undo_push_item_lock_content (GimpImage *image,
+ const gchar *undo_desc,
+ GimpItem *item)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+ g_return_val_if_fail (GIMP_IS_ITEM (item), NULL);
+ g_return_val_if_fail (gimp_item_is_attached (item), NULL);
+
+ return gimp_image_undo_push (image, GIMP_TYPE_ITEM_PROP_UNDO,
+ GIMP_UNDO_ITEM_LOCK_CONTENT, undo_desc,
+ GIMP_DIRTY_ITEM_META,
+ "item", item,
+ NULL);
+}
+
+GimpUndo *
+gimp_image_undo_push_item_lock_position (GimpImage *image,
+ const gchar *undo_desc,
+ GimpItem *item)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+ g_return_val_if_fail (GIMP_IS_ITEM (item), NULL);
+ g_return_val_if_fail (gimp_item_is_attached (item), NULL);
+
+ return gimp_image_undo_push (image, GIMP_TYPE_ITEM_PROP_UNDO,
+ GIMP_UNDO_ITEM_LOCK_POSITION, undo_desc,
+ GIMP_DIRTY_ITEM_META,
+ "item", item,
+ NULL);
+}
+
+GimpUndo *
+gimp_image_undo_push_item_parasite (GimpImage *image,
+ const gchar *undo_desc,
+ GimpItem *item,
+ const GimpParasite *parasite)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+ g_return_val_if_fail (GIMP_IS_ITEM (item), NULL);
+ g_return_val_if_fail (gimp_item_is_attached (item), NULL);
+ g_return_val_if_fail (parasite != NULL, NULL);
+
+ return gimp_image_undo_push (image, GIMP_TYPE_ITEM_PROP_UNDO,
+ GIMP_UNDO_PARASITE_ATTACH, undo_desc,
+ GIMP_DIRTY_ITEM_META,
+ "item", item,
+ "parasite-name", gimp_parasite_name (parasite),
+ NULL);
+}
+
+GimpUndo *
+gimp_image_undo_push_item_parasite_remove (GimpImage *image,
+ const gchar *undo_desc,
+ GimpItem *item,
+ const gchar *name)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+ g_return_val_if_fail (GIMP_IS_ITEM (item), NULL);
+ g_return_val_if_fail (gimp_item_is_attached (item), NULL);
+ g_return_val_if_fail (name != NULL, NULL);
+
+ return gimp_image_undo_push (image, GIMP_TYPE_ITEM_PROP_UNDO,
+ GIMP_UNDO_PARASITE_REMOVE, undo_desc,
+ GIMP_DIRTY_ITEM_META,
+ "item", item,
+ "parasite-name", name,
+ NULL);
+}
+
+
+/*****************/
+/* Layer Undos */
+/*****************/
+
+GimpUndo *
+gimp_image_undo_push_layer_add (GimpImage *image,
+ const gchar *undo_desc,
+ GimpLayer *layer,
+ GimpLayer *prev_layer)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+ g_return_val_if_fail (GIMP_IS_LAYER (layer), NULL);
+ g_return_val_if_fail (! gimp_item_is_attached (GIMP_ITEM (layer)), NULL);
+ g_return_val_if_fail (prev_layer == NULL || GIMP_IS_LAYER (prev_layer),
+ NULL);
+
+ return gimp_image_undo_push (image, GIMP_TYPE_LAYER_UNDO,
+ GIMP_UNDO_LAYER_ADD, undo_desc,
+ GIMP_DIRTY_IMAGE_STRUCTURE,
+ "item", layer,
+ "prev-layer", prev_layer,
+ NULL);
+}
+
+GimpUndo *
+gimp_image_undo_push_layer_remove (GimpImage *image,
+ const gchar *undo_desc,
+ GimpLayer *layer,
+ GimpLayer *prev_parent,
+ gint prev_position,
+ GimpLayer *prev_layer)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+ g_return_val_if_fail (GIMP_IS_LAYER (layer), NULL);
+ g_return_val_if_fail (gimp_item_is_attached (GIMP_ITEM (layer)), NULL);
+ g_return_val_if_fail (prev_parent == NULL || GIMP_IS_LAYER (prev_parent),
+ NULL);
+ g_return_val_if_fail (prev_layer == NULL || GIMP_IS_LAYER (prev_layer),
+ NULL);
+
+ return gimp_image_undo_push (image, GIMP_TYPE_LAYER_UNDO,
+ GIMP_UNDO_LAYER_REMOVE, undo_desc,
+ GIMP_DIRTY_IMAGE_STRUCTURE,
+ "item", layer,
+ "prev-parent", prev_parent,
+ "prev-position", prev_position,
+ "prev-layer", prev_layer,
+ NULL);
+}
+
+GimpUndo *
+gimp_image_undo_push_layer_mode (GimpImage *image,
+ const gchar *undo_desc,
+ GimpLayer *layer)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+ g_return_val_if_fail (GIMP_IS_LAYER (layer), NULL);
+ g_return_val_if_fail (gimp_item_is_attached (GIMP_ITEM (layer)), NULL);
+
+ return gimp_image_undo_push (image, GIMP_TYPE_LAYER_PROP_UNDO,
+ GIMP_UNDO_LAYER_MODE, undo_desc,
+ GIMP_DIRTY_ITEM_META,
+ "item", layer,
+ NULL);
+}
+
+GimpUndo *
+gimp_image_undo_push_layer_opacity (GimpImage *image,
+ const gchar *undo_desc,
+ GimpLayer *layer)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+ g_return_val_if_fail (GIMP_IS_LAYER (layer), NULL);
+ g_return_val_if_fail (gimp_item_is_attached (GIMP_ITEM (layer)), NULL);
+
+ return gimp_image_undo_push (image, GIMP_TYPE_LAYER_PROP_UNDO,
+ GIMP_UNDO_LAYER_OPACITY, undo_desc,
+ GIMP_DIRTY_ITEM_META,
+ "item", layer,
+ NULL);
+}
+
+GimpUndo *
+gimp_image_undo_push_layer_lock_alpha (GimpImage *image,
+ const gchar *undo_desc,
+ GimpLayer *layer)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+ g_return_val_if_fail (GIMP_IS_LAYER (layer), NULL);
+ g_return_val_if_fail (gimp_item_is_attached (GIMP_ITEM (layer)), NULL);
+
+ return gimp_image_undo_push (image, GIMP_TYPE_LAYER_PROP_UNDO,
+ GIMP_UNDO_LAYER_LOCK_ALPHA, undo_desc,
+ GIMP_DIRTY_ITEM_META,
+ "item", layer,
+ NULL);
+}
+
+
+/***********************/
+/* Group Layer Undos */
+/***********************/
+
+GimpUndo *
+gimp_image_undo_push_group_layer_suspend_resize (GimpImage *image,
+ const gchar *undo_desc,
+ GimpGroupLayer *group)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+ g_return_val_if_fail (GIMP_IS_GROUP_LAYER (group), NULL);
+ g_return_val_if_fail (gimp_item_is_attached (GIMP_ITEM (group)), NULL);
+
+ return gimp_image_undo_push (image, GIMP_TYPE_GROUP_LAYER_UNDO,
+ GIMP_UNDO_GROUP_LAYER_SUSPEND_RESIZE, undo_desc,
+ GIMP_DIRTY_ITEM | GIMP_DIRTY_DRAWABLE,
+ "item", group,
+ NULL);
+}
+
+GimpUndo *
+gimp_image_undo_push_group_layer_resume_resize (GimpImage *image,
+ const gchar *undo_desc,
+ GimpGroupLayer *group)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+ g_return_val_if_fail (GIMP_IS_GROUP_LAYER (group), NULL);
+ g_return_val_if_fail (gimp_item_is_attached (GIMP_ITEM (group)), NULL);
+
+ return gimp_image_undo_push (image, GIMP_TYPE_GROUP_LAYER_UNDO,
+ GIMP_UNDO_GROUP_LAYER_RESUME_RESIZE, undo_desc,
+ GIMP_DIRTY_ITEM | GIMP_DIRTY_DRAWABLE,
+ "item", group,
+ NULL);
+}
+
+GimpUndo *
+gimp_image_undo_push_group_layer_suspend_mask (GimpImage *image,
+ const gchar *undo_desc,
+ GimpGroupLayer *group)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+ g_return_val_if_fail (GIMP_IS_GROUP_LAYER (group), NULL);
+ g_return_val_if_fail (gimp_item_is_attached (GIMP_ITEM (group)), NULL);
+
+ return gimp_image_undo_push (image, GIMP_TYPE_GROUP_LAYER_UNDO,
+ GIMP_UNDO_GROUP_LAYER_SUSPEND_MASK, undo_desc,
+ GIMP_DIRTY_ITEM | GIMP_DIRTY_DRAWABLE,
+ "item", group,
+ NULL);
+}
+
+GimpUndo *
+gimp_image_undo_push_group_layer_resume_mask (GimpImage *image,
+ const gchar *undo_desc,
+ GimpGroupLayer *group)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+ g_return_val_if_fail (GIMP_IS_GROUP_LAYER (group), NULL);
+ g_return_val_if_fail (gimp_item_is_attached (GIMP_ITEM (group)), NULL);
+
+ return gimp_image_undo_push (image, GIMP_TYPE_GROUP_LAYER_UNDO,
+ GIMP_UNDO_GROUP_LAYER_RESUME_MASK, undo_desc,
+ GIMP_DIRTY_ITEM | GIMP_DIRTY_DRAWABLE,
+ "item", group,
+ NULL);
+}
+
+GimpUndo *
+gimp_image_undo_push_group_layer_start_transform (GimpImage *image,
+ const gchar *undo_desc,
+ GimpGroupLayer *group)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+ g_return_val_if_fail (GIMP_IS_GROUP_LAYER (group), NULL);
+ g_return_val_if_fail (gimp_item_is_attached (GIMP_ITEM (group)), NULL);
+
+ return gimp_image_undo_push (image, GIMP_TYPE_GROUP_LAYER_UNDO,
+ GIMP_UNDO_GROUP_LAYER_START_TRANSFORM, undo_desc,
+ GIMP_DIRTY_ITEM | GIMP_DIRTY_DRAWABLE,
+ "item", group,
+ NULL);
+}
+
+GimpUndo *
+gimp_image_undo_push_group_layer_end_transform (GimpImage *image,
+ const gchar *undo_desc,
+ GimpGroupLayer *group)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+ g_return_val_if_fail (GIMP_IS_GROUP_LAYER (group), NULL);
+ g_return_val_if_fail (gimp_item_is_attached (GIMP_ITEM (group)), NULL);
+
+ return gimp_image_undo_push (image, GIMP_TYPE_GROUP_LAYER_UNDO,
+ GIMP_UNDO_GROUP_LAYER_END_TRANSFORM, undo_desc,
+ GIMP_DIRTY_ITEM | GIMP_DIRTY_DRAWABLE,
+ "item", group,
+ NULL);
+}
+
+GimpUndo *
+gimp_image_undo_push_group_layer_convert (GimpImage *image,
+ const gchar *undo_desc,
+ GimpGroupLayer *group)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+ g_return_val_if_fail (GIMP_IS_GROUP_LAYER (group), NULL);
+ g_return_val_if_fail (gimp_item_is_attached (GIMP_ITEM (group)), NULL);
+
+ return gimp_image_undo_push (image, GIMP_TYPE_GROUP_LAYER_UNDO,
+ GIMP_UNDO_GROUP_LAYER_CONVERT, undo_desc,
+ GIMP_DIRTY_ITEM | GIMP_DIRTY_DRAWABLE,
+ "item", group,
+ NULL);
+}
+
+
+/**********************/
+/* Text Layer Undos */
+/**********************/
+
+GimpUndo *
+gimp_image_undo_push_text_layer (GimpImage *image,
+ const gchar *undo_desc,
+ GimpTextLayer *layer,
+ const GParamSpec *pspec)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+ g_return_val_if_fail (GIMP_IS_TEXT_LAYER (layer), NULL);
+ g_return_val_if_fail (gimp_item_is_attached (GIMP_ITEM (layer)), NULL);
+
+ return gimp_image_undo_push (image, GIMP_TYPE_TEXT_UNDO,
+ GIMP_UNDO_TEXT_LAYER, undo_desc,
+ GIMP_DIRTY_ITEM | GIMP_DIRTY_DRAWABLE,
+ "item", layer,
+ "param", pspec,
+ NULL);
+}
+
+GimpUndo *
+gimp_image_undo_push_text_layer_modified (GimpImage *image,
+ const gchar *undo_desc,
+ GimpTextLayer *layer)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+ g_return_val_if_fail (GIMP_IS_TEXT_LAYER (layer), NULL);
+ g_return_val_if_fail (gimp_item_is_attached (GIMP_ITEM (layer)), NULL);
+
+ return gimp_image_undo_push (image, GIMP_TYPE_TEXT_UNDO,
+ GIMP_UNDO_TEXT_LAYER_MODIFIED, undo_desc,
+ GIMP_DIRTY_ITEM_META,
+ "item", layer,
+ NULL);
+}
+
+GimpUndo *
+gimp_image_undo_push_text_layer_convert (GimpImage *image,
+ const gchar *undo_desc,
+ GimpTextLayer *layer)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+ g_return_val_if_fail (GIMP_IS_TEXT_LAYER (layer), NULL);
+ g_return_val_if_fail (gimp_item_is_attached (GIMP_ITEM (layer)), NULL);
+
+ return gimp_image_undo_push (image, GIMP_TYPE_TEXT_UNDO,
+ GIMP_UNDO_TEXT_LAYER_CONVERT, undo_desc,
+ GIMP_DIRTY_ITEM,
+ "item", layer,
+ NULL);
+}
+
+
+/**********************/
+/* Layer Mask Undos */
+/**********************/
+
+GimpUndo *
+gimp_image_undo_push_layer_mask_add (GimpImage *image,
+ const gchar *undo_desc,
+ GimpLayer *layer,
+ GimpLayerMask *mask)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+ g_return_val_if_fail (GIMP_IS_LAYER (layer), NULL);
+ g_return_val_if_fail (gimp_item_is_attached (GIMP_ITEM (layer)), NULL);
+ g_return_val_if_fail (GIMP_IS_LAYER_MASK (mask), NULL);
+ g_return_val_if_fail (! gimp_item_is_attached (GIMP_ITEM (mask)), NULL);
+
+ return gimp_image_undo_push (image, GIMP_TYPE_LAYER_MASK_UNDO,
+ GIMP_UNDO_LAYER_MASK_ADD, undo_desc,
+ GIMP_DIRTY_IMAGE_STRUCTURE,
+ "item", layer,
+ "layer-mask", mask,
+ NULL);
+}
+
+GimpUndo *
+gimp_image_undo_push_layer_mask_remove (GimpImage *image,
+ const gchar *undo_desc,
+ GimpLayer *layer,
+ GimpLayerMask *mask)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+ g_return_val_if_fail (GIMP_IS_LAYER (layer), NULL);
+ g_return_val_if_fail (gimp_item_is_attached (GIMP_ITEM (layer)), NULL);
+ g_return_val_if_fail (GIMP_IS_LAYER_MASK (mask), NULL);
+ g_return_val_if_fail (gimp_item_is_attached (GIMP_ITEM (mask)), NULL);
+ g_return_val_if_fail (gimp_layer_mask_get_layer (mask) == layer, NULL);
+ g_return_val_if_fail (gimp_layer_get_mask (layer) == mask, NULL);
+
+ return gimp_image_undo_push (image, GIMP_TYPE_LAYER_MASK_UNDO,
+ GIMP_UNDO_LAYER_MASK_REMOVE, undo_desc,
+ GIMP_DIRTY_IMAGE_STRUCTURE,
+ "item", layer,
+ "layer-mask", mask,
+ NULL);
+}
+
+GimpUndo *
+gimp_image_undo_push_layer_mask_apply (GimpImage *image,
+ const gchar *undo_desc,
+ GimpLayer *layer)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+ g_return_val_if_fail (GIMP_IS_LAYER (layer), NULL);
+ g_return_val_if_fail (gimp_item_is_attached (GIMP_ITEM (layer)), NULL);
+
+ return gimp_image_undo_push (image, GIMP_TYPE_LAYER_MASK_PROP_UNDO,
+ GIMP_UNDO_LAYER_MASK_APPLY, undo_desc,
+ GIMP_DIRTY_ITEM_META,
+ "item", layer,
+ NULL);
+}
+
+GimpUndo *
+gimp_image_undo_push_layer_mask_show (GimpImage *image,
+ const gchar *undo_desc,
+ GimpLayer *layer)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+ g_return_val_if_fail (GIMP_IS_LAYER (layer), NULL);
+ g_return_val_if_fail (gimp_item_is_attached (GIMP_ITEM (layer)), NULL);
+
+ return gimp_image_undo_push (image, GIMP_TYPE_LAYER_MASK_PROP_UNDO,
+ GIMP_UNDO_LAYER_MASK_SHOW, undo_desc,
+ GIMP_DIRTY_ITEM_META,
+ "item", layer,
+ NULL);
+}
+
+
+/*******************/
+/* Channel Undos */
+/*******************/
+
+GimpUndo *
+gimp_image_undo_push_channel_add (GimpImage *image,
+ const gchar *undo_desc,
+ GimpChannel *channel,
+ GimpChannel *prev_channel)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+ g_return_val_if_fail (GIMP_IS_CHANNEL (channel), NULL);
+ g_return_val_if_fail (! gimp_item_is_attached (GIMP_ITEM (channel)), NULL);
+ g_return_val_if_fail (prev_channel == NULL || GIMP_IS_CHANNEL (prev_channel),
+ NULL);
+
+ return gimp_image_undo_push (image, GIMP_TYPE_CHANNEL_UNDO,
+ GIMP_UNDO_CHANNEL_ADD, undo_desc,
+ GIMP_DIRTY_IMAGE_STRUCTURE,
+ "item", channel,
+ "prev-channel", prev_channel,
+ NULL);
+}
+
+GimpUndo *
+gimp_image_undo_push_channel_remove (GimpImage *image,
+ const gchar *undo_desc,
+ GimpChannel *channel,
+ GimpChannel *prev_parent,
+ gint prev_position,
+ GimpChannel *prev_channel)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+ g_return_val_if_fail (GIMP_IS_CHANNEL (channel), NULL);
+ g_return_val_if_fail (gimp_item_is_attached (GIMP_ITEM (channel)), NULL);
+ g_return_val_if_fail (prev_parent == NULL || GIMP_IS_CHANNEL (prev_parent),
+ NULL);
+ g_return_val_if_fail (prev_channel == NULL || GIMP_IS_CHANNEL (prev_channel),
+ NULL);
+
+ return gimp_image_undo_push (image, GIMP_TYPE_CHANNEL_UNDO,
+ GIMP_UNDO_CHANNEL_REMOVE, undo_desc,
+ GIMP_DIRTY_IMAGE_STRUCTURE,
+ "item", channel,
+ "prev-parent", prev_parent,
+ "prev-position", prev_position,
+ "prev-channel", prev_channel,
+ NULL);
+}
+
+GimpUndo *
+gimp_image_undo_push_channel_color (GimpImage *image,
+ const gchar *undo_desc,
+ GimpChannel *channel)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+ g_return_val_if_fail (GIMP_IS_CHANNEL (channel), NULL);
+ g_return_val_if_fail (gimp_item_is_attached (GIMP_ITEM (channel)), NULL);
+
+ return gimp_image_undo_push (image, GIMP_TYPE_CHANNEL_PROP_UNDO,
+ GIMP_UNDO_CHANNEL_COLOR, undo_desc,
+ GIMP_DIRTY_ITEM | GIMP_DIRTY_DRAWABLE,
+ "item", channel,
+ NULL);
+}
+
+
+/*******************/
+/* Vectors Undos */
+/*******************/
+
+GimpUndo *
+gimp_image_undo_push_vectors_add (GimpImage *image,
+ const gchar *undo_desc,
+ GimpVectors *vectors,
+ GimpVectors *prev_vectors)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+ g_return_val_if_fail (GIMP_IS_VECTORS (vectors), NULL);
+ g_return_val_if_fail (! gimp_item_is_attached (GIMP_ITEM (vectors)), NULL);
+ g_return_val_if_fail (prev_vectors == NULL || GIMP_IS_VECTORS (prev_vectors),
+ NULL);
+
+ return gimp_image_undo_push (image, GIMP_TYPE_VECTORS_UNDO,
+ GIMP_UNDO_VECTORS_ADD, undo_desc,
+ GIMP_DIRTY_IMAGE_STRUCTURE,
+ "item", vectors,
+ "prev-vectors", prev_vectors,
+ NULL);
+}
+
+GimpUndo *
+gimp_image_undo_push_vectors_remove (GimpImage *image,
+ const gchar *undo_desc,
+ GimpVectors *vectors,
+ GimpVectors *prev_parent,
+ gint prev_position,
+ GimpVectors *prev_vectors)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+ g_return_val_if_fail (GIMP_IS_VECTORS (vectors), NULL);
+ g_return_val_if_fail (gimp_item_is_attached (GIMP_ITEM (vectors)), NULL);
+ g_return_val_if_fail (prev_parent == NULL || GIMP_IS_VECTORS (prev_parent),
+ NULL);
+ g_return_val_if_fail (prev_vectors == NULL || GIMP_IS_VECTORS (prev_vectors),
+ NULL);
+
+ return gimp_image_undo_push (image, GIMP_TYPE_VECTORS_UNDO,
+ GIMP_UNDO_VECTORS_REMOVE, undo_desc,
+ GIMP_DIRTY_IMAGE_STRUCTURE,
+ "item", vectors,
+ "prev-parent", prev_parent,
+ "prev-position", prev_position,
+ "prev-vectors", prev_vectors,
+ NULL);
+}
+
+GimpUndo *
+gimp_image_undo_push_vectors_mod (GimpImage *image,
+ const gchar *undo_desc,
+ GimpVectors *vectors)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+ g_return_val_if_fail (GIMP_IS_VECTORS (vectors), NULL);
+ g_return_val_if_fail (gimp_item_is_attached (GIMP_ITEM (vectors)), NULL);
+
+ return gimp_image_undo_push (image, GIMP_TYPE_VECTORS_MOD_UNDO,
+ GIMP_UNDO_VECTORS_MOD, undo_desc,
+ GIMP_DIRTY_ITEM | GIMP_DIRTY_VECTORS,
+ "item", vectors,
+ NULL);
+}
+
+
+/******************************/
+/* Floating Selection Undos */
+/******************************/
+
+GimpUndo *
+gimp_image_undo_push_fs_to_layer (GimpImage *image,
+ const gchar *undo_desc,
+ GimpLayer *floating_layer)
+{
+ GimpUndo *undo;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+ g_return_val_if_fail (GIMP_IS_LAYER (floating_layer), NULL);
+
+ undo = gimp_image_undo_push (image, GIMP_TYPE_FLOATING_SELECTION_UNDO,
+ GIMP_UNDO_FS_TO_LAYER, undo_desc,
+ GIMP_DIRTY_IMAGE_STRUCTURE,
+ "item", floating_layer,
+ NULL);
+
+ return undo;
+}
+
+
+/******************************************************************************/
+/* Something for which programmer is too lazy to write an undo function for */
+/******************************************************************************/
+
+static void
+undo_pop_cantundo (GimpUndo *undo,
+ GimpUndoMode undo_mode,
+ GimpUndoAccumulator *accum)
+{
+ switch (undo_mode)
+ {
+ case GIMP_UNDO_MODE_UNDO:
+ gimp_message (undo->image->gimp, NULL, GIMP_MESSAGE_WARNING,
+ _("Can't undo %s"), gimp_object_get_name (undo));
+ break;
+
+ case GIMP_UNDO_MODE_REDO:
+ break;
+ }
+}
+
+GimpUndo *
+gimp_image_undo_push_cantundo (GimpImage *image,
+ const gchar *undo_desc)
+{
+ GimpUndo *undo;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+
+ /* This is the sole purpose of this type of undo: the ability to
+ * mark an image as having been mutated, without really providing
+ * any adequate undo facility.
+ */
+
+ undo = gimp_image_undo_push (image, GIMP_TYPE_UNDO,
+ GIMP_UNDO_CANT, undo_desc,
+ GIMP_DIRTY_ALL,
+ NULL);
+
+ if (undo)
+ g_signal_connect (undo, "pop",
+ G_CALLBACK (undo_pop_cantundo),
+ NULL);
+
+ return undo;
+}
diff --git a/app/core/gimpimage-undo-push.h b/app/core/gimpimage-undo-push.h
new file mode 100644
index 0000000..f06fcd8
--- /dev/null
+++ b/app/core/gimpimage-undo-push.h
@@ -0,0 +1,256 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_IMAGE_UNDO_PUSH_H__
+#define __GIMP_IMAGE_UNDO_PUSH_H__
+
+
+/* image undos */
+
+GimpUndo * gimp_image_undo_push_image_type (GimpImage *image,
+ const gchar *undo_desc);
+GimpUndo * gimp_image_undo_push_image_precision (GimpImage *image,
+ const gchar *undo_desc);
+GimpUndo * gimp_image_undo_push_image_size (GimpImage *image,
+ const gchar *undo_desc,
+ gint previous_origin_x,
+ gint previous_origin_y,
+ gint previous_width,
+ gint prevoius_height);
+GimpUndo * gimp_image_undo_push_image_resolution (GimpImage *image,
+ const gchar *undo_desc);
+GimpUndo * gimp_image_undo_push_image_grid (GimpImage *image,
+ const gchar *undo_desc,
+ GimpGrid *grid);
+GimpUndo * gimp_image_undo_push_image_colormap (GimpImage *image,
+ const gchar *undo_desc);
+GimpUndo * gimp_image_undo_push_image_color_managed (GimpImage *image,
+ const gchar *undo_desc);
+GimpUndo * gimp_image_undo_push_image_metadata (GimpImage *image,
+ const gchar *undo_desc);
+GimpUndo * gimp_image_undo_push_image_parasite (GimpImage *image,
+ const gchar *undo_desc,
+ const GimpParasite *parasite);
+GimpUndo * gimp_image_undo_push_image_parasite_remove (GimpImage *image,
+ const gchar *undo_desc,
+ const gchar *name);
+
+
+/* guide & sample point undos */
+
+GimpUndo * gimp_image_undo_push_guide (GimpImage *image,
+ const gchar *undo_desc,
+ GimpGuide *guide);
+GimpUndo * gimp_image_undo_push_sample_point (GimpImage *image,
+ const gchar *undo_desc,
+ GimpSamplePoint *sample_point);
+
+
+/* drawable undos */
+
+GimpUndo * gimp_image_undo_push_drawable (GimpImage *image,
+ const gchar *undo_desc,
+ GimpDrawable *drawable,
+ GeglBuffer *buffer,
+ gint x,
+ gint y);
+GimpUndo * gimp_image_undo_push_drawable_mod (GimpImage *image,
+ const gchar *undo_desc,
+ GimpDrawable *drawable,
+ gboolean copy_buffer);
+
+
+/* mask undos */
+
+GimpUndo * gimp_image_undo_push_mask (GimpImage *image,
+ const gchar *undo_desc,
+ GimpChannel *mask);
+GimpUndo * gimp_image_undo_push_mask_precision (GimpImage *image,
+ const gchar *undo_desc,
+ GimpChannel *mask);
+
+
+/* item undos */
+
+GimpUndo * gimp_image_undo_push_item_reorder (GimpImage *image,
+ const gchar *undo_desc,
+ GimpItem *item);
+GimpUndo * gimp_image_undo_push_item_rename (GimpImage *image,
+ const gchar *undo_desc,
+ GimpItem *item);
+GimpUndo * gimp_image_undo_push_item_displace (GimpImage *image,
+ const gchar *undo_desc,
+ GimpItem *item);
+GimpUndo * gimp_image_undo_push_item_visibility (GimpImage *image,
+ const gchar *undo_desc,
+ GimpItem *item);
+GimpUndo * gimp_image_undo_push_item_linked (GimpImage *image,
+ const gchar *undo_desc,
+ GimpItem *item);
+GimpUndo * gimp_image_undo_push_item_color_tag (GimpImage *image,
+ const gchar *undo_desc,
+ GimpItem *item);
+GimpUndo * gimp_image_undo_push_item_lock_content (GimpImage *image,
+ const gchar *undo_desc,
+ GimpItem *item);
+GimpUndo * gimp_image_undo_push_item_lock_position (GimpImage *image,
+ const gchar *undo_desc,
+ GimpItem *item);
+GimpUndo * gimp_image_undo_push_item_parasite (GimpImage *image,
+ const gchar *undo_desc,
+ GimpItem *item,
+ const GimpParasite *parasite);
+GimpUndo * gimp_image_undo_push_item_parasite_remove(GimpImage *image,
+ const gchar *undo_desc,
+ GimpItem *item,
+ const gchar *name);
+
+
+/* layer undos */
+
+GimpUndo * gimp_image_undo_push_layer_add (GimpImage *image,
+ const gchar *undo_desc,
+ GimpLayer *layer,
+ GimpLayer *prev_layer);
+GimpUndo * gimp_image_undo_push_layer_remove (GimpImage *image,
+ const gchar *undo_desc,
+ GimpLayer *layer,
+ GimpLayer *prev_parent,
+ gint prev_position,
+ GimpLayer *prev_layer);
+GimpUndo * gimp_image_undo_push_layer_mode (GimpImage *image,
+ const gchar *undo_desc,
+ GimpLayer *layer);
+GimpUndo * gimp_image_undo_push_layer_opacity (GimpImage *image,
+ const gchar *undo_desc,
+ GimpLayer *layer);
+GimpUndo * gimp_image_undo_push_layer_lock_alpha (GimpImage *image,
+ const gchar *undo_desc,
+ GimpLayer *layer);
+
+
+/* group layer undos */
+
+GimpUndo *
+ gimp_image_undo_push_group_layer_suspend_resize (GimpImage *image,
+ const gchar *undo_desc,
+ GimpGroupLayer *group);
+GimpUndo *
+ gimp_image_undo_push_group_layer_resume_resize (GimpImage *image,
+ const gchar *undo_desc,
+ GimpGroupLayer *group);
+GimpUndo *
+ gimp_image_undo_push_group_layer_suspend_mask (GimpImage *image,
+ const gchar *undo_desc,
+ GimpGroupLayer *group);
+GimpUndo *
+ gimp_image_undo_push_group_layer_resume_mask (GimpImage *image,
+ const gchar *undo_desc,
+ GimpGroupLayer *group);
+GimpUndo *
+ gimp_image_undo_push_group_layer_start_transform (GimpImage *image,
+ const gchar *undo_desc,
+ GimpGroupLayer *group);
+GimpUndo *
+ gimp_image_undo_push_group_layer_end_transform (GimpImage *image,
+ const gchar *undo_desc,
+ GimpGroupLayer *group);
+GimpUndo * gimp_image_undo_push_group_layer_convert (GimpImage *image,
+ const gchar *undo_desc,
+ GimpGroupLayer *group);
+
+
+/* text layer undos */
+
+GimpUndo * gimp_image_undo_push_text_layer (GimpImage *image,
+ const gchar *undo_desc,
+ GimpTextLayer *layer,
+ const GParamSpec *pspec);
+GimpUndo * gimp_image_undo_push_text_layer_modified (GimpImage *image,
+ const gchar *undo_desc,
+ GimpTextLayer *layer);
+GimpUndo * gimp_image_undo_push_text_layer_convert (GimpImage *image,
+ const gchar *undo_desc,
+ GimpTextLayer *layer);
+
+
+/* layer mask undos */
+
+GimpUndo * gimp_image_undo_push_layer_mask_add (GimpImage *image,
+ const gchar *undo_desc,
+ GimpLayer *layer,
+ GimpLayerMask *mask);
+GimpUndo * gimp_image_undo_push_layer_mask_remove (GimpImage *image,
+ const gchar *undo_desc,
+ GimpLayer *layer,
+ GimpLayerMask *mask);
+GimpUndo * gimp_image_undo_push_layer_mask_apply (GimpImage *image,
+ const gchar *undo_desc,
+ GimpLayer *layer);
+GimpUndo * gimp_image_undo_push_layer_mask_show (GimpImage *image,
+ const gchar *undo_desc,
+ GimpLayer *layer);
+
+
+/* channel undos */
+
+GimpUndo * gimp_image_undo_push_channel_add (GimpImage *image,
+ const gchar *undo_desc,
+ GimpChannel *channel,
+ GimpChannel *prev_channel);
+GimpUndo * gimp_image_undo_push_channel_remove (GimpImage *image,
+ const gchar *undo_desc,
+ GimpChannel *channel,
+ GimpChannel *prev_parent,
+ gint prev_position,
+ GimpChannel *prev_channel);
+GimpUndo * gimp_image_undo_push_channel_color (GimpImage *image,
+ const gchar *undo_desc,
+ GimpChannel *channel);
+
+
+/* vectors undos */
+
+GimpUndo * gimp_image_undo_push_vectors_add (GimpImage *image,
+ const gchar *undo_desc,
+ GimpVectors *vectors,
+ GimpVectors *prev_vectors);
+GimpUndo * gimp_image_undo_push_vectors_remove (GimpImage *image,
+ const gchar *undo_desc,
+ GimpVectors *vectors,
+ GimpVectors *prev_parent,
+ gint prev_position,
+ GimpVectors *prev_vectors);
+GimpUndo * gimp_image_undo_push_vectors_mod (GimpImage *image,
+ const gchar *undo_desc,
+ GimpVectors *vectors);
+
+
+/* floating selection undos */
+
+GimpUndo * gimp_image_undo_push_fs_to_layer (GimpImage *image,
+ const gchar *undo_desc,
+ GimpLayer *floating_layer);
+
+
+/* EEK undo */
+
+GimpUndo * gimp_image_undo_push_cantundo (GimpImage *image,
+ const gchar *undo_desc);
+
+
+#endif /* __GIMP_IMAGE_UNDO_PUSH_H__ */
diff --git a/app/core/gimpimage-undo.c b/app/core/gimpimage-undo.c
new file mode 100644
index 0000000..fbf808b
--- /dev/null
+++ b/app/core/gimpimage-undo.c
@@ -0,0 +1,695 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "core-types.h"
+
+#include "config/gimpcoreconfig.h"
+
+#include "gimp.h"
+#include "gimp-utils.h"
+#include "gimpimage.h"
+#include "gimpimage-private.h"
+#include "gimpimage-undo.h"
+#include "gimpitem.h"
+#include "gimplist.h"
+#include "gimpundostack.h"
+
+
+/* local function prototypes */
+
+static void gimp_image_undo_pop_stack (GimpImage *image,
+ GimpUndoStack *undo_stack,
+ GimpUndoStack *redo_stack,
+ GimpUndoMode undo_mode);
+static void gimp_image_undo_free_space (GimpImage *image);
+static void gimp_image_undo_free_redo (GimpImage *image);
+
+static GimpDirtyMask gimp_image_undo_dirty_from_type (GimpUndoType undo_type);
+
+
+/* public functions */
+
+gboolean
+gimp_image_undo_is_enabled (GimpImage *image)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);
+
+ return (GIMP_IMAGE_GET_PRIVATE (image)->undo_freeze_count == 0);
+}
+
+gboolean
+gimp_image_undo_enable (GimpImage *image)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);
+
+ /* Free all undo steps as they are now invalidated */
+ gimp_image_undo_free (image);
+
+ return gimp_image_undo_thaw (image);
+}
+
+gboolean
+gimp_image_undo_disable (GimpImage *image)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);
+
+ return gimp_image_undo_freeze (image);
+}
+
+gboolean
+gimp_image_undo_freeze (GimpImage *image)
+{
+ GimpImagePrivate *private;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);
+
+ private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ private->undo_freeze_count++;
+
+ if (private->undo_freeze_count == 1)
+ gimp_image_undo_event (image, GIMP_UNDO_EVENT_UNDO_FREEZE, NULL);
+
+ return TRUE;
+}
+
+gboolean
+gimp_image_undo_thaw (GimpImage *image)
+{
+ GimpImagePrivate *private;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);
+
+ private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ g_return_val_if_fail (private->undo_freeze_count > 0, FALSE);
+
+ private->undo_freeze_count--;
+
+ if (private->undo_freeze_count == 0)
+ gimp_image_undo_event (image, GIMP_UNDO_EVENT_UNDO_THAW, NULL);
+
+ return TRUE;
+}
+
+gboolean
+gimp_image_undo (GimpImage *image)
+{
+ GimpImagePrivate *private;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);
+
+ private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ g_return_val_if_fail (private->pushing_undo_group == GIMP_UNDO_GROUP_NONE,
+ FALSE);
+
+ gimp_image_undo_pop_stack (image,
+ private->undo_stack,
+ private->redo_stack,
+ GIMP_UNDO_MODE_UNDO);
+
+ return TRUE;
+}
+
+gboolean
+gimp_image_redo (GimpImage *image)
+{
+ GimpImagePrivate *private;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);
+
+ private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ g_return_val_if_fail (private->pushing_undo_group == GIMP_UNDO_GROUP_NONE,
+ FALSE);
+
+ gimp_image_undo_pop_stack (image,
+ private->redo_stack,
+ private->undo_stack,
+ GIMP_UNDO_MODE_REDO);
+
+ return TRUE;
+}
+
+/*
+ * this function continues to undo as long as it only sees certain
+ * undo types, in particular visibility changes.
+ */
+gboolean
+gimp_image_strong_undo (GimpImage *image)
+{
+ GimpImagePrivate *private;
+ GimpUndo *undo;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);
+
+ private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ g_return_val_if_fail (private->pushing_undo_group == GIMP_UNDO_GROUP_NONE,
+ FALSE);
+
+ undo = gimp_undo_stack_peek (private->undo_stack);
+
+ gimp_image_undo (image);
+
+ while (gimp_undo_is_weak (undo))
+ {
+ undo = gimp_undo_stack_peek (private->undo_stack);
+ if (gimp_undo_is_weak (undo))
+ gimp_image_undo (image);
+ }
+
+ return TRUE;
+}
+
+/*
+ * this function continues to redo as long as it only sees certain
+ * undo types, in particular visibility changes. Note that the
+ * order of events is set up to make it exactly reverse
+ * gimp_image_strong_undo().
+ */
+gboolean
+gimp_image_strong_redo (GimpImage *image)
+{
+ GimpImagePrivate *private;
+ GimpUndo *undo;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);
+
+ private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ g_return_val_if_fail (private->pushing_undo_group == GIMP_UNDO_GROUP_NONE,
+ FALSE);
+
+ undo = gimp_undo_stack_peek (private->redo_stack);
+
+ gimp_image_redo (image);
+
+ while (gimp_undo_is_weak (undo))
+ {
+ undo = gimp_undo_stack_peek (private->redo_stack);
+ if (gimp_undo_is_weak (undo))
+ gimp_image_redo (image);
+ }
+
+ return TRUE;
+}
+
+GimpUndoStack *
+gimp_image_get_undo_stack (GimpImage *image)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+
+ return GIMP_IMAGE_GET_PRIVATE (image)->undo_stack;
+}
+
+GimpUndoStack *
+gimp_image_get_redo_stack (GimpImage *image)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+
+ return GIMP_IMAGE_GET_PRIVATE (image)->redo_stack;
+}
+
+void
+gimp_image_undo_free (GimpImage *image)
+{
+ GimpImagePrivate *private;
+
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+
+ private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ /* Emit the UNDO_FREE event before actually freeing everything
+ * so the views can properly detach from the undo items
+ */
+ gimp_image_undo_event (image, GIMP_UNDO_EVENT_UNDO_FREE, NULL);
+
+ gimp_undo_free (GIMP_UNDO (private->undo_stack), GIMP_UNDO_MODE_UNDO);
+ gimp_undo_free (GIMP_UNDO (private->redo_stack), GIMP_UNDO_MODE_REDO);
+
+ /* If the image was dirty, but could become clean by redo-ing
+ * some actions, then it should now become 'infinitely' dirty.
+ * This is because we've just nuked the actions that would allow
+ * the image to become clean again.
+ */
+ if (private->dirty < 0)
+ private->dirty = 100000;
+
+ /* The same applies to the case where the image would become clean
+ * due to undo actions, but since user can't undo without an undo
+ * stack, that's not so much a problem.
+ */
+}
+
+gint
+gimp_image_get_undo_group_count (GimpImage *image)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), 0);
+
+ return GIMP_IMAGE_GET_PRIVATE (image)->group_count;
+}
+
+gboolean
+gimp_image_undo_group_start (GimpImage *image,
+ GimpUndoType undo_type,
+ const gchar *name)
+{
+ GimpImagePrivate *private;
+ GimpUndoStack *undo_group;
+ GimpDirtyMask dirty_mask;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);
+ g_return_val_if_fail (undo_type > GIMP_UNDO_GROUP_FIRST &&
+ undo_type <= GIMP_UNDO_GROUP_LAST, FALSE);
+
+ private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ if (! name)
+ name = gimp_undo_type_to_name (undo_type);
+
+ dirty_mask = gimp_image_undo_dirty_from_type (undo_type);
+
+ /* Notify listeners that the image will be modified */
+ if (private->group_count == 0 && dirty_mask != GIMP_DIRTY_NONE)
+ gimp_image_dirty (image, dirty_mask);
+
+ if (private->undo_freeze_count > 0)
+ return FALSE;
+
+ private->group_count++;
+
+ /* If we're already in a group...ignore */
+ if (private->group_count > 1)
+ return TRUE;
+
+ /* nuke the redo stack */
+ gimp_image_undo_free_redo (image);
+
+ undo_group = gimp_undo_stack_new (image);
+
+ gimp_object_set_name (GIMP_OBJECT (undo_group), name);
+ GIMP_UNDO (undo_group)->undo_type = undo_type;
+ GIMP_UNDO (undo_group)->dirty_mask = dirty_mask;
+
+ gimp_undo_stack_push_undo (private->undo_stack, GIMP_UNDO (undo_group));
+
+ private->pushing_undo_group = undo_type;
+
+ return TRUE;
+}
+
+gboolean
+gimp_image_undo_group_end (GimpImage *image)
+{
+ GimpImagePrivate *private;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);
+
+ private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ if (private->undo_freeze_count > 0)
+ return FALSE;
+
+ g_return_val_if_fail (private->group_count > 0, FALSE);
+
+ private->group_count--;
+
+ if (private->group_count == 0)
+ {
+ private->pushing_undo_group = GIMP_UNDO_GROUP_NONE;
+
+ /* Do it here, since undo_push doesn't emit this event while in
+ * the middle of a group
+ */
+ gimp_image_undo_event (image, GIMP_UNDO_EVENT_UNDO_PUSHED,
+ gimp_undo_stack_peek (private->undo_stack));
+
+ gimp_image_undo_free_space (image);
+ }
+
+ return TRUE;
+}
+
+GimpUndo *
+gimp_image_undo_push (GimpImage *image,
+ GType object_type,
+ GimpUndoType undo_type,
+ const gchar *name,
+ GimpDirtyMask dirty_mask,
+ ...)
+{
+ GimpImagePrivate *private;
+ gint n_properties = 0;
+ gchar **names = NULL;
+ GValue *values = NULL;
+ va_list args;
+ GimpUndo *undo;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+ g_return_val_if_fail (g_type_is_a (object_type, GIMP_TYPE_UNDO), NULL);
+ g_return_val_if_fail (undo_type > GIMP_UNDO_GROUP_LAST, NULL);
+
+ private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ /* Does this undo dirty the image? If so, we always want to mark
+ * image dirty, even if we can't actually push the undo.
+ */
+ if (dirty_mask != GIMP_DIRTY_NONE)
+ gimp_image_dirty (image, dirty_mask);
+
+ if (private->undo_freeze_count > 0)
+ return NULL;
+
+ if (! name)
+ name = gimp_undo_type_to_name (undo_type);
+
+ names = gimp_properties_append (object_type,
+ &n_properties, names, &values,
+ "name", name,
+ "image", image,
+ "undo-type", undo_type,
+ "dirty-mask", dirty_mask,
+ NULL);
+
+ va_start (args, dirty_mask);
+ names = gimp_properties_append_valist (object_type,
+ &n_properties, names, &values,
+ args);
+ va_end (args);
+
+ undo = (GimpUndo *) g_object_new_with_properties (object_type,
+ n_properties,
+ (const gchar **) names,
+ (const GValue *) values);
+
+ gimp_properties_free (n_properties, names, values);
+
+ /* nuke the redo stack */
+ gimp_image_undo_free_redo (image);
+
+ if (private->pushing_undo_group == GIMP_UNDO_GROUP_NONE)
+ {
+ gimp_undo_stack_push_undo (private->undo_stack, undo);
+
+ gimp_image_undo_event (image, GIMP_UNDO_EVENT_UNDO_PUSHED, undo);
+
+ gimp_image_undo_free_space (image);
+
+ /* freeing undo space may have freed the newly pushed undo */
+ if (gimp_undo_stack_peek (private->undo_stack) == undo)
+ return undo;
+ }
+ else
+ {
+ GimpUndoStack *undo_group;
+
+ undo_group = GIMP_UNDO_STACK (gimp_undo_stack_peek (private->undo_stack));
+
+ gimp_undo_stack_push_undo (undo_group, undo);
+
+ return undo;
+ }
+
+ return NULL;
+}
+
+GimpUndo *
+gimp_image_undo_can_compress (GimpImage *image,
+ GType object_type,
+ GimpUndoType undo_type)
+{
+ GimpImagePrivate *private;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+
+ private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ if (gimp_image_is_dirty (image) &&
+ ! gimp_undo_stack_peek (private->redo_stack))
+ {
+ GimpUndo *undo = gimp_undo_stack_peek (private->undo_stack);
+
+ if (undo && undo->undo_type == undo_type &&
+ g_type_is_a (G_TYPE_FROM_INSTANCE (undo), object_type))
+ {
+ return undo;
+ }
+ }
+
+ return NULL;
+}
+
+
+/* private functions */
+
+static void
+gimp_image_undo_pop_stack (GimpImage *image,
+ GimpUndoStack *undo_stack,
+ GimpUndoStack *redo_stack,
+ GimpUndoMode undo_mode)
+{
+ GimpUndo *undo;
+ GimpUndoAccumulator accum = { 0, };
+
+ g_object_freeze_notify (G_OBJECT (image));
+
+ undo = gimp_undo_stack_pop_undo (undo_stack, undo_mode, &accum);
+
+ if (undo)
+ {
+ if (GIMP_IS_UNDO_STACK (undo))
+ gimp_list_reverse (GIMP_LIST (GIMP_UNDO_STACK (undo)->undos));
+
+ gimp_undo_stack_push_undo (redo_stack, undo);
+
+ if (accum.mode_changed)
+ gimp_image_mode_changed (image);
+
+ if (accum.precision_changed)
+ gimp_image_precision_changed (image);
+
+ if (accum.size_changed)
+ gimp_image_size_changed_detailed (image,
+ accum.previous_origin_x,
+ accum.previous_origin_y,
+ accum.previous_width,
+ accum.previous_height);
+
+ if (accum.resolution_changed)
+ gimp_image_resolution_changed (image);
+
+ if (accum.unit_changed)
+ gimp_image_unit_changed (image);
+
+ /* let others know that we just popped an action */
+ gimp_image_undo_event (image,
+ (undo_mode == GIMP_UNDO_MODE_UNDO) ?
+ GIMP_UNDO_EVENT_UNDO : GIMP_UNDO_EVENT_REDO,
+ undo);
+ }
+
+ g_object_thaw_notify (G_OBJECT (image));
+}
+
+static void
+gimp_image_undo_free_space (GimpImage *image)
+{
+ GimpImagePrivate *private = GIMP_IMAGE_GET_PRIVATE (image);
+ GimpContainer *container;
+ gint min_undo_levels;
+ gint max_undo_levels;
+ gint64 undo_size;
+
+ container = private->undo_stack->undos;
+
+ min_undo_levels = image->gimp->config->levels_of_undo;
+ max_undo_levels = 1024; /* FIXME */
+ undo_size = image->gimp->config->undo_size;
+
+#ifdef DEBUG_IMAGE_UNDO
+ g_printerr ("undo_steps: %d undo_bytes: %ld\n",
+ gimp_container_get_n_children (container),
+ (glong) gimp_object_get_memsize (GIMP_OBJECT (container), NULL));
+#endif
+
+ /* keep at least min_undo_levels undo steps */
+ if (gimp_container_get_n_children (container) <= min_undo_levels)
+ return;
+
+ while ((gimp_object_get_memsize (GIMP_OBJECT (container), NULL) > undo_size) ||
+ (gimp_container_get_n_children (container) > max_undo_levels))
+ {
+ GimpUndo *freed = gimp_undo_stack_free_bottom (private->undo_stack,
+ GIMP_UNDO_MODE_UNDO);
+
+#ifdef DEBUG_IMAGE_UNDO
+ g_printerr ("freed one step: undo_steps: %d undo_bytes: %ld\n",
+ gimp_container_get_n_children (container),
+ (glong) gimp_object_get_memsize (GIMP_OBJECT (container),
+ NULL));
+#endif
+
+ gimp_image_undo_event (image, GIMP_UNDO_EVENT_UNDO_EXPIRED, freed);
+
+ g_object_unref (freed);
+
+ if (gimp_container_get_n_children (container) <= min_undo_levels)
+ return;
+ }
+}
+
+static void
+gimp_image_undo_free_redo (GimpImage *image)
+{
+ GimpImagePrivate *private = GIMP_IMAGE_GET_PRIVATE (image);
+ GimpContainer *container = private->redo_stack->undos;
+
+#ifdef DEBUG_IMAGE_UNDO
+ g_printerr ("redo_steps: %d redo_bytes: %ld\n",
+ gimp_container_get_n_children (container),
+ (glong) gimp_object_get_memsize (GIMP_OBJECT (container), NULL));
+#endif
+
+ if (gimp_container_is_empty (container))
+ return;
+
+ while (gimp_container_get_n_children (container) > 0)
+ {
+ GimpUndo *freed = gimp_undo_stack_free_bottom (private->redo_stack,
+ GIMP_UNDO_MODE_REDO);
+
+#ifdef DEBUG_IMAGE_UNDO
+ g_printerr ("freed one step: redo_steps: %d redo_bytes: %ld\n",
+ gimp_container_get_n_children (container),
+ (glong )gimp_object_get_memsize (GIMP_OBJECT (container),
+ NULL));
+#endif
+
+ gimp_image_undo_event (image, GIMP_UNDO_EVENT_REDO_EXPIRED, freed);
+
+ g_object_unref (freed);
+ }
+
+ /* We need to use <= here because the undo counter has already been
+ * incremented at this point.
+ */
+ if (private->dirty <= 0)
+ {
+ /* If the image was dirty, but could become clean by redo-ing
+ * some actions, then it should now become 'infinitely' dirty.
+ * This is because we've just nuked the actions that would allow
+ * the image to become clean again.
+ */
+ private->dirty = 100000;
+ }
+}
+
+static GimpDirtyMask
+gimp_image_undo_dirty_from_type (GimpUndoType undo_type)
+{
+ switch (undo_type)
+ {
+ case GIMP_UNDO_GROUP_IMAGE_SCALE:
+ case GIMP_UNDO_GROUP_IMAGE_RESIZE:
+ case GIMP_UNDO_GROUP_IMAGE_FLIP:
+ case GIMP_UNDO_GROUP_IMAGE_ROTATE:
+ case GIMP_UNDO_GROUP_IMAGE_TRANSFORM:
+ case GIMP_UNDO_GROUP_IMAGE_CROP:
+ return GIMP_DIRTY_IMAGE | GIMP_DIRTY_IMAGE_SIZE;
+
+ case GIMP_UNDO_GROUP_IMAGE_CONVERT:
+ return GIMP_DIRTY_IMAGE | GIMP_DIRTY_DRAWABLE;
+
+ case GIMP_UNDO_GROUP_IMAGE_LAYERS_MERGE:
+ return GIMP_DIRTY_IMAGE_STRUCTURE | GIMP_DIRTY_DRAWABLE;
+
+ case GIMP_UNDO_GROUP_IMAGE_VECTORS_MERGE:
+ return GIMP_DIRTY_IMAGE_STRUCTURE | GIMP_DIRTY_VECTORS;
+
+ case GIMP_UNDO_GROUP_IMAGE_QUICK_MASK: /* FIXME */
+ return GIMP_DIRTY_IMAGE_STRUCTURE | GIMP_DIRTY_SELECTION;
+
+ case GIMP_UNDO_GROUP_IMAGE_GRID:
+ case GIMP_UNDO_GROUP_GUIDE:
+ return GIMP_DIRTY_IMAGE_META;
+
+ case GIMP_UNDO_GROUP_DRAWABLE:
+ case GIMP_UNDO_GROUP_DRAWABLE_MOD:
+ return GIMP_DIRTY_ITEM | GIMP_DIRTY_DRAWABLE;
+
+ case GIMP_UNDO_GROUP_MASK: /* FIXME */
+ return GIMP_DIRTY_SELECTION;
+
+ case GIMP_UNDO_GROUP_ITEM_VISIBILITY:
+ case GIMP_UNDO_GROUP_ITEM_LINKED:
+ case GIMP_UNDO_GROUP_ITEM_PROPERTIES:
+ return GIMP_DIRTY_ITEM_META;
+
+ case GIMP_UNDO_GROUP_ITEM_DISPLACE: /* FIXME */
+ return GIMP_DIRTY_ITEM | GIMP_DIRTY_DRAWABLE | GIMP_DIRTY_VECTORS;
+
+ case GIMP_UNDO_GROUP_ITEM_SCALE: /* FIXME */
+ case GIMP_UNDO_GROUP_ITEM_RESIZE: /* FIXME */
+ return GIMP_DIRTY_ITEM | GIMP_DIRTY_DRAWABLE | GIMP_DIRTY_VECTORS;
+
+ case GIMP_UNDO_GROUP_LAYER_ADD_MASK:
+ case GIMP_UNDO_GROUP_LAYER_APPLY_MASK:
+ return GIMP_DIRTY_IMAGE_STRUCTURE;
+
+ case GIMP_UNDO_GROUP_FS_TO_LAYER:
+ case GIMP_UNDO_GROUP_FS_FLOAT:
+ case GIMP_UNDO_GROUP_FS_ANCHOR:
+ return GIMP_DIRTY_IMAGE_STRUCTURE;
+
+ case GIMP_UNDO_GROUP_EDIT_PASTE:
+ return GIMP_DIRTY_IMAGE_STRUCTURE;
+
+ case GIMP_UNDO_GROUP_EDIT_CUT:
+ return GIMP_DIRTY_ITEM | GIMP_DIRTY_DRAWABLE;
+
+ case GIMP_UNDO_GROUP_TEXT:
+ return GIMP_DIRTY_ITEM | GIMP_DIRTY_DRAWABLE;
+
+ case GIMP_UNDO_GROUP_TRANSFORM: /* FIXME */
+ return GIMP_DIRTY_ITEM | GIMP_DIRTY_DRAWABLE | GIMP_DIRTY_VECTORS;
+
+ case GIMP_UNDO_GROUP_PAINT:
+ return GIMP_DIRTY_ITEM | GIMP_DIRTY_DRAWABLE;
+
+ case GIMP_UNDO_GROUP_PARASITE_ATTACH:
+ case GIMP_UNDO_GROUP_PARASITE_REMOVE:
+ return GIMP_DIRTY_IMAGE_META | GIMP_DIRTY_ITEM_META;
+
+ case GIMP_UNDO_GROUP_VECTORS_IMPORT:
+ return GIMP_DIRTY_IMAGE_STRUCTURE | GIMP_DIRTY_VECTORS;
+
+ case GIMP_UNDO_GROUP_MISC:
+ return GIMP_DIRTY_ALL;
+
+ default:
+ break;
+ }
+
+ return GIMP_DIRTY_ALL;
+}
diff --git a/app/core/gimpimage-undo.h b/app/core/gimpimage-undo.h
new file mode 100644
index 0000000..34bd2f4
--- /dev/null
+++ b/app/core/gimpimage-undo.h
@@ -0,0 +1,57 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_IMAGE__UNDO_H__
+#define __GIMP_IMAGE__UNDO_H__
+
+
+gboolean gimp_image_undo_is_enabled (GimpImage *image);
+gboolean gimp_image_undo_enable (GimpImage *image);
+gboolean gimp_image_undo_disable (GimpImage *image);
+gboolean gimp_image_undo_freeze (GimpImage *image);
+gboolean gimp_image_undo_thaw (GimpImage *image);
+
+gboolean gimp_image_undo (GimpImage *image);
+gboolean gimp_image_redo (GimpImage *image);
+
+gboolean gimp_image_strong_undo (GimpImage *image);
+gboolean gimp_image_strong_redo (GimpImage *image);
+
+GimpUndoStack * gimp_image_get_undo_stack (GimpImage *image);
+GimpUndoStack * gimp_image_get_redo_stack (GimpImage *image);
+
+void gimp_image_undo_free (GimpImage *image);
+
+gint gimp_image_get_undo_group_count (GimpImage *image);
+gboolean gimp_image_undo_group_start (GimpImage *image,
+ GimpUndoType undo_type,
+ const gchar *name);
+gboolean gimp_image_undo_group_end (GimpImage *image);
+
+GimpUndo * gimp_image_undo_push (GimpImage *image,
+ GType object_type,
+ GimpUndoType undo_type,
+ const gchar *name,
+ GimpDirtyMask dirty_mask,
+ ...) G_GNUC_NULL_TERMINATED;
+
+GimpUndo * gimp_image_undo_can_compress (GimpImage *image,
+ GType object_type,
+ GimpUndoType undo_type);
+
+
+#endif /* __GIMP_IMAGE__UNDO_H__ */
diff --git a/app/core/gimpimage.c b/app/core/gimpimage.c
new file mode 100644
index 0000000..9908d1b
--- /dev/null
+++ b/app/core/gimpimage.c
@@ -0,0 +1,5191 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+#include <time.h>
+
+#include <cairo.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+#include <gexiv2/gexiv2.h>
+
+#include "libgimpcolor/gimpcolor.h"
+#include "libgimpmath/gimpmath.h"
+#include "libgimpbase/gimpbase.h"
+#include "libgimpconfig/gimpconfig.h"
+
+#include "core-types.h"
+
+#include "config/gimpcoreconfig.h"
+
+#include "operations/layer-modes/gimp-layer-modes.h"
+
+#include "gegl/gimp-babl.h"
+#include "gegl/gimp-gegl-loops.h"
+
+#include "gimp.h"
+#include "gimp-memsize.h"
+#include "gimp-parasites.h"
+#include "gimp-utils.h"
+#include "gimpcontext.h"
+#include "gimpdrawable-floating-selection.h"
+#include "gimpdrawablestack.h"
+#include "gimpgrid.h"
+#include "gimperror.h"
+#include "gimpguide.h"
+#include "gimpidtable.h"
+#include "gimpimage.h"
+#include "gimpimage-color-profile.h"
+#include "gimpimage-colormap.h"
+#include "gimpimage-guides.h"
+#include "gimpimage-item-list.h"
+#include "gimpimage-metadata.h"
+#include "gimpimage-sample-points.h"
+#include "gimpimage-preview.h"
+#include "gimpimage-private.h"
+#include "gimpimage-quick-mask.h"
+#include "gimpimage-symmetry.h"
+#include "gimpimage-undo.h"
+#include "gimpimage-undo-push.h"
+#include "gimpitemtree.h"
+#include "gimplayer.h"
+#include "gimplayer-floating-selection.h"
+#include "gimplayermask.h"
+#include "gimplayerstack.h"
+#include "gimpmarshal.h"
+#include "gimpparasitelist.h"
+#include "gimppickable.h"
+#include "gimpprojectable.h"
+#include "gimpprojection.h"
+#include "gimpsamplepoint.h"
+#include "gimpselection.h"
+#include "gimpsymmetry.h"
+#include "gimptempbuf.h"
+#include "gimptemplate.h"
+#include "gimpundostack.h"
+
+#include "vectors/gimpvectors.h"
+
+#include "gimp-log.h"
+#include "gimp-intl.h"
+
+
+#ifdef DEBUG
+#define TRC(x) g_printerr x
+#else
+#define TRC(x)
+#endif
+
+
+enum
+{
+ MODE_CHANGED,
+ PRECISION_CHANGED,
+ ALPHA_CHANGED,
+ FLOATING_SELECTION_CHANGED,
+ ACTIVE_LAYER_CHANGED,
+ ACTIVE_CHANNEL_CHANGED,
+ ACTIVE_VECTORS_CHANGED,
+ LINKED_ITEMS_CHANGED,
+ COMPONENT_VISIBILITY_CHANGED,
+ COMPONENT_ACTIVE_CHANGED,
+ MASK_CHANGED,
+ RESOLUTION_CHANGED,
+ SIZE_CHANGED_DETAILED,
+ UNIT_CHANGED,
+ QUICK_MASK_CHANGED,
+ SELECTION_INVALIDATE,
+ CLEAN,
+ DIRTY,
+ SAVING,
+ SAVED,
+ EXPORTED,
+ GUIDE_ADDED,
+ GUIDE_REMOVED,
+ GUIDE_MOVED,
+ SAMPLE_POINT_ADDED,
+ SAMPLE_POINT_REMOVED,
+ SAMPLE_POINT_MOVED,
+ PARASITE_ATTACHED,
+ PARASITE_DETACHED,
+ COLORMAP_CHANGED,
+ UNDO_EVENT,
+ LAST_SIGNAL
+};
+
+enum
+{
+ PROP_0,
+ PROP_GIMP,
+ PROP_ID,
+ PROP_WIDTH,
+ PROP_HEIGHT,
+ PROP_BASE_TYPE,
+ PROP_PRECISION,
+ PROP_METADATA,
+ PROP_BUFFER,
+ PROP_SYMMETRY,
+ PROP_CONVERTING,
+};
+
+
+/* local function prototypes */
+
+static void gimp_color_managed_iface_init (GimpColorManagedInterface *iface);
+static void gimp_projectable_iface_init (GimpProjectableInterface *iface);
+static void gimp_pickable_iface_init (GimpPickableInterface *iface);
+
+static void gimp_image_constructed (GObject *object);
+static void gimp_image_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_image_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+static void gimp_image_dispose (GObject *object);
+static void gimp_image_finalize (GObject *object);
+
+static void gimp_image_name_changed (GimpObject *object);
+static gint64 gimp_image_get_memsize (GimpObject *object,
+ gint64 *gui_size);
+
+static gboolean gimp_image_get_size (GimpViewable *viewable,
+ gint *width,
+ gint *height);
+static void gimp_image_size_changed (GimpViewable *viewable);
+static gchar * gimp_image_get_description (GimpViewable *viewable,
+ gchar **tooltip);
+
+static void gimp_image_real_mode_changed (GimpImage *image);
+static void gimp_image_real_precision_changed(GimpImage *image);
+static void gimp_image_real_resolution_changed(GimpImage *image);
+static void gimp_image_real_size_changed_detailed
+ (GimpImage *image,
+ gint previous_origin_x,
+ gint previous_origin_y,
+ gint previous_width,
+ gint previous_height);
+static void gimp_image_real_unit_changed (GimpImage *image);
+static void gimp_image_real_colormap_changed (GimpImage *image,
+ gint color_index);
+
+static const guint8 *
+ gimp_image_color_managed_get_icc_profile (GimpColorManaged *managed,
+ gsize *len);
+static GimpColorProfile *
+ gimp_image_color_managed_get_color_profile (GimpColorManaged *managed);
+static void
+ gimp_image_color_managed_profile_changed (GimpColorManaged *managed);
+
+static void gimp_image_projectable_flush (GimpProjectable *projectable,
+ gboolean invalidate_preview);
+static GeglRectangle gimp_image_get_bounding_box (GimpProjectable *projectable);
+static GeglNode * gimp_image_get_graph (GimpProjectable *projectable);
+static GimpImage * gimp_image_get_image (GimpProjectable *projectable);
+static const Babl * gimp_image_get_proj_format (GimpProjectable *projectable);
+
+static void gimp_image_pickable_flush (GimpPickable *pickable);
+static GeglBuffer * gimp_image_get_buffer (GimpPickable *pickable);
+static gboolean gimp_image_get_pixel_at (GimpPickable *pickable,
+ gint x,
+ gint y,
+ const Babl *format,
+ gpointer pixel);
+static gdouble gimp_image_get_opacity_at (GimpPickable *pickable,
+ gint x,
+ gint y);
+static void gimp_image_get_pixel_average (GimpPickable *pickable,
+ const GeglRectangle *rect,
+ const Babl *format,
+ gpointer pixel);
+static void gimp_image_pixel_to_srgb (GimpPickable *pickable,
+ const Babl *format,
+ gpointer pixel,
+ GimpRGB *color);
+static void gimp_image_srgb_to_pixel (GimpPickable *pickable,
+ const GimpRGB *color,
+ const Babl *format,
+ gpointer pixel);
+
+static void gimp_image_projection_buffer_notify
+ (GimpProjection *projection,
+ const GParamSpec *pspec,
+ GimpImage *image);
+static void gimp_image_mask_update (GimpDrawable *drawable,
+ gint x,
+ gint y,
+ gint width,
+ gint height,
+ GimpImage *image);
+static void gimp_image_layers_changed (GimpContainer *container,
+ GimpChannel *channel,
+ GimpImage *image);
+static void gimp_image_layer_offset_changed (GimpDrawable *drawable,
+ const GParamSpec *pspec,
+ GimpImage *image);
+static void gimp_image_layer_bounding_box_changed
+ (GimpDrawable *drawable,
+ GimpImage *image);
+static void gimp_image_layer_alpha_changed (GimpDrawable *drawable,
+ GimpImage *image);
+static void gimp_image_channel_add (GimpContainer *container,
+ GimpChannel *channel,
+ GimpImage *image);
+static void gimp_image_channel_remove (GimpContainer *container,
+ GimpChannel *channel,
+ GimpImage *image);
+static void gimp_image_channel_name_changed (GimpChannel *channel,
+ GimpImage *image);
+static void gimp_image_channel_color_changed (GimpChannel *channel,
+ GimpImage *image);
+static void gimp_image_active_layer_notify (GimpItemTree *tree,
+ const GParamSpec *pspec,
+ GimpImage *image);
+static void gimp_image_active_channel_notify (GimpItemTree *tree,
+ const GParamSpec *pspec,
+ GimpImage *image);
+static void gimp_image_active_vectors_notify (GimpItemTree *tree,
+ const GParamSpec *pspec,
+ GimpImage *image);
+
+static void gimp_image_freeze_bounding_box (GimpImage *image);
+static void gimp_image_thaw_bounding_box (GimpImage *image);
+static void gimp_image_update_bounding_box (GimpImage *image);
+
+
+G_DEFINE_TYPE_WITH_CODE (GimpImage, gimp_image, GIMP_TYPE_VIEWABLE,
+ G_ADD_PRIVATE (GimpImage)
+ G_IMPLEMENT_INTERFACE (GIMP_TYPE_COLOR_MANAGED,
+ gimp_color_managed_iface_init)
+ G_IMPLEMENT_INTERFACE (GIMP_TYPE_PROJECTABLE,
+ gimp_projectable_iface_init)
+ G_IMPLEMENT_INTERFACE (GIMP_TYPE_PICKABLE,
+ gimp_pickable_iface_init))
+
+#define parent_class gimp_image_parent_class
+
+static guint gimp_image_signals[LAST_SIGNAL] = { 0 };
+
+
+static void
+gimp_image_class_init (GimpImageClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpObjectClass *gimp_object_class = GIMP_OBJECT_CLASS (klass);
+ GimpViewableClass *viewable_class = GIMP_VIEWABLE_CLASS (klass);
+
+ gimp_image_signals[MODE_CHANGED] =
+ g_signal_new ("mode-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpImageClass, mode_changed),
+ NULL, NULL,
+ gimp_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ gimp_image_signals[PRECISION_CHANGED] =
+ g_signal_new ("precision-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpImageClass, precision_changed),
+ NULL, NULL,
+ gimp_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ gimp_image_signals[ALPHA_CHANGED] =
+ g_signal_new ("alpha-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpImageClass, alpha_changed),
+ NULL, NULL,
+ gimp_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ gimp_image_signals[FLOATING_SELECTION_CHANGED] =
+ g_signal_new ("floating-selection-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpImageClass, floating_selection_changed),
+ NULL, NULL,
+ gimp_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ gimp_image_signals[ACTIVE_LAYER_CHANGED] =
+ g_signal_new ("active-layer-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpImageClass, active_layer_changed),
+ NULL, NULL,
+ gimp_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ gimp_image_signals[ACTIVE_CHANNEL_CHANGED] =
+ g_signal_new ("active-channel-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpImageClass, active_channel_changed),
+ NULL, NULL,
+ gimp_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ gimp_image_signals[ACTIVE_VECTORS_CHANGED] =
+ g_signal_new ("active-vectors-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpImageClass, active_vectors_changed),
+ NULL, NULL,
+ gimp_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ gimp_image_signals[LINKED_ITEMS_CHANGED] =
+ g_signal_new ("linked-items-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpImageClass, linked_items_changed),
+ NULL, NULL,
+ gimp_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ gimp_image_signals[COMPONENT_VISIBILITY_CHANGED] =
+ g_signal_new ("component-visibility-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpImageClass, component_visibility_changed),
+ NULL, NULL,
+ gimp_marshal_VOID__ENUM,
+ G_TYPE_NONE, 1,
+ GIMP_TYPE_CHANNEL_TYPE);
+
+ gimp_image_signals[COMPONENT_ACTIVE_CHANGED] =
+ g_signal_new ("component-active-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpImageClass, component_active_changed),
+ NULL, NULL,
+ gimp_marshal_VOID__ENUM,
+ G_TYPE_NONE, 1,
+ GIMP_TYPE_CHANNEL_TYPE);
+
+ gimp_image_signals[MASK_CHANGED] =
+ g_signal_new ("mask-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpImageClass, mask_changed),
+ NULL, NULL,
+ gimp_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ gimp_image_signals[RESOLUTION_CHANGED] =
+ g_signal_new ("resolution-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpImageClass, resolution_changed),
+ NULL, NULL,
+ gimp_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ gimp_image_signals[SIZE_CHANGED_DETAILED] =
+ g_signal_new ("size-changed-detailed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpImageClass, size_changed_detailed),
+ NULL, NULL,
+ gimp_marshal_VOID__INT_INT_INT_INT,
+ G_TYPE_NONE, 4,
+ G_TYPE_INT,
+ G_TYPE_INT,
+ G_TYPE_INT,
+ G_TYPE_INT);
+
+ gimp_image_signals[UNIT_CHANGED] =
+ g_signal_new ("unit-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpImageClass, unit_changed),
+ NULL, NULL,
+ gimp_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ gimp_image_signals[QUICK_MASK_CHANGED] =
+ g_signal_new ("quick-mask-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpImageClass, quick_mask_changed),
+ NULL, NULL,
+ gimp_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ gimp_image_signals[SELECTION_INVALIDATE] =
+ g_signal_new ("selection-invalidate",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpImageClass, selection_invalidate),
+ NULL, NULL,
+ gimp_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ gimp_image_signals[CLEAN] =
+ g_signal_new ("clean",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpImageClass, clean),
+ NULL, NULL,
+ gimp_marshal_VOID__FLAGS,
+ G_TYPE_NONE, 1,
+ GIMP_TYPE_DIRTY_MASK);
+
+ gimp_image_signals[DIRTY] =
+ g_signal_new ("dirty",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpImageClass, dirty),
+ NULL, NULL,
+ gimp_marshal_VOID__FLAGS,
+ G_TYPE_NONE, 1,
+ GIMP_TYPE_DIRTY_MASK);
+
+ gimp_image_signals[SAVING] =
+ g_signal_new ("saving",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpImageClass, saving),
+ NULL, NULL,
+ gimp_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ gimp_image_signals[SAVED] =
+ g_signal_new ("saved",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpImageClass, saved),
+ NULL, NULL,
+ gimp_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1,
+ G_TYPE_FILE);
+
+ gimp_image_signals[EXPORTED] =
+ g_signal_new ("exported",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpImageClass, exported),
+ NULL, NULL,
+ gimp_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1,
+ G_TYPE_FILE);
+
+ gimp_image_signals[GUIDE_ADDED] =
+ g_signal_new ("guide-added",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpImageClass, guide_added),
+ NULL, NULL,
+ gimp_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1,
+ GIMP_TYPE_GUIDE);
+
+ gimp_image_signals[GUIDE_REMOVED] =
+ g_signal_new ("guide-removed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpImageClass, guide_removed),
+ NULL, NULL,
+ gimp_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1,
+ GIMP_TYPE_GUIDE);
+
+ gimp_image_signals[GUIDE_MOVED] =
+ g_signal_new ("guide-moved",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpImageClass, guide_moved),
+ NULL, NULL,
+ gimp_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1,
+ GIMP_TYPE_GUIDE);
+
+ gimp_image_signals[SAMPLE_POINT_ADDED] =
+ g_signal_new ("sample-point-added",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpImageClass, sample_point_added),
+ NULL, NULL,
+ gimp_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1,
+ GIMP_TYPE_SAMPLE_POINT);
+
+ gimp_image_signals[SAMPLE_POINT_REMOVED] =
+ g_signal_new ("sample-point-removed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpImageClass, sample_point_removed),
+ NULL, NULL,
+ gimp_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1,
+ GIMP_TYPE_SAMPLE_POINT);
+
+ gimp_image_signals[SAMPLE_POINT_MOVED] =
+ g_signal_new ("sample-point-moved",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpImageClass, sample_point_moved),
+ NULL, NULL,
+ gimp_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1,
+ GIMP_TYPE_SAMPLE_POINT);
+
+ gimp_image_signals[PARASITE_ATTACHED] =
+ g_signal_new ("parasite-attached",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpImageClass, parasite_attached),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__STRING,
+ G_TYPE_NONE, 1,
+ G_TYPE_STRING);
+
+ gimp_image_signals[PARASITE_DETACHED] =
+ g_signal_new ("parasite-detached",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpImageClass, parasite_detached),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__STRING,
+ G_TYPE_NONE, 1,
+ G_TYPE_STRING);
+
+ gimp_image_signals[COLORMAP_CHANGED] =
+ g_signal_new ("colormap-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpImageClass, colormap_changed),
+ NULL, NULL,
+ gimp_marshal_VOID__INT,
+ G_TYPE_NONE, 1,
+ G_TYPE_INT);
+
+ gimp_image_signals[UNDO_EVENT] =
+ g_signal_new ("undo-event",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpImageClass, undo_event),
+ NULL, NULL,
+ gimp_marshal_VOID__ENUM_OBJECT,
+ G_TYPE_NONE, 2,
+ GIMP_TYPE_UNDO_EVENT,
+ GIMP_TYPE_UNDO);
+
+ object_class->constructed = gimp_image_constructed;
+ object_class->set_property = gimp_image_set_property;
+ object_class->get_property = gimp_image_get_property;
+ object_class->dispose = gimp_image_dispose;
+ object_class->finalize = gimp_image_finalize;
+
+ gimp_object_class->name_changed = gimp_image_name_changed;
+ gimp_object_class->get_memsize = gimp_image_get_memsize;
+
+ viewable_class->default_icon_name = "gimp-image";
+ viewable_class->get_size = gimp_image_get_size;
+ viewable_class->size_changed = gimp_image_size_changed;
+ viewable_class->get_preview_size = gimp_image_get_preview_size;
+ viewable_class->get_popup_size = gimp_image_get_popup_size;
+ viewable_class->get_new_preview = gimp_image_get_new_preview;
+ viewable_class->get_new_pixbuf = gimp_image_get_new_pixbuf;
+ viewable_class->get_description = gimp_image_get_description;
+
+ klass->mode_changed = gimp_image_real_mode_changed;
+ klass->precision_changed = gimp_image_real_precision_changed;
+ klass->alpha_changed = NULL;
+ klass->floating_selection_changed = NULL;
+ klass->active_layer_changed = NULL;
+ klass->active_channel_changed = NULL;
+ klass->active_vectors_changed = NULL;
+ klass->linked_items_changed = NULL;
+ klass->component_visibility_changed = NULL;
+ klass->component_active_changed = NULL;
+ klass->mask_changed = NULL;
+ klass->resolution_changed = gimp_image_real_resolution_changed;
+ klass->size_changed_detailed = gimp_image_real_size_changed_detailed;
+ klass->unit_changed = gimp_image_real_unit_changed;
+ klass->quick_mask_changed = NULL;
+ klass->selection_invalidate = NULL;
+
+ klass->clean = NULL;
+ klass->dirty = NULL;
+ klass->saving = NULL;
+ klass->saved = NULL;
+ klass->exported = NULL;
+ klass->guide_added = NULL;
+ klass->guide_removed = NULL;
+ klass->guide_moved = NULL;
+ klass->sample_point_added = NULL;
+ klass->sample_point_removed = NULL;
+ klass->sample_point_moved = NULL;
+ klass->parasite_attached = NULL;
+ klass->parasite_detached = NULL;
+ klass->colormap_changed = gimp_image_real_colormap_changed;
+ klass->undo_event = NULL;
+
+ g_object_class_install_property (object_class, PROP_GIMP,
+ g_param_spec_object ("gimp", NULL, NULL,
+ GIMP_TYPE_GIMP,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+
+ g_object_class_install_property (object_class, PROP_ID,
+ g_param_spec_int ("id", NULL, NULL,
+ 0, G_MAXINT, 0,
+ GIMP_PARAM_READABLE));
+
+ g_object_class_install_property (object_class, PROP_WIDTH,
+ g_param_spec_int ("width", NULL, NULL,
+ 1, GIMP_MAX_IMAGE_SIZE, 1,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_HEIGHT,
+ g_param_spec_int ("height", NULL, NULL,
+ 1, GIMP_MAX_IMAGE_SIZE, 1,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_BASE_TYPE,
+ g_param_spec_enum ("base-type", NULL, NULL,
+ GIMP_TYPE_IMAGE_BASE_TYPE,
+ GIMP_RGB,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_PRECISION,
+ g_param_spec_enum ("precision", NULL, NULL,
+ GIMP_TYPE_PRECISION,
+ GIMP_PRECISION_U8_GAMMA,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_METADATA,
+ g_param_spec_object ("metadata", NULL, NULL,
+ GEXIV2_TYPE_METADATA,
+ GIMP_PARAM_READABLE));
+
+ g_object_class_override_property (object_class, PROP_BUFFER, "buffer");
+
+ g_object_class_install_property (object_class, PROP_SYMMETRY,
+ g_param_spec_gtype ("symmetry",
+ NULL, _("Symmetry"),
+ GIMP_TYPE_SYMMETRY,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_CONVERTING,
+ g_param_spec_boolean ("converting",
+ NULL, NULL,
+ FALSE,
+ GIMP_PARAM_READWRITE));
+}
+
+static void
+gimp_color_managed_iface_init (GimpColorManagedInterface *iface)
+{
+ iface->get_icc_profile = gimp_image_color_managed_get_icc_profile;
+ iface->get_color_profile = gimp_image_color_managed_get_color_profile;
+ iface->profile_changed = gimp_image_color_managed_profile_changed;
+}
+
+static void
+gimp_projectable_iface_init (GimpProjectableInterface *iface)
+{
+ iface->flush = gimp_image_projectable_flush;
+ iface->get_image = gimp_image_get_image;
+ iface->get_format = gimp_image_get_proj_format;
+ iface->get_bounding_box = gimp_image_get_bounding_box;
+ iface->get_graph = gimp_image_get_graph;
+ iface->invalidate_preview = (void (*) (GimpProjectable*)) gimp_viewable_invalidate_preview;
+}
+
+static void
+gimp_pickable_iface_init (GimpPickableInterface *iface)
+{
+ iface->flush = gimp_image_pickable_flush;
+ iface->get_image = (GimpImage * (*) (GimpPickable *pickable)) gimp_image_get_image;
+ iface->get_format = (const Babl * (*) (GimpPickable *pickable)) gimp_image_get_proj_format;
+ iface->get_format_with_alpha = (const Babl * (*) (GimpPickable *pickable)) gimp_image_get_proj_format;
+ iface->get_buffer = gimp_image_get_buffer;
+ iface->get_pixel_at = gimp_image_get_pixel_at;
+ iface->get_opacity_at = gimp_image_get_opacity_at;
+ iface->get_pixel_average = gimp_image_get_pixel_average;
+ iface->pixel_to_srgb = gimp_image_pixel_to_srgb;
+ iface->srgb_to_pixel = gimp_image_srgb_to_pixel;
+}
+
+static void
+gimp_image_init (GimpImage *image)
+{
+ GimpImagePrivate *private = gimp_image_get_instance_private (image);
+ gint i;
+
+ image->priv = private;
+
+ private->ID = 0;
+
+ private->load_proc = NULL;
+ private->save_proc = NULL;
+
+ private->width = 0;
+ private->height = 0;
+ private->xresolution = 1.0;
+ private->yresolution = 1.0;
+ private->resolution_set = FALSE;
+ private->resolution_unit = GIMP_UNIT_INCH;
+ private->base_type = GIMP_RGB;
+ private->precision = GIMP_PRECISION_U8_GAMMA;
+ private->new_layer_mode = -1;
+
+ private->show_all = 0;
+ private->bounding_box.x = 0;
+ private->bounding_box.y = 0;
+ private->bounding_box.width = 0;
+ private->bounding_box.height = 0;
+ private->pickable_buffer = NULL;
+
+ private->colormap = NULL;
+ private->n_colors = 0;
+ private->palette = NULL;
+
+ private->is_color_managed = TRUE;
+
+ private->metadata = NULL;
+
+ private->dirty = 1;
+ private->dirty_time = 0;
+ private->undo_freeze_count = 0;
+
+ private->export_dirty = 1;
+
+ private->instance_count = 0;
+ private->disp_count = 0;
+
+ private->tattoo_state = 0;
+
+ private->projection = gimp_projection_new (GIMP_PROJECTABLE (image));
+
+ private->symmetries = NULL;
+ private->active_symmetry = NULL;
+
+ private->guides = NULL;
+ private->grid = NULL;
+ private->sample_points = NULL;
+
+ private->layers = gimp_item_tree_new (image,
+ GIMP_TYPE_LAYER_STACK,
+ GIMP_TYPE_LAYER);
+ private->channels = gimp_item_tree_new (image,
+ GIMP_TYPE_DRAWABLE_STACK,
+ GIMP_TYPE_CHANNEL);
+ private->vectors = gimp_item_tree_new (image,
+ GIMP_TYPE_ITEM_STACK,
+ GIMP_TYPE_VECTORS);
+ private->layer_stack = NULL;
+
+ g_signal_connect (private->projection, "notify::buffer",
+ G_CALLBACK (gimp_image_projection_buffer_notify),
+ image);
+
+ g_signal_connect (private->layers, "notify::active-item",
+ G_CALLBACK (gimp_image_active_layer_notify),
+ image);
+ g_signal_connect (private->channels, "notify::active-item",
+ G_CALLBACK (gimp_image_active_channel_notify),
+ image);
+ g_signal_connect (private->vectors, "notify::active-item",
+ G_CALLBACK (gimp_image_active_vectors_notify),
+ image);
+
+ g_signal_connect_swapped (private->layers->container, "update",
+ G_CALLBACK (gimp_image_invalidate),
+ image);
+
+ private->layer_offset_x_handler =
+ gimp_container_add_handler (private->layers->container, "notify::offset-x",
+ G_CALLBACK (gimp_image_layer_offset_changed),
+ image);
+ private->layer_offset_y_handler =
+ gimp_container_add_handler (private->layers->container, "notify::offset-y",
+ G_CALLBACK (gimp_image_layer_offset_changed),
+ image);
+ private->layer_bounding_box_handler =
+ gimp_container_add_handler (private->layers->container, "bounding-box-changed",
+ G_CALLBACK (gimp_image_layer_bounding_box_changed),
+ image);
+ private->layer_alpha_handler =
+ gimp_container_add_handler (private->layers->container, "alpha-changed",
+ G_CALLBACK (gimp_image_layer_alpha_changed),
+ image);
+
+ g_signal_connect (private->layers->container, "add",
+ G_CALLBACK (gimp_image_layers_changed),
+ image);
+ g_signal_connect (private->layers->container, "remove",
+ G_CALLBACK (gimp_image_layers_changed),
+ image);
+
+ g_signal_connect_swapped (private->channels->container, "update",
+ G_CALLBACK (gimp_image_invalidate),
+ image);
+
+ private->channel_name_changed_handler =
+ gimp_container_add_handler (private->channels->container, "name-changed",
+ G_CALLBACK (gimp_image_channel_name_changed),
+ image);
+ private->channel_color_changed_handler =
+ gimp_container_add_handler (private->channels->container, "color-changed",
+ G_CALLBACK (gimp_image_channel_color_changed),
+ image);
+
+ g_signal_connect (private->channels->container, "add",
+ G_CALLBACK (gimp_image_channel_add),
+ image);
+ g_signal_connect (private->channels->container, "remove",
+ G_CALLBACK (gimp_image_channel_remove),
+ image);
+
+ private->floating_sel = NULL;
+ private->selection_mask = NULL;
+
+ private->parasites = gimp_parasite_list_new ();
+
+ for (i = 0; i < MAX_CHANNELS; i++)
+ {
+ private->visible[i] = TRUE;
+ private->active[i] = TRUE;
+ }
+
+ private->quick_mask_state = FALSE;
+ private->quick_mask_inverted = FALSE;
+ gimp_rgba_set (&private->quick_mask_color, 1.0, 0.0, 0.0, 0.5);
+
+ private->undo_stack = gimp_undo_stack_new (image);
+ private->redo_stack = gimp_undo_stack_new (image);
+ private->group_count = 0;
+ private->pushing_undo_group = GIMP_UNDO_GROUP_NONE;
+
+ private->flush_accum.alpha_changed = FALSE;
+ private->flush_accum.mask_changed = FALSE;
+ private->flush_accum.floating_selection_changed = FALSE;
+ private->flush_accum.preview_invalidated = FALSE;
+}
+
+static void
+gimp_image_constructed (GObject *object)
+{
+ GimpImage *image = GIMP_IMAGE (object);
+ GimpImagePrivate *private = GIMP_IMAGE_GET_PRIVATE (image);
+ GimpChannel *selection;
+ GimpCoreConfig *config;
+ GimpTemplate *template;
+
+ G_OBJECT_CLASS (parent_class)->constructed (object);
+
+ gimp_assert (GIMP_IS_GIMP (image->gimp));
+
+ config = image->gimp->config;
+
+ private->ID = gimp_id_table_insert (image->gimp->image_table, image);
+
+ template = config->default_image;
+
+ private->xresolution = gimp_template_get_resolution_x (template);
+ private->yresolution = gimp_template_get_resolution_y (template);
+ private->resolution_unit = gimp_template_get_resolution_unit (template);
+
+ private->grid = gimp_config_duplicate (GIMP_CONFIG (config->default_grid));
+
+ private->quick_mask_color = config->quick_mask_color;
+
+ gimp_image_update_bounding_box (image);
+
+ if (private->base_type == GIMP_INDEXED)
+ gimp_image_colormap_init (image);
+
+ selection = gimp_selection_new (image,
+ gimp_image_get_width (image),
+ gimp_image_get_height (image));
+ gimp_image_take_mask (image, selection);
+
+ g_signal_connect_object (config, "notify::transparency-type",
+ G_CALLBACK (gimp_item_stack_invalidate_previews),
+ private->layers->container, G_CONNECT_SWAPPED);
+ g_signal_connect_object (config, "notify::transparency-size",
+ G_CALLBACK (gimp_item_stack_invalidate_previews),
+ private->layers->container, G_CONNECT_SWAPPED);
+ g_signal_connect_object (config, "notify::layer-previews",
+ G_CALLBACK (gimp_viewable_size_changed),
+ image, G_CONNECT_SWAPPED);
+ g_signal_connect_object (config, "notify::group-layer-previews",
+ G_CALLBACK (gimp_viewable_size_changed),
+ image, G_CONNECT_SWAPPED);
+
+ gimp_container_add (image->gimp->images, GIMP_OBJECT (image));
+}
+
+static void
+gimp_image_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpImage *image = GIMP_IMAGE (object);
+ GimpImagePrivate *private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ switch (property_id)
+ {
+ case PROP_GIMP:
+ image->gimp = g_value_get_object (value);
+ break;
+
+ case PROP_WIDTH:
+ private->width = g_value_get_int (value);
+ break;
+ case PROP_HEIGHT:
+ private->height = g_value_get_int (value);
+ break;
+
+ case PROP_BASE_TYPE:
+ private->base_type = g_value_get_enum (value);
+ _gimp_image_free_color_transforms (image);
+ break;
+
+ case PROP_PRECISION:
+ private->precision = g_value_get_enum (value);
+ _gimp_image_free_color_transforms (image);
+ break;
+
+ case PROP_SYMMETRY:
+ {
+ GList *iter;
+ GType type = g_value_get_gtype (value);
+
+ if (private->active_symmetry)
+ g_object_set (private->active_symmetry,
+ "active", FALSE,
+ NULL);
+ private->active_symmetry = NULL;
+
+ for (iter = private->symmetries; iter; iter = g_list_next (iter))
+ {
+ GimpSymmetry *sym = iter->data;
+
+ if (type == G_TYPE_FROM_INSTANCE (sym))
+ private->active_symmetry = iter->data;
+ }
+
+ if (! private->active_symmetry &&
+ g_type_is_a (type, GIMP_TYPE_SYMMETRY))
+ {
+ GimpSymmetry *sym = gimp_image_symmetry_new (image, type);
+
+ gimp_image_symmetry_add (image, sym);
+ g_object_unref (sym);
+
+ private->active_symmetry = sym;
+ }
+
+ if (private->active_symmetry)
+ g_object_set (private->active_symmetry,
+ "active", TRUE,
+ NULL);
+ }
+ break;
+
+ case PROP_CONVERTING:
+ private->converting = g_value_get_boolean (value);
+ break;
+
+ case PROP_ID:
+ case PROP_METADATA:
+ case PROP_BUFFER:
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_image_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpImage *image = GIMP_IMAGE (object);
+ GimpImagePrivate *private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ switch (property_id)
+ {
+ case PROP_GIMP:
+ g_value_set_object (value, image->gimp);
+ break;
+ case PROP_ID:
+ g_value_set_int (value, private->ID);
+ break;
+ case PROP_WIDTH:
+ g_value_set_int (value, private->width);
+ break;
+ case PROP_HEIGHT:
+ g_value_set_int (value, private->height);
+ break;
+ case PROP_BASE_TYPE:
+ g_value_set_enum (value, private->base_type);
+ break;
+ case PROP_PRECISION:
+ g_value_set_enum (value, private->precision);
+ break;
+ case PROP_METADATA:
+ g_value_set_object (value, gimp_image_get_metadata (image));
+ break;
+ case PROP_BUFFER:
+ g_value_set_object (value, gimp_image_get_buffer (GIMP_PICKABLE (image)));
+ break;
+ case PROP_SYMMETRY:
+ g_value_set_gtype (value,
+ private->active_symmetry ?
+ G_TYPE_FROM_INSTANCE (private->active_symmetry) :
+ G_TYPE_NONE);
+ break;
+ case PROP_CONVERTING:
+ g_value_set_boolean (value, private->converting);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_image_dispose (GObject *object)
+{
+ GimpImage *image = GIMP_IMAGE (object);
+ GimpImagePrivate *private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ if (private->colormap)
+ gimp_image_colormap_dispose (image);
+
+ gimp_image_undo_free (image);
+
+ g_signal_handlers_disconnect_by_func (private->layers->container,
+ gimp_image_invalidate,
+ image);
+
+ gimp_container_remove_handler (private->layers->container,
+ private->layer_offset_x_handler);
+ gimp_container_remove_handler (private->layers->container,
+ private->layer_offset_y_handler);
+ gimp_container_remove_handler (private->layers->container,
+ private->layer_bounding_box_handler);
+ gimp_container_remove_handler (private->layers->container,
+ private->layer_alpha_handler);
+
+ g_signal_handlers_disconnect_by_func (private->layers->container,
+ gimp_image_layers_changed,
+ image);
+
+ g_signal_handlers_disconnect_by_func (private->channels->container,
+ gimp_image_invalidate,
+ image);
+
+ gimp_container_remove_handler (private->channels->container,
+ private->channel_name_changed_handler);
+ gimp_container_remove_handler (private->channels->container,
+ private->channel_color_changed_handler);
+
+ g_signal_handlers_disconnect_by_func (private->channels->container,
+ gimp_image_channel_add,
+ image);
+ g_signal_handlers_disconnect_by_func (private->channels->container,
+ gimp_image_channel_remove,
+ image);
+
+ g_object_run_dispose (G_OBJECT (private->layers));
+ g_object_run_dispose (G_OBJECT (private->channels));
+ g_object_run_dispose (G_OBJECT (private->vectors));
+
+ G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+gimp_image_finalize (GObject *object)
+{
+ GimpImage *image = GIMP_IMAGE (object);
+ GimpImagePrivate *private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ g_clear_object (&private->projection);
+ g_clear_object (&private->graph);
+ private->visible_mask = NULL;
+
+ if (private->colormap)
+ gimp_image_colormap_free (image);
+
+ if (private->color_profile)
+ _gimp_image_free_color_profile (image);
+
+ g_clear_object (&private->pickable_buffer);
+ g_clear_object (&private->metadata);
+ g_clear_object (&private->file);
+ g_clear_object (&private->imported_file);
+ g_clear_object (&private->exported_file);
+ g_clear_object (&private->save_a_copy_file);
+ g_clear_object (&private->untitled_file);
+ g_clear_object (&private->layers);
+ g_clear_object (&private->channels);
+ g_clear_object (&private->vectors);
+
+ if (private->layer_stack)
+ {
+ g_slist_free (private->layer_stack);
+ private->layer_stack = NULL;
+ }
+
+ g_clear_object (&private->selection_mask);
+ g_clear_object (&private->parasites);
+
+ if (private->guides)
+ {
+ g_list_free_full (private->guides, (GDestroyNotify) g_object_unref);
+ private->guides = NULL;
+ }
+
+ if (private->symmetries)
+ {
+ g_list_free_full (private->symmetries, g_object_unref);
+ private->symmetries = NULL;
+ }
+
+ g_clear_object (&private->grid);
+
+ if (private->sample_points)
+ {
+ g_list_free_full (private->sample_points,
+ (GDestroyNotify) g_object_unref);
+ private->sample_points = NULL;
+ }
+
+ g_clear_object (&private->undo_stack);
+ g_clear_object (&private->redo_stack);
+
+ if (image->gimp && image->gimp->image_table)
+ {
+ gimp_id_table_remove (image->gimp->image_table, private->ID);
+ image->gimp = NULL;
+ }
+
+ g_clear_pointer (&private->display_name, g_free);
+ g_clear_pointer (&private->display_path, g_free);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gimp_image_name_changed (GimpObject *object)
+{
+ GimpImage *image = GIMP_IMAGE (object);
+ GimpImagePrivate *private = GIMP_IMAGE_GET_PRIVATE (image);
+ const gchar *name;
+
+ if (GIMP_OBJECT_CLASS (parent_class)->name_changed)
+ GIMP_OBJECT_CLASS (parent_class)->name_changed (object);
+
+ g_clear_pointer (&private->display_name, g_free);
+ g_clear_pointer (&private->display_path, g_free);
+
+ /* We never want the empty string as a name, so change empty strings
+ * to NULL strings (without emitting the "name-changed" signal
+ * again)
+ */
+ name = gimp_object_get_name (object);
+ if (name && strlen (name) == 0)
+ {
+ gimp_object_name_free (object);
+ name = NULL;
+ }
+
+ g_clear_object (&private->file);
+
+ if (name)
+ private->file = g_file_new_for_uri (name);
+}
+
+static gint64
+gimp_image_get_memsize (GimpObject *object,
+ gint64 *gui_size)
+{
+ GimpImage *image = GIMP_IMAGE (object);
+ GimpImagePrivate *private = GIMP_IMAGE_GET_PRIVATE (image);
+ gint64 memsize = 0;
+
+ if (gimp_image_get_colormap (image))
+ memsize += GIMP_IMAGE_COLORMAP_SIZE;
+
+ memsize += gimp_object_get_memsize (GIMP_OBJECT (private->palette),
+ gui_size);
+
+ memsize += gimp_object_get_memsize (GIMP_OBJECT (private->projection),
+ gui_size);
+
+ memsize += gimp_g_list_get_memsize (gimp_image_get_guides (image),
+ sizeof (GimpGuide));
+
+ memsize += gimp_object_get_memsize (GIMP_OBJECT (private->grid), gui_size);
+
+ memsize += gimp_g_list_get_memsize (gimp_image_get_sample_points (image),
+ sizeof (GimpSamplePoint));
+
+ memsize += gimp_object_get_memsize (GIMP_OBJECT (private->layers),
+ gui_size);
+ memsize += gimp_object_get_memsize (GIMP_OBJECT (private->channels),
+ gui_size);
+ memsize += gimp_object_get_memsize (GIMP_OBJECT (private->vectors),
+ gui_size);
+
+ memsize += gimp_g_slist_get_memsize (private->layer_stack, 0);
+
+ memsize += gimp_object_get_memsize (GIMP_OBJECT (private->selection_mask),
+ gui_size);
+
+ memsize += gimp_object_get_memsize (GIMP_OBJECT (private->parasites),
+ gui_size);
+
+ memsize += gimp_object_get_memsize (GIMP_OBJECT (private->undo_stack),
+ gui_size);
+ memsize += gimp_object_get_memsize (GIMP_OBJECT (private->redo_stack),
+ gui_size);
+
+ return memsize + GIMP_OBJECT_CLASS (parent_class)->get_memsize (object,
+ gui_size);
+}
+
+static gboolean
+gimp_image_get_size (GimpViewable *viewable,
+ gint *width,
+ gint *height)
+{
+ GimpImage *image = GIMP_IMAGE (viewable);
+
+ *width = gimp_image_get_width (image);
+ *height = gimp_image_get_height (image);
+
+ return TRUE;
+}
+
+static void
+gimp_image_size_changed (GimpViewable *viewable)
+{
+ GimpImage *image = GIMP_IMAGE (viewable);
+ GList *all_items;
+ GList *list;
+
+ if (GIMP_VIEWABLE_CLASS (parent_class)->size_changed)
+ GIMP_VIEWABLE_CLASS (parent_class)->size_changed (viewable);
+
+ all_items = gimp_image_get_layer_list (image);
+ for (list = all_items; list; list = g_list_next (list))
+ {
+ GimpLayerMask *mask = gimp_layer_get_mask (GIMP_LAYER (list->data));
+
+ gimp_viewable_size_changed (GIMP_VIEWABLE (list->data));
+
+ if (mask)
+ gimp_viewable_size_changed (GIMP_VIEWABLE (mask));
+ }
+ g_list_free (all_items);
+
+ all_items = gimp_image_get_channel_list (image);
+ g_list_free_full (all_items, (GDestroyNotify) gimp_viewable_size_changed);
+
+ all_items = gimp_image_get_vectors_list (image);
+ g_list_free_full (all_items, (GDestroyNotify) gimp_viewable_size_changed);
+
+ gimp_viewable_size_changed (GIMP_VIEWABLE (gimp_image_get_mask (image)));
+
+ gimp_image_metadata_update_pixel_size (image);
+
+ g_clear_object (&GIMP_IMAGE_GET_PRIVATE (image)->pickable_buffer);
+
+ gimp_image_update_bounding_box (image);
+}
+
+static gchar *
+gimp_image_get_description (GimpViewable *viewable,
+ gchar **tooltip)
+{
+ GimpImage *image = GIMP_IMAGE (viewable);
+
+ if (tooltip)
+ *tooltip = g_strdup (gimp_image_get_display_path (image));
+
+ return g_strdup_printf ("%s-%d",
+ gimp_image_get_display_name (image),
+ gimp_image_get_ID (image));
+}
+
+static void
+gimp_image_real_mode_changed (GimpImage *image)
+{
+ gimp_projectable_structure_changed (GIMP_PROJECTABLE (image));
+}
+
+static void
+gimp_image_real_precision_changed (GimpImage *image)
+{
+ gimp_image_metadata_update_bits_per_sample (image);
+
+ gimp_projectable_structure_changed (GIMP_PROJECTABLE (image));
+}
+
+static void
+gimp_image_real_resolution_changed (GimpImage *image)
+{
+ gimp_image_metadata_update_resolution (image);
+}
+
+static void
+gimp_image_real_size_changed_detailed (GimpImage *image,
+ gint previous_origin_x,
+ gint previous_origin_y,
+ gint previous_width,
+ gint previous_height)
+{
+ /* Whenever GimpImage::size-changed-detailed is emitted, so is
+ * GimpViewable::size-changed. Clients choose what signal to listen
+ * to depending on how much info they need.
+ */
+ gimp_viewable_size_changed (GIMP_VIEWABLE (image));
+}
+
+static void
+gimp_image_real_unit_changed (GimpImage *image)
+{
+ gimp_image_metadata_update_resolution (image);
+}
+
+static void
+gimp_image_real_colormap_changed (GimpImage *image,
+ gint color_index)
+{
+ GimpImagePrivate *private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ if (private->colormap && private->n_colors > 0)
+ {
+ babl_palette_set_palette (private->babl_palette_rgb,
+ gimp_babl_format (GIMP_RGB,
+ private->precision, FALSE),
+ private->colormap,
+ private->n_colors);
+ babl_palette_set_palette (private->babl_palette_rgba,
+ gimp_babl_format (GIMP_RGB,
+ private->precision, FALSE),
+ private->colormap,
+ private->n_colors);
+ }
+
+ if (gimp_image_get_base_type (image) == GIMP_INDEXED)
+ {
+ /* A colormap alteration affects the whole image */
+ gimp_image_invalidate_all (image);
+
+ gimp_item_stack_invalidate_previews (GIMP_ITEM_STACK (private->layers->container));
+ }
+}
+
+static const guint8 *
+gimp_image_color_managed_get_icc_profile (GimpColorManaged *managed,
+ gsize *len)
+{
+ return gimp_image_get_icc_profile (GIMP_IMAGE (managed), len);
+}
+
+static GimpColorProfile *
+gimp_image_color_managed_get_color_profile (GimpColorManaged *managed)
+{
+ GimpImage *image = GIMP_IMAGE (managed);
+ GimpColorProfile *profile = NULL;
+
+ if (gimp_image_get_is_color_managed (image))
+ profile = gimp_image_get_color_profile (image);
+
+ if (! profile)
+ profile = gimp_image_get_builtin_color_profile (image);
+
+ return profile;
+}
+
+static void
+gimp_image_color_managed_profile_changed (GimpColorManaged *managed)
+{
+ GimpImage *image = GIMP_IMAGE (managed);
+ GimpItemStack *layers = GIMP_ITEM_STACK (gimp_image_get_layers (image));
+
+ gimp_image_metadata_update_colorspace (image);
+
+ gimp_projectable_structure_changed (GIMP_PROJECTABLE (image));
+ gimp_viewable_invalidate_preview (GIMP_VIEWABLE (image));
+ gimp_item_stack_profile_changed (layers);
+}
+
+static void
+gimp_image_projectable_flush (GimpProjectable *projectable,
+ gboolean invalidate_preview)
+{
+ GimpImage *image = GIMP_IMAGE (projectable);
+ GimpImagePrivate *private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ if (private->flush_accum.alpha_changed)
+ {
+ gimp_image_alpha_changed (image);
+ private->flush_accum.alpha_changed = FALSE;
+ }
+
+ if (private->flush_accum.mask_changed)
+ {
+ gimp_image_mask_changed (image);
+ private->flush_accum.mask_changed = FALSE;
+ }
+
+ if (private->flush_accum.floating_selection_changed)
+ {
+ gimp_image_floating_selection_changed (image);
+ private->flush_accum.floating_selection_changed = FALSE;
+ }
+
+ if (private->flush_accum.preview_invalidated)
+ {
+ /* don't invalidate the preview here, the projection does this when
+ * it is completely constructed.
+ */
+ private->flush_accum.preview_invalidated = FALSE;
+ }
+}
+
+static GimpImage *
+gimp_image_get_image (GimpProjectable *projectable)
+{
+ return GIMP_IMAGE (projectable);
+}
+
+static const Babl *
+gimp_image_get_proj_format (GimpProjectable *projectable)
+{
+ GimpImage *image = GIMP_IMAGE (projectable);
+ GimpImagePrivate *private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ switch (private->base_type)
+ {
+ case GIMP_RGB:
+ case GIMP_INDEXED:
+ return gimp_image_get_format (image, GIMP_RGB,
+ gimp_image_get_precision (image), TRUE);
+
+ case GIMP_GRAY:
+ return gimp_image_get_format (image, GIMP_GRAY,
+ gimp_image_get_precision (image), TRUE);
+ }
+
+ g_return_val_if_reached (NULL);
+}
+
+static void
+gimp_image_pickable_flush (GimpPickable *pickable)
+{
+ GimpImagePrivate *private = GIMP_IMAGE_GET_PRIVATE (pickable);
+
+ return gimp_pickable_flush (GIMP_PICKABLE (private->projection));
+}
+
+static GeglBuffer *
+gimp_image_get_buffer (GimpPickable *pickable)
+{
+ GimpImage *image = GIMP_IMAGE (pickable);
+ GimpImagePrivate *private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ if (! private->pickable_buffer)
+ {
+ GeglBuffer *buffer;
+
+ buffer = gimp_pickable_get_buffer (GIMP_PICKABLE (private->projection));
+
+ if (! private->show_all)
+ {
+ private->pickable_buffer = g_object_ref (buffer);
+ }
+ else
+ {
+ private->pickable_buffer = gegl_buffer_create_sub_buffer (
+ buffer,
+ GEGL_RECTANGLE (0, 0,
+ gimp_image_get_width (image),
+ gimp_image_get_height (image)));
+ }
+ }
+
+ return private->pickable_buffer;
+}
+
+static gboolean
+gimp_image_get_pixel_at (GimpPickable *pickable,
+ gint x,
+ gint y,
+ const Babl *format,
+ gpointer pixel)
+{
+ GimpImage *image = GIMP_IMAGE (pickable);
+ GimpImagePrivate *private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ if (x >= 0 &&
+ y >= 0 &&
+ x < gimp_image_get_width (image) &&
+ y < gimp_image_get_height (image))
+ {
+ return gimp_pickable_get_pixel_at (GIMP_PICKABLE (private->projection),
+ x, y, format, pixel);
+ }
+
+ return FALSE;
+}
+
+static gdouble
+gimp_image_get_opacity_at (GimpPickable *pickable,
+ gint x,
+ gint y)
+{
+ GimpImage *image = GIMP_IMAGE (pickable);
+ GimpImagePrivate *private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ if (x >= 0 &&
+ y >= 0 &&
+ x < gimp_image_get_width (image) &&
+ y < gimp_image_get_height (image))
+ {
+ return gimp_pickable_get_opacity_at (GIMP_PICKABLE (private->projection),
+ x, y);
+ }
+
+ return FALSE;
+}
+
+static void
+gimp_image_get_pixel_average (GimpPickable *pickable,
+ const GeglRectangle *rect,
+ const Babl *format,
+ gpointer pixel)
+{
+ GeglBuffer *buffer = gimp_pickable_get_buffer (pickable);
+
+ return gimp_gegl_average_color (buffer, rect, TRUE, GEGL_ABYSS_NONE, format,
+ pixel);
+}
+
+static void
+gimp_image_pixel_to_srgb (GimpPickable *pickable,
+ const Babl *format,
+ gpointer pixel,
+ GimpRGB *color)
+{
+ gimp_image_color_profile_pixel_to_srgb (GIMP_IMAGE (pickable),
+ format, pixel, color);
+}
+
+static void
+gimp_image_srgb_to_pixel (GimpPickable *pickable,
+ const GimpRGB *color,
+ const Babl *format,
+ gpointer pixel)
+{
+ gimp_image_color_profile_srgb_to_pixel (GIMP_IMAGE (pickable),
+ color, format, pixel);
+}
+
+static GeglRectangle
+gimp_image_get_bounding_box (GimpProjectable *projectable)
+{
+ GimpImage *image = GIMP_IMAGE (projectable);
+
+ return GIMP_IMAGE_GET_PRIVATE (image)->bounding_box;
+}
+
+static GeglNode *
+gimp_image_get_graph (GimpProjectable *projectable)
+{
+ GimpImage *image = GIMP_IMAGE (projectable);
+ GimpImagePrivate *private = GIMP_IMAGE_GET_PRIVATE (image);
+ GeglNode *layers_node;
+ GeglNode *channels_node;
+ GeglNode *output;
+ GimpComponentMask mask;
+
+ if (private->graph)
+ return private->graph;
+
+ private->graph = gegl_node_new ();
+
+ layers_node =
+ gimp_filter_stack_get_graph (GIMP_FILTER_STACK (private->layers->container));
+
+ gegl_node_add_child (private->graph, layers_node);
+
+ mask = ~gimp_image_get_visible_mask (image) & GIMP_COMPONENT_MASK_ALL;
+
+ private->visible_mask =
+ gegl_node_new_child (private->graph,
+ "operation", "gimp:mask-components",
+ "mask", mask,
+ "alpha", 1.0,
+ NULL);
+
+ gegl_node_connect_to (layers_node, "output",
+ private->visible_mask, "input");
+
+ channels_node =
+ gimp_filter_stack_get_graph (GIMP_FILTER_STACK (private->channels->container));
+
+ gegl_node_add_child (private->graph, channels_node);
+
+ gegl_node_connect_to (private->visible_mask, "output",
+ channels_node, "input");
+
+ output = gegl_node_get_output_proxy (private->graph, "output");
+
+ gegl_node_connect_to (channels_node, "output",
+ output, "input");
+
+ return private->graph;
+}
+
+static void
+gimp_image_projection_buffer_notify (GimpProjection *projection,
+ const GParamSpec *pspec,
+ GimpImage *image)
+{
+ g_clear_object (&GIMP_IMAGE_GET_PRIVATE (image)->pickable_buffer);
+}
+
+static void
+gimp_image_mask_update (GimpDrawable *drawable,
+ gint x,
+ gint y,
+ gint width,
+ gint height,
+ GimpImage *image)
+{
+ GIMP_IMAGE_GET_PRIVATE (image)->flush_accum.mask_changed = TRUE;
+}
+
+static void
+gimp_image_layers_changed (GimpContainer *container,
+ GimpChannel *channel,
+ GimpImage *image)
+{
+ gimp_image_update_bounding_box (image);
+}
+
+static void
+gimp_image_layer_offset_changed (GimpDrawable *drawable,
+ const GParamSpec *pspec,
+ GimpImage *image)
+{
+ gimp_image_update_bounding_box (image);
+}
+
+static void
+gimp_image_layer_bounding_box_changed (GimpDrawable *drawable,
+ GimpImage *image)
+{
+ gimp_image_update_bounding_box (image);
+}
+
+static void
+gimp_image_layer_alpha_changed (GimpDrawable *drawable,
+ GimpImage *image)
+{
+ GimpImagePrivate *private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ if (gimp_container_get_n_children (private->layers->container) == 1)
+ private->flush_accum.alpha_changed = TRUE;
+}
+
+static void
+gimp_image_channel_add (GimpContainer *container,
+ GimpChannel *channel,
+ GimpImage *image)
+{
+ if (! strcmp (GIMP_IMAGE_QUICK_MASK_NAME,
+ gimp_object_get_name (channel)))
+ {
+ gimp_image_set_quick_mask_state (image, TRUE);
+ }
+}
+
+static void
+gimp_image_channel_remove (GimpContainer *container,
+ GimpChannel *channel,
+ GimpImage *image)
+{
+ if (! strcmp (GIMP_IMAGE_QUICK_MASK_NAME,
+ gimp_object_get_name (channel)))
+ {
+ gimp_image_set_quick_mask_state (image, FALSE);
+ }
+}
+
+static void
+gimp_image_channel_name_changed (GimpChannel *channel,
+ GimpImage *image)
+{
+ if (! strcmp (GIMP_IMAGE_QUICK_MASK_NAME,
+ gimp_object_get_name (channel)))
+ {
+ gimp_image_set_quick_mask_state (image, TRUE);
+ }
+ else if (gimp_image_get_quick_mask_state (image) &&
+ ! gimp_image_get_quick_mask (image))
+ {
+ gimp_image_set_quick_mask_state (image, FALSE);
+ }
+}
+
+static void
+gimp_image_channel_color_changed (GimpChannel *channel,
+ GimpImage *image)
+{
+ if (! strcmp (GIMP_IMAGE_QUICK_MASK_NAME,
+ gimp_object_get_name (channel)))
+ {
+ GIMP_IMAGE_GET_PRIVATE (image)->quick_mask_color = channel->color;
+ }
+}
+
+static void
+gimp_image_active_layer_notify (GimpItemTree *tree,
+ const GParamSpec *pspec,
+ GimpImage *image)
+{
+ GimpImagePrivate *private = GIMP_IMAGE_GET_PRIVATE (image);
+ GimpLayer *layer = gimp_image_get_active_layer (image);
+
+ if (layer)
+ {
+ /* Configure the layer stack to reflect this change */
+ private->layer_stack = g_slist_remove (private->layer_stack, layer);
+ private->layer_stack = g_slist_prepend (private->layer_stack, layer);
+ }
+
+ g_signal_emit (image, gimp_image_signals[ACTIVE_LAYER_CHANGED], 0);
+
+ if (layer && gimp_image_get_active_channel (image))
+ gimp_image_set_active_channel (image, NULL);
+}
+
+static void
+gimp_image_active_channel_notify (GimpItemTree *tree,
+ const GParamSpec *pspec,
+ GimpImage *image)
+{
+ GimpChannel *channel = gimp_image_get_active_channel (image);
+
+ g_signal_emit (image, gimp_image_signals[ACTIVE_CHANNEL_CHANGED], 0);
+
+ if (channel && gimp_image_get_active_layer (image))
+ gimp_image_set_active_layer (image, NULL);
+}
+
+static void
+gimp_image_active_vectors_notify (GimpItemTree *tree,
+ const GParamSpec *pspec,
+ GimpImage *image)
+{
+ g_signal_emit (image, gimp_image_signals[ACTIVE_VECTORS_CHANGED], 0);
+}
+
+static void
+gimp_image_freeze_bounding_box (GimpImage *image)
+{
+ GimpImagePrivate *private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ private->bounding_box_freeze_count++;
+}
+
+static void
+gimp_image_thaw_bounding_box (GimpImage *image)
+{
+ GimpImagePrivate *private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ private->bounding_box_freeze_count--;
+
+ if (private->bounding_box_freeze_count == 0 &&
+ private->bounding_box_update_pending)
+ {
+ private->bounding_box_update_pending = FALSE;
+
+ gimp_image_update_bounding_box (image);
+ }
+}
+
+static void
+gimp_image_update_bounding_box (GimpImage *image)
+{
+ GimpImagePrivate *private = GIMP_IMAGE_GET_PRIVATE (image);
+ GeglRectangle bounding_box;
+
+ if (private->bounding_box_freeze_count > 0)
+ {
+ private->bounding_box_update_pending = TRUE;
+
+ return;
+ }
+
+ bounding_box.x = 0;
+ bounding_box.y = 0;
+ bounding_box.width = gimp_image_get_width (image);
+ bounding_box.height = gimp_image_get_height (image);
+
+ if (private->show_all)
+ {
+ GList *iter;
+
+ for (iter = gimp_image_get_layer_iter (image);
+ iter;
+ iter = g_list_next (iter))
+ {
+ GimpLayer *layer = iter->data;
+ GeglRectangle layer_bounding_box;
+ gint offset_x;
+ gint offset_y;
+
+ gimp_item_get_offset (GIMP_ITEM (layer), &offset_x, &offset_y);
+
+ layer_bounding_box = gimp_drawable_get_bounding_box (
+ GIMP_DRAWABLE (layer));
+
+ layer_bounding_box.x += offset_x;
+ layer_bounding_box.y += offset_y;
+
+ gegl_rectangle_bounding_box (&bounding_box,
+ &bounding_box, &layer_bounding_box);
+ }
+ }
+
+ if (! gegl_rectangle_equal (&bounding_box, &private->bounding_box))
+ {
+ private->bounding_box = bounding_box;
+
+ gimp_projectable_bounds_changed (GIMP_PROJECTABLE (image), 0, 0);
+ }
+}
+
+
+/* public functions */
+
+GimpImage *
+gimp_image_new (Gimp *gimp,
+ gint width,
+ gint height,
+ GimpImageBaseType base_type,
+ GimpPrecision precision)
+{
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
+ g_return_val_if_fail (gimp_babl_is_valid (base_type, precision), NULL);
+
+ return g_object_new (GIMP_TYPE_IMAGE,
+ "gimp", gimp,
+ "width", width,
+ "height", height,
+ "base-type", base_type,
+ "precision", precision,
+ NULL);
+}
+
+gint64
+gimp_image_estimate_memsize (GimpImage *image,
+ GimpComponentType component_type,
+ gint width,
+ gint height)
+{
+ GList *drawables;
+ GList *list;
+ gint current_width;
+ gint current_height;
+ gint64 current_size;
+ gint64 scalable_size = 0;
+ gint64 scaled_size = 0;
+ gint64 new_size;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), 0);
+
+ current_width = gimp_image_get_width (image);
+ current_height = gimp_image_get_height (image);
+ current_size = gimp_object_get_memsize (GIMP_OBJECT (image), NULL);
+
+ /* the part of the image's memsize that scales linearly with the image */
+ drawables = gimp_image_item_list_get_list (image,
+ GIMP_ITEM_TYPE_LAYERS |
+ GIMP_ITEM_TYPE_CHANNELS,
+ GIMP_ITEM_SET_ALL);
+
+ gimp_image_item_list_filter (drawables);
+
+ drawables = g_list_prepend (drawables, gimp_image_get_mask (image));
+
+ for (list = drawables; list; list = g_list_next (list))
+ {
+ GimpDrawable *drawable = list->data;
+ gdouble drawable_width;
+ gdouble drawable_height;
+
+ drawable_width = gimp_item_get_width (GIMP_ITEM (drawable));
+ drawable_height = gimp_item_get_height (GIMP_ITEM (drawable));
+
+ scalable_size += gimp_drawable_estimate_memsize (drawable,
+ gimp_drawable_get_component_type (drawable),
+ drawable_width,
+ drawable_height);
+
+ scaled_size += gimp_drawable_estimate_memsize (drawable,
+ component_type,
+ drawable_width * width /
+ current_width,
+ drawable_height * height /
+ current_height);
+ }
+
+ g_list_free (drawables);
+
+ scalable_size +=
+ gimp_projection_estimate_memsize (gimp_image_get_base_type (image),
+ gimp_image_get_component_type (image),
+ gimp_image_get_width (image),
+ gimp_image_get_height (image));
+
+ scaled_size +=
+ gimp_projection_estimate_memsize (gimp_image_get_base_type (image),
+ component_type,
+ width, height);
+
+ GIMP_LOG (IMAGE_SCALE,
+ "scalable_size = %"G_GINT64_FORMAT" scaled_size = %"G_GINT64_FORMAT,
+ scalable_size, scaled_size);
+
+ new_size = current_size - scalable_size + scaled_size;
+
+ return new_size;
+}
+
+GimpImageBaseType
+gimp_image_get_base_type (GimpImage *image)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), -1);
+
+ return GIMP_IMAGE_GET_PRIVATE (image)->base_type;
+}
+
+GimpComponentType
+gimp_image_get_component_type (GimpImage *image)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), -1);
+
+ return gimp_babl_component_type (GIMP_IMAGE_GET_PRIVATE (image)->precision);
+}
+
+GimpPrecision
+gimp_image_get_precision (GimpImage *image)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), -1);
+
+ return GIMP_IMAGE_GET_PRIVATE (image)->precision;
+}
+
+const Babl *
+gimp_image_get_format (GimpImage *image,
+ GimpImageBaseType base_type,
+ GimpPrecision precision,
+ gboolean with_alpha)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+
+ switch (base_type)
+ {
+ case GIMP_RGB:
+ case GIMP_GRAY:
+ return gimp_babl_format (base_type, precision, with_alpha);
+
+ case GIMP_INDEXED:
+ if (precision == GIMP_PRECISION_U8_GAMMA)
+ {
+ if (with_alpha)
+ return gimp_image_colormap_get_rgba_format (image);
+ else
+ return gimp_image_colormap_get_rgb_format (image);
+ }
+ }
+
+ g_return_val_if_reached (NULL);
+}
+
+const Babl *
+gimp_image_get_layer_format (GimpImage *image,
+ gboolean with_alpha)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+
+ return gimp_image_get_format (image,
+ gimp_image_get_base_type (image),
+ gimp_image_get_precision (image),
+ with_alpha);
+}
+
+const Babl *
+gimp_image_get_channel_format (GimpImage *image)
+{
+ GimpPrecision precision;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+
+ precision = gimp_image_get_precision (image);
+
+ if (precision == GIMP_PRECISION_U8_GAMMA)
+ return gimp_image_get_format (image, GIMP_GRAY,
+ gimp_image_get_precision (image),
+ FALSE);
+
+ return gimp_babl_mask_format (precision);
+}
+
+const Babl *
+gimp_image_get_mask_format (GimpImage *image)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+
+ return gimp_babl_mask_format (gimp_image_get_precision (image));
+}
+
+GimpLayerMode
+gimp_image_get_default_new_layer_mode (GimpImage *image)
+{
+ GimpImagePrivate *private;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), GIMP_LAYER_MODE_NORMAL);
+
+ private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ if (private->new_layer_mode == -1)
+ {
+ GList *layers = gimp_image_get_layer_list (image);
+
+ if (layers)
+ {
+ GList *list;
+
+ for (list = layers; list; list = g_list_next (list))
+ {
+ GimpLayer *layer = list->data;
+ GimpLayerMode mode = gimp_layer_get_mode (layer);
+
+ if (! gimp_layer_mode_is_legacy (mode))
+ {
+ /* any non-legacy layer switches the mode to non-legacy
+ */
+ private->new_layer_mode = GIMP_LAYER_MODE_NORMAL;
+ break;
+ }
+ }
+
+ /* only if all layers are legacy, the mode is also legacy
+ */
+ if (! list)
+ private->new_layer_mode = GIMP_LAYER_MODE_NORMAL_LEGACY;
+
+ g_list_free (layers);
+ }
+ else
+ {
+ /* empty images are never considered legacy
+ */
+ private->new_layer_mode = GIMP_LAYER_MODE_NORMAL;
+ }
+ }
+
+ return private->new_layer_mode;
+}
+
+void
+gimp_image_unset_default_new_layer_mode (GimpImage *image)
+{
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+
+ GIMP_IMAGE_GET_PRIVATE (image)->new_layer_mode = -1;
+}
+
+gint
+gimp_image_get_ID (GimpImage *image)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), -1);
+
+ return GIMP_IMAGE_GET_PRIVATE (image)->ID;
+}
+
+GimpImage *
+gimp_image_get_by_ID (Gimp *gimp,
+ gint image_id)
+{
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
+
+ if (gimp->image_table == NULL)
+ return NULL;
+
+ return (GimpImage *) gimp_id_table_lookup (gimp->image_table, image_id);
+}
+
+void
+gimp_image_set_file (GimpImage *image,
+ GFile *file)
+{
+ GimpImagePrivate *private;
+
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+ g_return_if_fail (file == NULL || G_IS_FILE (file));
+
+ private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ if (private->file != file)
+ {
+ gimp_object_take_name (GIMP_OBJECT (image),
+ file ? g_file_get_uri (file) : NULL);
+ }
+}
+
+/**
+ * gimp_image_get_untitled_file:
+ *
+ * Returns: A #GFile saying "Untitled" for newly created images.
+ **/
+GFile *
+gimp_image_get_untitled_file (GimpImage *image)
+{
+ GimpImagePrivate *private;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+
+ private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ if (! private->untitled_file)
+ private->untitled_file = g_file_new_for_uri (_("Untitled"));
+
+ return private->untitled_file;
+}
+
+/**
+ * gimp_image_get_file_or_untitled:
+ * @image: A #GimpImage.
+ *
+ * Get the file of the XCF image, or the "Untitled" file if there is no file.
+ *
+ * Returns: A #GFile.
+ **/
+GFile *
+gimp_image_get_file_or_untitled (GimpImage *image)
+{
+ GFile *file;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+
+ file = gimp_image_get_file (image);
+
+ if (! file)
+ file = gimp_image_get_untitled_file (image);
+
+ return file;
+}
+
+/**
+ * gimp_image_get_file:
+ * @image: A #GimpImage.
+ *
+ * Get the file of the XCF image, or NULL if there is no file.
+ *
+ * Returns: The file, or NULL.
+ **/
+GFile *
+gimp_image_get_file (GimpImage *image)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+
+ return GIMP_IMAGE_GET_PRIVATE (image)->file;
+}
+
+/**
+ * gimp_image_get_imported_file:
+ * @image: A #GimpImage.
+ *
+ * Returns: The file of the imported image, or NULL if the image has
+ * been saved as XCF after it was imported.
+ **/
+GFile *
+gimp_image_get_imported_file (GimpImage *image)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+
+ return GIMP_IMAGE_GET_PRIVATE (image)->imported_file;
+}
+
+/**
+ * gimp_image_get_exported_file:
+ * @image: A #GimpImage.
+ *
+ * Returns: The file of the image last exported from this XCF file, or
+ * NULL if the image has never been exported.
+ **/
+GFile *
+gimp_image_get_exported_file (GimpImage *image)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+
+ return GIMP_IMAGE_GET_PRIVATE (image)->exported_file;
+}
+
+/**
+ * gimp_image_get_save_a_copy_file:
+ * @image: A #GimpImage.
+ *
+ * Returns: The URI of the last copy that was saved of this XCF file.
+ **/
+GFile *
+gimp_image_get_save_a_copy_file (GimpImage *image)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+
+ return GIMP_IMAGE_GET_PRIVATE (image)->save_a_copy_file;
+}
+
+/**
+ * gimp_image_get_any_file:
+ * @image: A #GimpImage.
+ *
+ * Returns: The XCF file, the imported file, or the exported file, in
+ * that order of precedence.
+ **/
+GFile *
+gimp_image_get_any_file (GimpImage *image)
+{
+ GFile *file;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+
+ file = gimp_image_get_file (image);
+ if (! file)
+ {
+ file = gimp_image_get_imported_file (image);
+ if (! file)
+ {
+ file = gimp_image_get_exported_file (image);
+ }
+ }
+
+ return file;
+}
+
+/**
+ * gimp_image_set_imported_uri:
+ * @image: A #GimpImage.
+ * @file:
+ *
+ * Sets the URI this file was imported from.
+ **/
+void
+gimp_image_set_imported_file (GimpImage *image,
+ GFile *file)
+{
+ GimpImagePrivate *private;
+
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+ g_return_if_fail (file == NULL || G_IS_FILE (file));
+
+ private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ if (g_set_object (&private->imported_file, file))
+ {
+ gimp_object_name_changed (GIMP_OBJECT (image));
+ }
+
+ if (! private->resolution_set && file != NULL)
+ {
+ /* Unlike new files (which follow technological progress and will
+ * use higher default resolution, or explicitly chosen templates),
+ * imported files have a more backward-compatible value.
+ *
+ * 72 PPI is traditionnally the default value when none other had
+ * been explicitly set (for instance it is the default when no
+ * resolution metadata was set in Exif version 2.32, and below,
+ * standard). This historical value will only ever apply to loaded
+ * images. New images will continue having more modern or
+ * templated defaults.
+ */
+ private->xresolution = 72.0;
+ private->yresolution = 72.0;
+ private->resolution_unit = GIMP_UNIT_INCH;
+ }
+}
+
+/**
+ * gimp_image_set_exported_file:
+ * @image: A #GimpImage.
+ * @file:
+ *
+ * Sets the file this image was last exported to. Note that saving as
+ * XCF is not "exporting".
+ **/
+void
+gimp_image_set_exported_file (GimpImage *image,
+ GFile *file)
+{
+ GimpImagePrivate *private;
+
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+ g_return_if_fail (file == NULL || G_IS_FILE (file));
+
+ private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ if (g_set_object (&private->exported_file, file))
+ {
+ gimp_object_name_changed (GIMP_OBJECT (image));
+ }
+}
+
+/**
+ * gimp_image_set_save_a_copy_file:
+ * @image: A #GimpImage.
+ * @uri:
+ *
+ * Set the URI to the last copy this XCF file was saved to through the
+ * "save a copy" action.
+ **/
+void
+gimp_image_set_save_a_copy_file (GimpImage *image,
+ GFile *file)
+{
+ GimpImagePrivate *private;
+
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+ g_return_if_fail (file == NULL || G_IS_FILE (file));
+
+ private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ g_set_object (&private->save_a_copy_file, file);
+}
+
+static gchar *
+gimp_image_format_display_uri (GimpImage *image,
+ gboolean basename)
+{
+ const gchar *uri_format = NULL;
+ const gchar *export_status = NULL;
+ GFile *file = NULL;
+ GFile *source = NULL;
+ GFile *dest = NULL;
+ GFile *display_file = NULL;
+ gboolean is_imported;
+ gboolean is_exported;
+ gchar *display_uri = NULL;
+ gchar *format_string;
+ gchar *tmp;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+
+ file = gimp_image_get_file (image);
+ source = gimp_image_get_imported_file (image);
+ dest = gimp_image_get_exported_file (image);
+
+ is_imported = (source != NULL);
+ is_exported = (dest != NULL);
+
+ if (file)
+ {
+ display_file = g_object_ref (file);
+ uri_format = "%s";
+ }
+ else
+ {
+ if (is_imported)
+ display_file = source;
+
+ /* Calculate filename suffix */
+ if (! gimp_image_is_export_dirty (image))
+ {
+ if (is_exported)
+ {
+ display_file = dest;
+ export_status = _(" (exported)");
+ }
+ else if (is_imported)
+ {
+ export_status = _(" (overwritten)");
+ }
+ else
+ {
+ g_warning ("Unexpected code path, Save+export implementation is buggy!");
+ }
+ }
+ else if (is_imported)
+ {
+ export_status = _(" (imported)");
+ }
+
+ if (display_file)
+ display_file = gimp_file_with_new_extension (display_file, NULL);
+
+ uri_format = "[%s]";
+ }
+
+ if (! display_file)
+ display_file = g_object_ref (gimp_image_get_untitled_file (image));
+
+ if (basename)
+ display_uri = g_path_get_basename (gimp_file_get_utf8_name (display_file));
+ else
+ display_uri = g_strdup (gimp_file_get_utf8_name (display_file));
+
+ g_object_unref (display_file);
+
+ format_string = g_strconcat (uri_format, export_status, NULL);
+
+ tmp = g_strdup_printf (format_string, display_uri);
+ g_free (display_uri);
+ display_uri = tmp;
+
+ g_free (format_string);
+
+ return display_uri;
+}
+
+const gchar *
+gimp_image_get_display_name (GimpImage *image)
+{
+ GimpImagePrivate *private;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+
+ private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ if (! private->display_name)
+ private->display_name = gimp_image_format_display_uri (image, TRUE);
+
+ return private->display_name;
+}
+
+const gchar *
+gimp_image_get_display_path (GimpImage *image)
+{
+ GimpImagePrivate *private;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+
+ private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ if (! private->display_path)
+ private->display_path = gimp_image_format_display_uri (image, FALSE);
+
+ return private->display_path;
+}
+
+void
+gimp_image_set_load_proc (GimpImage *image,
+ GimpPlugInProcedure *proc)
+{
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+
+ GIMP_IMAGE_GET_PRIVATE (image)->load_proc = proc;
+}
+
+GimpPlugInProcedure *
+gimp_image_get_load_proc (GimpImage *image)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+
+ return GIMP_IMAGE_GET_PRIVATE (image)->load_proc;
+}
+
+void
+gimp_image_set_save_proc (GimpImage *image,
+ GimpPlugInProcedure *proc)
+{
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+
+ GIMP_IMAGE_GET_PRIVATE (image)->save_proc = proc;
+}
+
+GimpPlugInProcedure *
+gimp_image_get_save_proc (GimpImage *image)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+
+ return GIMP_IMAGE_GET_PRIVATE (image)->save_proc;
+}
+
+void
+gimp_image_set_export_proc (GimpImage *image,
+ GimpPlugInProcedure *proc)
+{
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+
+ GIMP_IMAGE_GET_PRIVATE (image)->export_proc = proc;
+}
+
+GimpPlugInProcedure *
+gimp_image_get_export_proc (GimpImage *image)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+
+ return GIMP_IMAGE_GET_PRIVATE (image)->export_proc;
+}
+
+gint
+gimp_image_get_xcf_version (GimpImage *image,
+ gboolean zlib_compression,
+ gint *gimp_version,
+ const gchar **version_string,
+ gchar **version_reason)
+{
+ GList *layers;
+ GList *list;
+ GList *reasons = NULL;
+ gint version = 0; /* default to oldest */
+ const gchar *enum_desc;
+
+#define ADD_REASON(_reason) \
+ if (version_reason) { \
+ gchar *tmp = _reason; \
+ if (g_list_find_custom (reasons, tmp, (GCompareFunc) strcmp)) \
+ g_free (tmp); \
+ else \
+ reasons = g_list_prepend (reasons, tmp); }
+
+ /* need version 1 for colormaps */
+ if (gimp_image_get_colormap (image))
+ version = 1;
+
+ layers = gimp_image_get_layer_list (image);
+
+ for (list = layers; list; list = g_list_next (list))
+ {
+ GimpLayer *layer = GIMP_LAYER (list->data);
+
+ switch (gimp_layer_get_mode (layer))
+ {
+ /* Modes that exist since ancient times */
+ case GIMP_LAYER_MODE_NORMAL_LEGACY:
+ case GIMP_LAYER_MODE_DISSOLVE:
+ case GIMP_LAYER_MODE_BEHIND_LEGACY:
+ case GIMP_LAYER_MODE_MULTIPLY_LEGACY:
+ case GIMP_LAYER_MODE_SCREEN_LEGACY:
+ case GIMP_LAYER_MODE_OVERLAY_LEGACY:
+ case GIMP_LAYER_MODE_DIFFERENCE_LEGACY:
+ case GIMP_LAYER_MODE_ADDITION_LEGACY:
+ case GIMP_LAYER_MODE_SUBTRACT_LEGACY:
+ case GIMP_LAYER_MODE_DARKEN_ONLY_LEGACY:
+ case GIMP_LAYER_MODE_LIGHTEN_ONLY_LEGACY:
+ case GIMP_LAYER_MODE_HSV_HUE_LEGACY:
+ case GIMP_LAYER_MODE_HSV_SATURATION_LEGACY:
+ case GIMP_LAYER_MODE_HSL_COLOR_LEGACY:
+ case GIMP_LAYER_MODE_HSV_VALUE_LEGACY:
+ case GIMP_LAYER_MODE_DIVIDE_LEGACY:
+ case GIMP_LAYER_MODE_DODGE_LEGACY:
+ case GIMP_LAYER_MODE_BURN_LEGACY:
+ case GIMP_LAYER_MODE_HARDLIGHT_LEGACY:
+ break;
+
+ /* Since 2.6 */
+ case GIMP_LAYER_MODE_SOFTLIGHT_LEGACY:
+ case GIMP_LAYER_MODE_GRAIN_EXTRACT_LEGACY:
+ case GIMP_LAYER_MODE_GRAIN_MERGE_LEGACY:
+ case GIMP_LAYER_MODE_COLOR_ERASE_LEGACY:
+ gimp_enum_get_value (GIMP_TYPE_LAYER_MODE,
+ gimp_layer_get_mode (layer),
+ NULL, NULL, &enum_desc, NULL);
+ ADD_REASON (g_strdup_printf (_("Layer mode '%s' was added in %s"),
+ enum_desc, "GIMP 2.6"));
+ version = MAX (2, version);
+ break;
+
+ /* Since 2.10 */
+ case GIMP_LAYER_MODE_OVERLAY:
+ case GIMP_LAYER_MODE_LCH_HUE:
+ case GIMP_LAYER_MODE_LCH_CHROMA:
+ case GIMP_LAYER_MODE_LCH_COLOR:
+ case GIMP_LAYER_MODE_LCH_LIGHTNESS:
+ gimp_enum_get_value (GIMP_TYPE_LAYER_MODE,
+ gimp_layer_get_mode (layer),
+ NULL, NULL, &enum_desc, NULL);
+ ADD_REASON (g_strdup_printf (_("Layer mode '%s' was added in %s"),
+ enum_desc, "GIMP 2.10"));
+ version = MAX (9, version);
+ break;
+
+ /* Since 2.10 */
+ case GIMP_LAYER_MODE_NORMAL:
+ case GIMP_LAYER_MODE_BEHIND:
+ case GIMP_LAYER_MODE_MULTIPLY:
+ case GIMP_LAYER_MODE_SCREEN:
+ case GIMP_LAYER_MODE_DIFFERENCE:
+ case GIMP_LAYER_MODE_ADDITION:
+ case GIMP_LAYER_MODE_SUBTRACT:
+ case GIMP_LAYER_MODE_DARKEN_ONLY:
+ case GIMP_LAYER_MODE_LIGHTEN_ONLY:
+ case GIMP_LAYER_MODE_HSV_HUE:
+ case GIMP_LAYER_MODE_HSV_SATURATION:
+ case GIMP_LAYER_MODE_HSL_COLOR:
+ case GIMP_LAYER_MODE_HSV_VALUE:
+ case GIMP_LAYER_MODE_DIVIDE:
+ case GIMP_LAYER_MODE_DODGE:
+ case GIMP_LAYER_MODE_BURN:
+ case GIMP_LAYER_MODE_HARDLIGHT:
+ case GIMP_LAYER_MODE_SOFTLIGHT:
+ case GIMP_LAYER_MODE_GRAIN_EXTRACT:
+ case GIMP_LAYER_MODE_GRAIN_MERGE:
+ case GIMP_LAYER_MODE_VIVID_LIGHT:
+ case GIMP_LAYER_MODE_PIN_LIGHT:
+ case GIMP_LAYER_MODE_LINEAR_LIGHT:
+ case GIMP_LAYER_MODE_HARD_MIX:
+ case GIMP_LAYER_MODE_EXCLUSION:
+ case GIMP_LAYER_MODE_LINEAR_BURN:
+ case GIMP_LAYER_MODE_LUMA_DARKEN_ONLY:
+ case GIMP_LAYER_MODE_LUMA_LIGHTEN_ONLY:
+ case GIMP_LAYER_MODE_LUMINANCE:
+ case GIMP_LAYER_MODE_COLOR_ERASE:
+ case GIMP_LAYER_MODE_ERASE:
+ case GIMP_LAYER_MODE_MERGE:
+ case GIMP_LAYER_MODE_SPLIT:
+ case GIMP_LAYER_MODE_PASS_THROUGH:
+ gimp_enum_get_value (GIMP_TYPE_LAYER_MODE,
+ gimp_layer_get_mode (layer),
+ NULL, NULL, &enum_desc, NULL);
+ ADD_REASON (g_strdup_printf (_("Layer mode '%s' was added in %s"),
+ enum_desc, "GIMP 2.10"));
+ version = MAX (10, version);
+ break;
+
+ /* Just here instead of default so we get compiler warnings */
+ case GIMP_LAYER_MODE_REPLACE:
+ case GIMP_LAYER_MODE_ANTI_ERASE:
+ case GIMP_LAYER_MODE_SEPARATOR:
+ break;
+ }
+
+ /* need version 3 for layer trees */
+ if (gimp_viewable_get_children (GIMP_VIEWABLE (layer)))
+ {
+ ADD_REASON (g_strdup_printf (_("Layer groups were added in %s"),
+ "GIMP 2.8"));
+ version = MAX (3, version);
+
+ /* need version 13 for group layers with masks */
+ if (gimp_layer_get_mask (layer))
+ {
+ ADD_REASON (g_strdup_printf (_("Masks on layer groups were "
+ "added in %s"), "GIMP 2.10"));
+ version = MAX (13, version);
+ }
+ }
+ }
+
+ g_list_free (layers);
+
+ /* version 6 for new metadata has been dropped since they are
+ * saved through parasites, which is compatible with older versions.
+ */
+
+ /* need version 7 for != 8-bit gamma images */
+ if (gimp_image_get_precision (image) != GIMP_PRECISION_U8_GAMMA)
+ {
+ ADD_REASON (g_strdup_printf (_("High bit-depth images were added "
+ "in %s"), "GIMP 2.10"));
+ version = MAX (7, version);
+ }
+
+ /* need version 12 for > 8-bit images for proper endian swapping */
+ if (gimp_image_get_precision (image) > GIMP_PRECISION_U8_GAMMA)
+ version = MAX (12, version);
+
+ /* need version 8 for zlib compression */
+ if (zlib_compression)
+ {
+ ADD_REASON (g_strdup_printf (_("Internal zlib compression was "
+ "added in %s"), "GIMP 2.10"));
+ version = MAX (8, version);
+ }
+
+ /* if version is 10 (lots of new layer modes), go to version 11 with
+ * 64 bit offsets right away
+ */
+ if (version == 10)
+ version = 11;
+
+ /* use the image's in-memory size as an upper bound to estimate the
+ * need for 64 bit file offsets inside the XCF, this is a *very*
+ * conservative estimate and should never fail
+ */
+ if (gimp_object_get_memsize (GIMP_OBJECT (image), NULL) >= ((gint64) 1 << 32))
+ {
+ ADD_REASON (g_strdup_printf (_("Support for image files larger than "
+ "4GB was added in %s"), "GIMP 2.10"));
+ version = MAX (11, version);
+ }
+
+#undef ADD_REASON
+
+ switch (version)
+ {
+ case 0:
+ case 1:
+ case 2:
+ if (gimp_version) *gimp_version = 206;
+ if (version_string) *version_string = "GIMP 2.6";
+ break;
+
+ case 3:
+ if (gimp_version) *gimp_version = 208;
+ if (version_string) *version_string = "GIMP 2.8";
+ break;
+
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ case 8:
+ case 9:
+ case 10:
+ case 11:
+ case 12:
+ case 13:
+ if (gimp_version) *gimp_version = 210;
+ if (version_string) *version_string = "GIMP 2.10";
+ break;
+ }
+
+ if (version_reason && reasons)
+ {
+ GString *reason = g_string_new (NULL);
+
+ reasons = g_list_sort (reasons, (GCompareFunc) strcmp);
+
+ for (list = reasons; list; list = g_list_next (list))
+ {
+ g_string_append (reason, list->data);
+ if (g_list_next (list))
+ g_string_append_c (reason, '\n');
+ }
+
+ *version_reason = g_string_free (reason, FALSE);
+ }
+ if (reasons)
+ g_list_free_full (reasons, g_free);
+
+ return version;
+}
+
+void
+gimp_image_set_xcf_compression (GimpImage *image,
+ gboolean compression)
+{
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+
+ GIMP_IMAGE_GET_PRIVATE (image)->xcf_compression = compression;
+}
+
+gboolean
+gimp_image_get_xcf_compression (GimpImage *image)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);
+
+ return GIMP_IMAGE_GET_PRIVATE (image)->xcf_compression;
+}
+
+void
+gimp_image_set_resolution (GimpImage *image,
+ gdouble xresolution,
+ gdouble yresolution)
+{
+ GimpImagePrivate *private;
+
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+
+ private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ /* don't allow to set the resolution out of bounds */
+ if (xresolution < GIMP_MIN_RESOLUTION || xresolution > GIMP_MAX_RESOLUTION ||
+ yresolution < GIMP_MIN_RESOLUTION || yresolution > GIMP_MAX_RESOLUTION)
+ return;
+
+ private->resolution_set = TRUE;
+
+ if ((ABS (private->xresolution - xresolution) >= 1e-5) ||
+ (ABS (private->yresolution - yresolution) >= 1e-5))
+ {
+ gimp_image_undo_push_image_resolution (image,
+ C_("undo-type", "Change Image Resolution"));
+
+ private->xresolution = xresolution;
+ private->yresolution = yresolution;
+
+ gimp_image_resolution_changed (image);
+ gimp_image_size_changed_detailed (image,
+ 0,
+ 0,
+ gimp_image_get_width (image),
+ gimp_image_get_height (image));
+ }
+}
+
+void
+gimp_image_get_resolution (GimpImage *image,
+ gdouble *xresolution,
+ gdouble *yresolution)
+{
+ GimpImagePrivate *private;
+
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+ g_return_if_fail (xresolution != NULL && yresolution != NULL);
+
+ private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ *xresolution = private->xresolution;
+ *yresolution = private->yresolution;
+}
+
+void
+gimp_image_resolution_changed (GimpImage *image)
+{
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+
+ g_signal_emit (image, gimp_image_signals[RESOLUTION_CHANGED], 0);
+}
+
+void
+gimp_image_set_unit (GimpImage *image,
+ GimpUnit unit)
+{
+ GimpImagePrivate *private;
+
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+ g_return_if_fail (unit > GIMP_UNIT_PIXEL);
+
+ private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ if (private->resolution_unit != unit)
+ {
+ gimp_image_undo_push_image_resolution (image,
+ C_("undo-type", "Change Image Unit"));
+
+ private->resolution_unit = unit;
+ gimp_image_unit_changed (image);
+ }
+}
+
+GimpUnit
+gimp_image_get_unit (GimpImage *image)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), GIMP_UNIT_INCH);
+
+ return GIMP_IMAGE_GET_PRIVATE (image)->resolution_unit;
+}
+
+void
+gimp_image_unit_changed (GimpImage *image)
+{
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+
+ g_signal_emit (image, gimp_image_signals[UNIT_CHANGED], 0);
+}
+
+gint
+gimp_image_get_width (GimpImage *image)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), 0);
+
+ return GIMP_IMAGE_GET_PRIVATE (image)->width;
+}
+
+gint
+gimp_image_get_height (GimpImage *image)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), 0);
+
+ return GIMP_IMAGE_GET_PRIVATE (image)->height;
+}
+
+gboolean
+gimp_image_has_alpha (GimpImage *image)
+{
+ GimpImagePrivate *private;
+ GimpLayer *layer;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), TRUE);
+
+ private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ layer = GIMP_LAYER (gimp_container_get_first_child (private->layers->container));
+
+ return ((gimp_image_get_n_layers (image) > 1) ||
+ (layer && gimp_drawable_has_alpha (GIMP_DRAWABLE (layer))));
+}
+
+gboolean
+gimp_image_is_empty (GimpImage *image)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), TRUE);
+
+ return gimp_container_is_empty (GIMP_IMAGE_GET_PRIVATE (image)->layers->container);
+}
+
+void
+gimp_image_set_floating_selection (GimpImage *image,
+ GimpLayer *floating_sel)
+{
+ GimpImagePrivate *private;
+
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+ g_return_if_fail (floating_sel == NULL || GIMP_IS_LAYER (floating_sel));
+
+ private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ if (private->floating_sel != floating_sel)
+ {
+ private->floating_sel = floating_sel;
+
+ private->flush_accum.floating_selection_changed = TRUE;
+ }
+}
+
+GimpLayer *
+gimp_image_get_floating_selection (GimpImage *image)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+
+ return GIMP_IMAGE_GET_PRIVATE (image)->floating_sel;
+}
+
+void
+gimp_image_floating_selection_changed (GimpImage *image)
+{
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+
+ g_signal_emit (image, gimp_image_signals[FLOATING_SELECTION_CHANGED], 0);
+}
+
+GimpChannel *
+gimp_image_get_mask (GimpImage *image)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+
+ return GIMP_IMAGE_GET_PRIVATE (image)->selection_mask;
+}
+
+void
+gimp_image_mask_changed (GimpImage *image)
+{
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+
+ g_signal_emit (image, gimp_image_signals[MASK_CHANGED], 0);
+}
+
+void
+gimp_image_take_mask (GimpImage *image,
+ GimpChannel *mask)
+{
+ GimpImagePrivate *private;
+
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+ g_return_if_fail (GIMP_IS_SELECTION (mask));
+
+ private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ if (private->selection_mask)
+ g_object_unref (private->selection_mask);
+
+ private->selection_mask = g_object_ref_sink (mask);
+
+ g_signal_connect (private->selection_mask, "update",
+ G_CALLBACK (gimp_image_mask_update),
+ image);
+}
+
+
+/* image components */
+
+const Babl *
+gimp_image_get_component_format (GimpImage *image,
+ GimpChannelType channel)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+
+ switch (channel)
+ {
+ case GIMP_CHANNEL_RED:
+ return gimp_babl_component_format (GIMP_RGB,
+ gimp_image_get_precision (image),
+ RED);
+
+ case GIMP_CHANNEL_GREEN:
+ return gimp_babl_component_format (GIMP_RGB,
+ gimp_image_get_precision (image),
+ GREEN);
+
+ case GIMP_CHANNEL_BLUE:
+ return gimp_babl_component_format (GIMP_RGB,
+ gimp_image_get_precision (image),
+ BLUE);
+
+ case GIMP_CHANNEL_ALPHA:
+ return gimp_babl_component_format (GIMP_RGB,
+ gimp_image_get_precision (image),
+ ALPHA);
+
+ case GIMP_CHANNEL_GRAY:
+ return gimp_babl_component_format (GIMP_GRAY,
+ gimp_image_get_precision (image),
+ GRAY);
+
+ case GIMP_CHANNEL_INDEXED:
+ return babl_format ("Y u8"); /* will extract grayscale, the best
+ * we can do here */
+ }
+
+ return NULL;
+}
+
+gint
+gimp_image_get_component_index (GimpImage *image,
+ GimpChannelType channel)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), -1);
+
+ switch (channel)
+ {
+ case GIMP_CHANNEL_RED: return RED;
+ case GIMP_CHANNEL_GREEN: return GREEN;
+ case GIMP_CHANNEL_BLUE: return BLUE;
+ case GIMP_CHANNEL_GRAY: return GRAY;
+ case GIMP_CHANNEL_INDEXED: return INDEXED;
+ case GIMP_CHANNEL_ALPHA:
+ switch (gimp_image_get_base_type (image))
+ {
+ case GIMP_RGB: return ALPHA;
+ case GIMP_GRAY: return ALPHA_G;
+ case GIMP_INDEXED: return ALPHA_I;
+ }
+ }
+
+ return -1;
+}
+
+void
+gimp_image_set_component_active (GimpImage *image,
+ GimpChannelType channel,
+ gboolean active)
+{
+ GimpImagePrivate *private;
+ gint index = -1;
+
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+
+ private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ index = gimp_image_get_component_index (image, channel);
+
+ if (index != -1 && active != private->active[index])
+ {
+ private->active[index] = active ? TRUE : FALSE;
+
+ /* If there is an active channel and we mess with the components,
+ * the active channel gets unset...
+ */
+ gimp_image_unset_active_channel (image);
+
+ g_signal_emit (image,
+ gimp_image_signals[COMPONENT_ACTIVE_CHANGED], 0,
+ channel);
+ }
+}
+
+gboolean
+gimp_image_get_component_active (GimpImage *image,
+ GimpChannelType channel)
+{
+ gint index = -1;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);
+
+ index = gimp_image_get_component_index (image, channel);
+
+ if (index != -1)
+ return GIMP_IMAGE_GET_PRIVATE (image)->active[index];
+
+ return FALSE;
+}
+
+void
+gimp_image_get_active_array (GimpImage *image,
+ gboolean *components)
+{
+ GimpImagePrivate *private;
+ gint i;
+
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+ g_return_if_fail (components != NULL);
+
+ private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ for (i = 0; i < MAX_CHANNELS; i++)
+ components[i] = private->active[i];
+}
+
+GimpComponentMask
+gimp_image_get_active_mask (GimpImage *image)
+{
+ GimpImagePrivate *private;
+ GimpComponentMask mask = 0;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), 0);
+
+ private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ switch (gimp_image_get_base_type (image))
+ {
+ case GIMP_RGB:
+ mask |= (private->active[RED]) ? GIMP_COMPONENT_MASK_RED : 0;
+ mask |= (private->active[GREEN]) ? GIMP_COMPONENT_MASK_GREEN : 0;
+ mask |= (private->active[BLUE]) ? GIMP_COMPONENT_MASK_BLUE : 0;
+ mask |= (private->active[ALPHA]) ? GIMP_COMPONENT_MASK_ALPHA : 0;
+ break;
+
+ case GIMP_GRAY:
+ case GIMP_INDEXED:
+ mask |= (private->active[GRAY]) ? GIMP_COMPONENT_MASK_RED : 0;
+ mask |= (private->active[GRAY]) ? GIMP_COMPONENT_MASK_GREEN : 0;
+ mask |= (private->active[GRAY]) ? GIMP_COMPONENT_MASK_BLUE : 0;
+ mask |= (private->active[ALPHA_G]) ? GIMP_COMPONENT_MASK_ALPHA : 0;
+ break;
+ }
+
+ return mask;
+}
+
+void
+gimp_image_set_component_visible (GimpImage *image,
+ GimpChannelType channel,
+ gboolean visible)
+{
+ GimpImagePrivate *private;
+ gint index = -1;
+
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+
+ private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ index = gimp_image_get_component_index (image, channel);
+
+ if (index != -1 && visible != private->visible[index])
+ {
+ private->visible[index] = visible ? TRUE : FALSE;
+
+ if (private->visible_mask)
+ {
+ GimpComponentMask mask;
+
+ mask = ~gimp_image_get_visible_mask (image) & GIMP_COMPONENT_MASK_ALL;
+
+ gegl_node_set (private->visible_mask,
+ "mask", mask,
+ NULL);
+ }
+
+ g_signal_emit (image,
+ gimp_image_signals[COMPONENT_VISIBILITY_CHANGED], 0,
+ channel);
+
+ gimp_image_invalidate_all (image);
+ }
+}
+
+gboolean
+gimp_image_get_component_visible (GimpImage *image,
+ GimpChannelType channel)
+{
+ gint index = -1;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);
+
+ index = gimp_image_get_component_index (image, channel);
+
+ if (index != -1)
+ return GIMP_IMAGE_GET_PRIVATE (image)->visible[index];
+
+ return FALSE;
+}
+
+void
+gimp_image_get_visible_array (GimpImage *image,
+ gboolean *components)
+{
+ GimpImagePrivate *private;
+ gint i;
+
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+ g_return_if_fail (components != NULL);
+
+ private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ for (i = 0; i < MAX_CHANNELS; i++)
+ components[i] = private->visible[i];
+}
+
+GimpComponentMask
+gimp_image_get_visible_mask (GimpImage *image)
+{
+ GimpImagePrivate *private;
+ GimpComponentMask mask = 0;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), 0);
+
+ private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ switch (gimp_image_get_base_type (image))
+ {
+ case GIMP_RGB:
+ mask |= (private->visible[RED]) ? GIMP_COMPONENT_MASK_RED : 0;
+ mask |= (private->visible[GREEN]) ? GIMP_COMPONENT_MASK_GREEN : 0;
+ mask |= (private->visible[BLUE]) ? GIMP_COMPONENT_MASK_BLUE : 0;
+ mask |= (private->visible[ALPHA]) ? GIMP_COMPONENT_MASK_ALPHA : 0;
+ break;
+
+ case GIMP_GRAY:
+ case GIMP_INDEXED:
+ mask |= (private->visible[GRAY]) ? GIMP_COMPONENT_MASK_RED : 0;
+ mask |= (private->visible[GRAY]) ? GIMP_COMPONENT_MASK_GREEN : 0;
+ mask |= (private->visible[GRAY]) ? GIMP_COMPONENT_MASK_BLUE : 0;
+ mask |= (private->visible[ALPHA]) ? GIMP_COMPONENT_MASK_ALPHA : 0;
+ break;
+ }
+
+ return mask;
+}
+
+
+/* emitting image signals */
+
+void
+gimp_image_mode_changed (GimpImage *image)
+{
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+
+ g_signal_emit (image, gimp_image_signals[MODE_CHANGED], 0);
+}
+
+void
+gimp_image_precision_changed (GimpImage *image)
+{
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+
+ g_signal_emit (image, gimp_image_signals[PRECISION_CHANGED], 0);
+}
+
+void
+gimp_image_alpha_changed (GimpImage *image)
+{
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+
+ g_signal_emit (image, gimp_image_signals[ALPHA_CHANGED], 0);
+}
+
+void
+gimp_image_linked_items_changed (GimpImage *image)
+{
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+
+ g_signal_emit (image, gimp_image_signals[LINKED_ITEMS_CHANGED], 0);
+}
+
+void
+gimp_image_invalidate (GimpImage *image,
+ gint x,
+ gint y,
+ gint width,
+ gint height)
+{
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+
+ gimp_projectable_invalidate (GIMP_PROJECTABLE (image),
+ x, y, width, height);
+
+ GIMP_IMAGE_GET_PRIVATE (image)->flush_accum.preview_invalidated = TRUE;
+}
+
+void
+gimp_image_invalidate_all (GimpImage *image)
+{
+ const GeglRectangle *bounding_box;
+
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+
+ bounding_box = &GIMP_IMAGE_GET_PRIVATE (image)->bounding_box;
+
+ gimp_image_invalidate (image,
+ bounding_box->x, bounding_box->y,
+ bounding_box->width, bounding_box->height);
+}
+
+void
+gimp_image_guide_added (GimpImage *image,
+ GimpGuide *guide)
+{
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+ g_return_if_fail (GIMP_IS_GUIDE (guide));
+
+ g_signal_emit (image, gimp_image_signals[GUIDE_ADDED], 0,
+ guide);
+}
+
+void
+gimp_image_guide_removed (GimpImage *image,
+ GimpGuide *guide)
+{
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+ g_return_if_fail (GIMP_IS_GUIDE (guide));
+
+ g_signal_emit (image, gimp_image_signals[GUIDE_REMOVED], 0,
+ guide);
+}
+
+void
+gimp_image_guide_moved (GimpImage *image,
+ GimpGuide *guide)
+{
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+ g_return_if_fail (GIMP_IS_GUIDE (guide));
+
+ g_signal_emit (image, gimp_image_signals[GUIDE_MOVED], 0,
+ guide);
+}
+
+void
+gimp_image_sample_point_added (GimpImage *image,
+ GimpSamplePoint *sample_point)
+{
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+ g_return_if_fail (GIMP_IS_SAMPLE_POINT (sample_point));
+
+ g_signal_emit (image, gimp_image_signals[SAMPLE_POINT_ADDED], 0,
+ sample_point);
+}
+
+void
+gimp_image_sample_point_removed (GimpImage *image,
+ GimpSamplePoint *sample_point)
+{
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+ g_return_if_fail (GIMP_IS_SAMPLE_POINT (sample_point));
+
+ g_signal_emit (image, gimp_image_signals[SAMPLE_POINT_REMOVED], 0,
+ sample_point);
+}
+
+void
+gimp_image_sample_point_moved (GimpImage *image,
+ GimpSamplePoint *sample_point)
+{
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+ g_return_if_fail (GIMP_IS_SAMPLE_POINT (sample_point));
+
+ g_signal_emit (image, gimp_image_signals[SAMPLE_POINT_MOVED], 0,
+ sample_point);
+}
+
+/**
+ * gimp_image_size_changed_detailed:
+ * @image:
+ * @previous_origin_x:
+ * @previous_origin_y:
+ *
+ * Emits the size-changed-detailed signal that is typically used to adjust the
+ * position of the image in the display shell on various operations,
+ * e.g. crop.
+ *
+ * This function makes sure that GimpViewable::size-changed is also emitted.
+ **/
+void
+gimp_image_size_changed_detailed (GimpImage *image,
+ gint previous_origin_x,
+ gint previous_origin_y,
+ gint previous_width,
+ gint previous_height)
+{
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+
+ g_signal_emit (image, gimp_image_signals[SIZE_CHANGED_DETAILED], 0,
+ previous_origin_x,
+ previous_origin_y,
+ previous_width,
+ previous_height);
+}
+
+void
+gimp_image_colormap_changed (GimpImage *image,
+ gint color_index)
+{
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+ g_return_if_fail (color_index >= -1 &&
+ color_index < GIMP_IMAGE_GET_PRIVATE (image)->n_colors);
+
+ g_signal_emit (image, gimp_image_signals[COLORMAP_CHANGED], 0,
+ color_index);
+}
+
+void
+gimp_image_selection_invalidate (GimpImage *image)
+{
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+
+ g_signal_emit (image, gimp_image_signals[SELECTION_INVALIDATE], 0);
+}
+
+void
+gimp_image_quick_mask_changed (GimpImage *image)
+{
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+
+ g_signal_emit (image, gimp_image_signals[QUICK_MASK_CHANGED], 0);
+}
+
+void
+gimp_image_undo_event (GimpImage *image,
+ GimpUndoEvent event,
+ GimpUndo *undo)
+{
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+ g_return_if_fail (((event == GIMP_UNDO_EVENT_UNDO_FREE ||
+ event == GIMP_UNDO_EVENT_UNDO_FREEZE ||
+ event == GIMP_UNDO_EVENT_UNDO_THAW) && undo == NULL) ||
+ GIMP_IS_UNDO (undo));
+
+ g_signal_emit (image, gimp_image_signals[UNDO_EVENT], 0, event, undo);
+}
+
+
+/* dirty counters */
+
+/* NOTE about the image->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 100000, this is due to undo-ing back
+ * before a saved version, then changing 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 gimpimage-undo.c for the gory 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.
+ */
+
+gint
+gimp_image_dirty (GimpImage *image,
+ GimpDirtyMask dirty_mask)
+{
+ GimpImagePrivate *private;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);
+
+ private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ private->dirty++;
+ private->export_dirty++;
+
+ if (! private->dirty_time)
+ private->dirty_time = time (NULL);
+
+ g_signal_emit (image, gimp_image_signals[DIRTY], 0, dirty_mask);
+
+ TRC (("dirty %d -> %d\n", private->dirty - 1, private->dirty));
+
+ return private->dirty;
+}
+
+gint
+gimp_image_clean (GimpImage *image,
+ GimpDirtyMask dirty_mask)
+{
+ GimpImagePrivate *private;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);
+
+ private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ private->dirty--;
+ private->export_dirty--;
+
+ g_signal_emit (image, gimp_image_signals[CLEAN], 0, dirty_mask);
+
+ TRC (("clean %d -> %d\n", private->dirty + 1, private->dirty));
+
+ return private->dirty;
+}
+
+void
+gimp_image_clean_all (GimpImage *image)
+{
+ GimpImagePrivate *private;
+
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+
+ private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ private->dirty = 0;
+ private->dirty_time = 0;
+
+ g_signal_emit (image, gimp_image_signals[CLEAN], 0, GIMP_DIRTY_ALL);
+
+ gimp_object_name_changed (GIMP_OBJECT (image));
+}
+
+void
+gimp_image_export_clean_all (GimpImage *image)
+{
+ GimpImagePrivate *private;
+
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+
+ private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ private->export_dirty = 0;
+
+ g_signal_emit (image, gimp_image_signals[CLEAN], 0, GIMP_DIRTY_ALL);
+
+ gimp_object_name_changed (GIMP_OBJECT (image));
+}
+
+/**
+ * gimp_image_is_dirty:
+ * @image:
+ *
+ * Returns: True if the image is dirty, false otherwise.
+ **/
+gint
+gimp_image_is_dirty (GimpImage *image)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);
+
+ return GIMP_IMAGE_GET_PRIVATE (image)->dirty != 0;
+}
+
+/**
+ * gimp_image_is_export_dirty:
+ * @image:
+ *
+ * Returns: True if the image export is dirty, false otherwise.
+ **/
+gboolean
+gimp_image_is_export_dirty (GimpImage *image)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);
+
+ return GIMP_IMAGE_GET_PRIVATE (image)->export_dirty != 0;
+}
+
+gint64
+gimp_image_get_dirty_time (GimpImage *image)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), 0);
+
+ return GIMP_IMAGE_GET_PRIVATE (image)->dirty_time;
+}
+
+/**
+ * gimp_image_saving:
+ * @image:
+ *
+ * Emits the "saving" signal, indicating that @image is about to be saved,
+ * or exported.
+ */
+void
+gimp_image_saving (GimpImage *image)
+{
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+
+ g_signal_emit (image, gimp_image_signals[SAVING], 0);
+}
+
+/**
+ * gimp_image_saved:
+ * @image:
+ * @file:
+ *
+ * Emits the "saved" signal, indicating that @image was saved to the
+ * location specified by @file.
+ */
+void
+gimp_image_saved (GimpImage *image,
+ GFile *file)
+{
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+ g_return_if_fail (G_IS_FILE (file));
+
+ g_signal_emit (image, gimp_image_signals[SAVED], 0, file);
+}
+
+/**
+ * gimp_image_exported:
+ * @image:
+ * @file:
+ *
+ * Emits the "exported" signal, indicating that @image was exported to the
+ * location specified by @file.
+ */
+void
+gimp_image_exported (GimpImage *image,
+ GFile *file)
+{
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+ g_return_if_fail (G_IS_FILE (file));
+
+ g_signal_emit (image, gimp_image_signals[EXPORTED], 0, file);
+}
+
+
+/* flush this image's displays */
+
+void
+gimp_image_flush (GimpImage *image)
+{
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+
+ gimp_projectable_flush (GIMP_PROJECTABLE (image),
+ GIMP_IMAGE_GET_PRIVATE (image)->flush_accum.preview_invalidated);
+}
+
+
+/* display / instance counters */
+
+gint
+gimp_image_get_display_count (GimpImage *image)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), 0);
+
+ return GIMP_IMAGE_GET_PRIVATE (image)->disp_count;
+}
+
+void
+gimp_image_inc_display_count (GimpImage *image)
+{
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+
+ GIMP_IMAGE_GET_PRIVATE (image)->disp_count++;
+}
+
+void
+gimp_image_dec_display_count (GimpImage *image)
+{
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+
+ GIMP_IMAGE_GET_PRIVATE (image)->disp_count--;
+}
+
+gint
+gimp_image_get_instance_count (GimpImage *image)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), 0);
+
+ return GIMP_IMAGE_GET_PRIVATE (image)->instance_count;
+}
+
+void
+gimp_image_inc_instance_count (GimpImage *image)
+{
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+
+ GIMP_IMAGE_GET_PRIVATE (image)->instance_count++;
+}
+
+void
+gimp_image_inc_show_all_count (GimpImage *image)
+{
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+
+ GIMP_IMAGE_GET_PRIVATE (image)->show_all++;
+
+ if (GIMP_IMAGE_GET_PRIVATE (image)->show_all == 1)
+ {
+ g_clear_object (&GIMP_IMAGE_GET_PRIVATE (image)->pickable_buffer);
+
+ gimp_image_update_bounding_box (image);
+ }
+}
+
+void
+gimp_image_dec_show_all_count (GimpImage *image)
+{
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+
+ GIMP_IMAGE_GET_PRIVATE (image)->show_all--;
+
+ if (GIMP_IMAGE_GET_PRIVATE (image)->show_all == 0)
+ {
+ g_clear_object (&GIMP_IMAGE_GET_PRIVATE (image)->pickable_buffer);
+
+ gimp_image_update_bounding_box (image);
+ }
+}
+
+
+/* parasites */
+
+const GimpParasite *
+gimp_image_parasite_find (GimpImage *image,
+ const gchar *name)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+
+ return gimp_parasite_list_find (GIMP_IMAGE_GET_PRIVATE (image)->parasites,
+ name);
+}
+
+static void
+list_func (gchar *key,
+ GimpParasite *p,
+ gchar ***cur)
+{
+ *(*cur)++ = (gchar *) g_strdup (key);
+}
+
+gchar **
+gimp_image_parasite_list (GimpImage *image,
+ gint *count)
+{
+ GimpImagePrivate *private;
+ gchar **list;
+ gchar **cur;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+
+ private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ *count = gimp_parasite_list_length (private->parasites);
+ cur = list = g_new (gchar *, *count);
+
+ gimp_parasite_list_foreach (private->parasites, (GHFunc) list_func, &cur);
+
+ return list;
+}
+
+gboolean
+gimp_image_parasite_validate (GimpImage *image,
+ const GimpParasite *parasite,
+ GError **error)
+{
+ const gchar *name;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);
+ g_return_val_if_fail (parasite != NULL, FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ name = gimp_parasite_name (parasite);
+
+ if (strcmp (name, GIMP_ICC_PROFILE_PARASITE_NAME) == 0)
+ {
+ return gimp_image_validate_icc_parasite (image, parasite, NULL, error);
+ }
+ else if (strcmp (name, "gimp-comment") == 0)
+ {
+ const gchar *data = gimp_parasite_data (parasite);
+ gssize length = gimp_parasite_data_size (parasite);
+ gboolean valid = FALSE;
+
+ if (length > 0)
+ {
+ if (data[length - 1] == '\0')
+ valid = g_utf8_validate (data, -1, NULL);
+ else
+ valid = g_utf8_validate (data, length, NULL);
+ }
+
+ if (! valid)
+ {
+ g_set_error (error, GIMP_ERROR, GIMP_FAILED,
+ _("'gimp-comment' parasite validation failed: "
+ "comment contains invalid UTF-8"));
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+void
+gimp_image_parasite_attach (GimpImage *image,
+ const GimpParasite *parasite,
+ gboolean push_undo)
+{
+ GimpImagePrivate *private;
+ GimpParasite copy;
+ const gchar *name;
+
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+ g_return_if_fail (parasite != NULL);
+
+ private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ name = gimp_parasite_name (parasite);
+
+ /* this is so ugly and is only for the PDB */
+ if (strcmp (name, GIMP_ICC_PROFILE_PARASITE_NAME) == 0)
+ {
+ GimpColorProfile *profile;
+ GimpColorProfile *builtin;
+
+ profile =
+ gimp_color_profile_new_from_icc_profile (gimp_parasite_data (parasite),
+ gimp_parasite_data_size (parasite),
+ NULL);
+ builtin = gimp_image_get_builtin_color_profile (image);
+
+ if (gimp_color_profile_is_equal (profile, builtin))
+ {
+ /* setting the builtin profile is equal to removing the profile */
+ gimp_image_parasite_detach (image, GIMP_ICC_PROFILE_PARASITE_NAME,
+ push_undo);
+ g_object_unref (profile);
+ return;
+ }
+
+ g_object_unref (profile);
+ }
+
+ /* make a temporary copy of the GimpParasite struct because
+ * gimp_parasite_shift_parent() changes it
+ */
+ copy = *parasite;
+
+ /* only set the dirty bit manually if we can be saved and the new
+ * parasite differs from the current one and we aren't undoable
+ */
+ if (push_undo && gimp_parasite_is_undoable (&copy))
+ gimp_image_undo_push_image_parasite (image,
+ C_("undo-type", "Attach Parasite to Image"),
+ &copy);
+
+ /* We used to push a cantundo on the stack here. This made the undo stack
+ * unusable (NULL on the stack) and prevented people from undoing after a
+ * save (since most save plug-ins attach an undoable comment parasite).
+ * Now we simply attach the parasite without pushing an undo. That way
+ * it's undoable but does not block the undo system. --Sven
+ */
+ gimp_parasite_list_add (private->parasites, &copy);
+
+ if (push_undo && gimp_parasite_has_flag (&copy, GIMP_PARASITE_ATTACH_PARENT))
+ {
+ gimp_parasite_shift_parent (&copy);
+ gimp_parasite_attach (image->gimp, &copy);
+ }
+
+ if (strcmp (name, GIMP_ICC_PROFILE_PARASITE_NAME) == 0)
+ _gimp_image_update_color_profile (image, parasite);
+
+ g_signal_emit (image, gimp_image_signals[PARASITE_ATTACHED], 0,
+ name);
+}
+
+void
+gimp_image_parasite_detach (GimpImage *image,
+ const gchar *name,
+ gboolean push_undo)
+{
+ GimpImagePrivate *private;
+ const GimpParasite *parasite;
+
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+ g_return_if_fail (name != NULL);
+
+ private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ if (! (parasite = gimp_parasite_list_find (private->parasites, name)))
+ return;
+
+ if (push_undo && gimp_parasite_is_undoable (parasite))
+ gimp_image_undo_push_image_parasite_remove (image,
+ C_("undo-type", "Remove Parasite from Image"),
+ name);
+
+ gimp_parasite_list_remove (private->parasites, name);
+
+ if (strcmp (name, GIMP_ICC_PROFILE_PARASITE_NAME) == 0)
+ _gimp_image_update_color_profile (image, NULL);
+
+ g_signal_emit (image, gimp_image_signals[PARASITE_DETACHED], 0,
+ name);
+}
+
+
+/* tattoos */
+
+GimpTattoo
+gimp_image_get_new_tattoo (GimpImage *image)
+{
+ GimpImagePrivate *private;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), 0);
+
+ private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ private->tattoo_state++;
+
+ if (G_UNLIKELY (private->tattoo_state == 0))
+ g_warning ("%s: Tattoo state corrupted (integer overflow).", G_STRFUNC);
+
+ return private->tattoo_state;
+}
+
+GimpTattoo
+gimp_image_get_tattoo_state (GimpImage *image)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), 0);
+
+ return GIMP_IMAGE_GET_PRIVATE (image)->tattoo_state;
+}
+
+gboolean
+gimp_image_set_tattoo_state (GimpImage *image,
+ GimpTattoo val)
+{
+ GList *all_items;
+ GList *list;
+ gboolean retval = TRUE;
+ GimpTattoo maxval = 0;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);
+
+ /* Check that the layer tattoos don't overlap with channel or vector ones */
+ all_items = gimp_image_get_layer_list (image);
+
+ for (list = all_items; list; list = g_list_next (list))
+ {
+ GimpTattoo ltattoo;
+
+ ltattoo = gimp_item_get_tattoo (GIMP_ITEM (list->data));
+ if (ltattoo > maxval)
+ maxval = ltattoo;
+
+ if (gimp_image_get_channel_by_tattoo (image, ltattoo))
+ retval = FALSE; /* Oopps duplicated tattoo in channel */
+
+ if (gimp_image_get_vectors_by_tattoo (image, ltattoo))
+ retval = FALSE; /* Oopps duplicated tattoo in vectors */
+ }
+
+ g_list_free (all_items);
+
+ /* Now check that the channel and vectors tattoos don't overlap */
+ all_items = gimp_image_get_channel_list (image);
+
+ for (list = all_items; list; list = g_list_next (list))
+ {
+ GimpTattoo ctattoo;
+
+ ctattoo = gimp_item_get_tattoo (GIMP_ITEM (list->data));
+ if (ctattoo > maxval)
+ maxval = ctattoo;
+
+ if (gimp_image_get_vectors_by_tattoo (image, ctattoo))
+ retval = FALSE; /* Oopps duplicated tattoo in vectors */
+ }
+
+ g_list_free (all_items);
+
+ /* Find the max tattoo value in the vectors */
+ all_items = gimp_image_get_vectors_list (image);
+
+ for (list = all_items; list; list = g_list_next (list))
+ {
+ GimpTattoo vtattoo;
+
+ vtattoo = gimp_item_get_tattoo (GIMP_ITEM (list->data));
+ if (vtattoo > maxval)
+ maxval = vtattoo;
+ }
+
+ g_list_free (all_items);
+
+ if (val < maxval)
+ retval = FALSE;
+
+ /* Must check if the state is valid */
+ if (retval == TRUE)
+ GIMP_IMAGE_GET_PRIVATE (image)->tattoo_state = val;
+
+ return retval;
+}
+
+
+/* projection */
+
+GimpProjection *
+gimp_image_get_projection (GimpImage *image)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+
+ return GIMP_IMAGE_GET_PRIVATE (image)->projection;
+}
+
+
+/* layers / channels / vectors */
+
+GimpItemTree *
+gimp_image_get_layer_tree (GimpImage *image)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+
+ return GIMP_IMAGE_GET_PRIVATE (image)->layers;
+}
+
+GimpItemTree *
+gimp_image_get_channel_tree (GimpImage *image)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+
+ return GIMP_IMAGE_GET_PRIVATE (image)->channels;
+}
+
+GimpItemTree *
+gimp_image_get_vectors_tree (GimpImage *image)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+
+ return GIMP_IMAGE_GET_PRIVATE (image)->vectors;
+}
+
+GimpContainer *
+gimp_image_get_layers (GimpImage *image)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+
+ return GIMP_IMAGE_GET_PRIVATE (image)->layers->container;
+}
+
+GimpContainer *
+gimp_image_get_channels (GimpImage *image)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+
+ return GIMP_IMAGE_GET_PRIVATE (image)->channels->container;
+}
+
+GimpContainer *
+gimp_image_get_vectors (GimpImage *image)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+
+ return GIMP_IMAGE_GET_PRIVATE (image)->vectors->container;
+}
+
+gint
+gimp_image_get_n_layers (GimpImage *image)
+{
+ GimpItemStack *stack;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), 0);
+
+ stack = GIMP_ITEM_STACK (gimp_image_get_layers (image));
+
+ return gimp_item_stack_get_n_items (stack);
+}
+
+gint
+gimp_image_get_n_channels (GimpImage *image)
+{
+ GimpItemStack *stack;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), 0);
+
+ stack = GIMP_ITEM_STACK (gimp_image_get_channels (image));
+
+ return gimp_item_stack_get_n_items (stack);
+}
+
+gint
+gimp_image_get_n_vectors (GimpImage *image)
+{
+ GimpItemStack *stack;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), 0);
+
+ stack = GIMP_ITEM_STACK (gimp_image_get_vectors (image));
+
+ return gimp_item_stack_get_n_items (stack);
+}
+
+GList *
+gimp_image_get_layer_iter (GimpImage *image)
+{
+ GimpItemStack *stack;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+
+ stack = GIMP_ITEM_STACK (gimp_image_get_layers (image));
+
+ return gimp_item_stack_get_item_iter (stack);
+}
+
+GList *
+gimp_image_get_channel_iter (GimpImage *image)
+{
+ GimpItemStack *stack;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+
+ stack = GIMP_ITEM_STACK (gimp_image_get_channels (image));
+
+ return gimp_item_stack_get_item_iter (stack);
+}
+
+GList *
+gimp_image_get_vectors_iter (GimpImage *image)
+{
+ GimpItemStack *stack;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+
+ stack = GIMP_ITEM_STACK (gimp_image_get_vectors (image));
+
+ return gimp_item_stack_get_item_iter (stack);
+}
+
+GList *
+gimp_image_get_layer_list (GimpImage *image)
+{
+ GimpItemStack *stack;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+
+ stack = GIMP_ITEM_STACK (gimp_image_get_layers (image));
+
+ return gimp_item_stack_get_item_list (stack);
+}
+
+GList *
+gimp_image_get_channel_list (GimpImage *image)
+{
+ GimpItemStack *stack;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+
+ stack = GIMP_ITEM_STACK (gimp_image_get_channels (image));
+
+ return gimp_item_stack_get_item_list (stack);
+}
+
+GList *
+gimp_image_get_vectors_list (GimpImage *image)
+{
+ GimpItemStack *stack;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+
+ stack = GIMP_ITEM_STACK (gimp_image_get_vectors (image));
+
+ return gimp_item_stack_get_item_list (stack);
+}
+
+
+/* active drawable, layer, channel, vectors */
+
+GimpDrawable *
+gimp_image_get_active_drawable (GimpImage *image)
+{
+ GimpImagePrivate *private;
+ GimpItem *active_channel;
+ GimpItem *active_layer;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+
+ private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ active_channel = gimp_item_tree_get_active_item (private->channels);
+ active_layer = gimp_item_tree_get_active_item (private->layers);
+
+ /* If there is an active channel (a saved selection, etc.),
+ * we ignore the active layer
+ */
+ if (active_channel)
+ {
+ return GIMP_DRAWABLE (active_channel);
+ }
+ else if (active_layer)
+ {
+ GimpLayer *layer = GIMP_LAYER (active_layer);
+ GimpLayerMask *mask = gimp_layer_get_mask (layer);
+
+ if (mask && gimp_layer_get_edit_mask (layer))
+ return GIMP_DRAWABLE (mask);
+ else
+ return GIMP_DRAWABLE (layer);
+ }
+
+ return NULL;
+}
+
+GimpLayer *
+gimp_image_get_active_layer (GimpImage *image)
+{
+ GimpImagePrivate *private;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+
+ private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ return GIMP_LAYER (gimp_item_tree_get_active_item (private->layers));
+}
+
+GimpChannel *
+gimp_image_get_active_channel (GimpImage *image)
+{
+ GimpImagePrivate *private;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+
+ private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ return GIMP_CHANNEL (gimp_item_tree_get_active_item (private->channels));
+}
+
+GimpVectors *
+gimp_image_get_active_vectors (GimpImage *image)
+{
+ GimpImagePrivate *private;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+
+ private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ return GIMP_VECTORS (gimp_item_tree_get_active_item (private->vectors));
+}
+
+GimpLayer *
+gimp_image_set_active_layer (GimpImage *image,
+ GimpLayer *layer)
+{
+ GimpImagePrivate *private;
+ GimpLayer *floating_sel;
+ GimpLayer *active_layer;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+ g_return_val_if_fail (layer == NULL || GIMP_IS_LAYER (layer), NULL);
+ g_return_val_if_fail (layer == NULL ||
+ (gimp_item_is_attached (GIMP_ITEM (layer)) &&
+ gimp_item_get_image (GIMP_ITEM (layer)) == image),
+ NULL);
+
+ private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ floating_sel = gimp_image_get_floating_selection (image);
+
+ /* Make sure the floating_sel always is the active layer */
+ if (floating_sel && layer != floating_sel)
+ return floating_sel;
+
+ active_layer = gimp_image_get_active_layer (image);
+
+ if (layer != active_layer)
+ {
+ /* Don't cache selection info for the previous active layer */
+ if (active_layer)
+ gimp_drawable_invalidate_boundary (GIMP_DRAWABLE (active_layer));
+
+ gimp_item_tree_set_active_item (private->layers, GIMP_ITEM (layer));
+ }
+
+ return gimp_image_get_active_layer (image);
+}
+
+GimpChannel *
+gimp_image_set_active_channel (GimpImage *image,
+ GimpChannel *channel)
+{
+ GimpImagePrivate *private;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+ g_return_val_if_fail (channel == NULL || GIMP_IS_CHANNEL (channel), NULL);
+ g_return_val_if_fail (channel == NULL ||
+ (gimp_item_is_attached (GIMP_ITEM (channel)) &&
+ gimp_item_get_image (GIMP_ITEM (channel)) == image),
+ NULL);
+
+ private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ /* Not if there is a floating selection */
+ if (channel && gimp_image_get_floating_selection (image))
+ return NULL;
+
+ if (channel != gimp_image_get_active_channel (image))
+ {
+ gimp_item_tree_set_active_item (private->channels, GIMP_ITEM (channel));
+ }
+
+ return gimp_image_get_active_channel (image);
+}
+
+GimpChannel *
+gimp_image_unset_active_channel (GimpImage *image)
+{
+ GimpImagePrivate *private;
+ GimpChannel *channel;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+
+ private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ channel = gimp_image_get_active_channel (image);
+
+ if (channel)
+ {
+ gimp_image_set_active_channel (image, NULL);
+
+ if (private->layer_stack)
+ gimp_image_set_active_layer (image, private->layer_stack->data);
+ }
+
+ return channel;
+}
+
+GimpVectors *
+gimp_image_set_active_vectors (GimpImage *image,
+ GimpVectors *vectors)
+{
+ GimpImagePrivate *private;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+ g_return_val_if_fail (vectors == NULL || GIMP_IS_VECTORS (vectors), NULL);
+ g_return_val_if_fail (vectors == NULL ||
+ (gimp_item_is_attached (GIMP_ITEM (vectors)) &&
+ gimp_item_get_image (GIMP_ITEM (vectors)) == image),
+ NULL);
+
+ private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ if (vectors != gimp_image_get_active_vectors (image))
+ {
+ gimp_item_tree_set_active_item (private->vectors, GIMP_ITEM (vectors));
+ }
+
+ return gimp_image_get_active_vectors (image);
+}
+
+
+/* layer, channel, vectors by tattoo */
+
+GimpLayer *
+gimp_image_get_layer_by_tattoo (GimpImage *image,
+ GimpTattoo tattoo)
+{
+ GimpItemStack *stack;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+
+ stack = GIMP_ITEM_STACK (gimp_image_get_layers (image));
+
+ return GIMP_LAYER (gimp_item_stack_get_item_by_tattoo (stack, tattoo));
+}
+
+GimpChannel *
+gimp_image_get_channel_by_tattoo (GimpImage *image,
+ GimpTattoo tattoo)
+{
+ GimpItemStack *stack;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+
+ stack = GIMP_ITEM_STACK (gimp_image_get_channels (image));
+
+ return GIMP_CHANNEL (gimp_item_stack_get_item_by_tattoo (stack, tattoo));
+}
+
+GimpVectors *
+gimp_image_get_vectors_by_tattoo (GimpImage *image,
+ GimpTattoo tattoo)
+{
+ GimpItemStack *stack;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+
+ stack = GIMP_ITEM_STACK (gimp_image_get_vectors (image));
+
+ return GIMP_VECTORS (gimp_item_stack_get_item_by_tattoo (stack, tattoo));
+}
+
+
+/* layer, channel, vectors by name */
+
+GimpLayer *
+gimp_image_get_layer_by_name (GimpImage *image,
+ const gchar *name)
+{
+ GimpItemTree *tree;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+ g_return_val_if_fail (name != NULL, NULL);
+
+ tree = gimp_image_get_layer_tree (image);
+
+ return GIMP_LAYER (gimp_item_tree_get_item_by_name (tree, name));
+}
+
+GimpChannel *
+gimp_image_get_channel_by_name (GimpImage *image,
+ const gchar *name)
+{
+ GimpItemTree *tree;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+ g_return_val_if_fail (name != NULL, NULL);
+
+ tree = gimp_image_get_channel_tree (image);
+
+ return GIMP_CHANNEL (gimp_item_tree_get_item_by_name (tree, name));
+}
+
+GimpVectors *
+gimp_image_get_vectors_by_name (GimpImage *image,
+ const gchar *name)
+{
+ GimpItemTree *tree;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+ g_return_val_if_fail (name != NULL, NULL);
+
+ tree = gimp_image_get_vectors_tree (image);
+
+ return GIMP_VECTORS (gimp_item_tree_get_item_by_name (tree, name));
+}
+
+
+/* items */
+
+gboolean
+gimp_image_reorder_item (GimpImage *image,
+ GimpItem *item,
+ GimpItem *new_parent,
+ gint new_index,
+ gboolean push_undo,
+ const gchar *undo_desc)
+{
+ GimpItemTree *tree;
+ gboolean result;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);
+ g_return_val_if_fail (GIMP_IS_ITEM (item), FALSE);
+ g_return_val_if_fail (gimp_item_get_image (item) == image, FALSE);
+
+ tree = gimp_item_get_tree (item);
+
+ g_return_val_if_fail (tree != NULL, FALSE);
+
+ if (push_undo)
+ {
+ if (! undo_desc)
+ undo_desc = GIMP_ITEM_GET_CLASS (item)->reorder_desc;
+
+ gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_IMAGE_ITEM_REORDER,
+ undo_desc);
+ }
+
+ gimp_image_freeze_bounding_box (image);
+
+ gimp_item_start_move (item, push_undo);
+
+ /* item and new_parent are type-checked in GimpItemTree
+ */
+ result = gimp_item_tree_reorder_item (tree, item,
+ new_parent, new_index,
+ push_undo, undo_desc);
+
+ gimp_item_end_move (item, push_undo);
+
+ gimp_image_thaw_bounding_box (image);
+
+ if (push_undo)
+ gimp_image_undo_group_end (image);
+
+ return result;
+}
+
+gboolean
+gimp_image_raise_item (GimpImage *image,
+ GimpItem *item,
+ GError **error)
+{
+ gint index;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);
+ g_return_val_if_fail (GIMP_IS_ITEM (item), FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ index = gimp_item_get_index (item);
+
+ g_return_val_if_fail (index != -1, FALSE);
+
+ if (index == 0)
+ {
+ g_set_error_literal (error, GIMP_ERROR, GIMP_FAILED,
+ GIMP_ITEM_GET_CLASS (item)->raise_failed);
+ return FALSE;
+ }
+
+ return gimp_image_reorder_item (image, item,
+ gimp_item_get_parent (item), index - 1,
+ TRUE, GIMP_ITEM_GET_CLASS (item)->raise_desc);
+}
+
+gboolean
+gimp_image_raise_item_to_top (GimpImage *image,
+ GimpItem *item)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);
+ g_return_val_if_fail (GIMP_IS_ITEM (item), FALSE);
+
+ return gimp_image_reorder_item (image, item,
+ gimp_item_get_parent (item), 0,
+ TRUE, GIMP_ITEM_GET_CLASS (item)->raise_to_top_desc);
+}
+
+gboolean
+gimp_image_lower_item (GimpImage *image,
+ GimpItem *item,
+ GError **error)
+{
+ GimpContainer *container;
+ gint index;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);
+ g_return_val_if_fail (GIMP_IS_ITEM (item), FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ container = gimp_item_get_container (item);
+
+ g_return_val_if_fail (container != NULL, FALSE);
+
+ index = gimp_item_get_index (item);
+
+ if (index == gimp_container_get_n_children (container) - 1)
+ {
+ g_set_error_literal (error, GIMP_ERROR, GIMP_FAILED,
+ GIMP_ITEM_GET_CLASS (item)->lower_failed);
+ return FALSE;
+ }
+
+ return gimp_image_reorder_item (image, item,
+ gimp_item_get_parent (item), index + 1,
+ TRUE, GIMP_ITEM_GET_CLASS (item)->lower_desc);
+}
+
+gboolean
+gimp_image_lower_item_to_bottom (GimpImage *image,
+ GimpItem *item)
+{
+ GimpContainer *container;
+ gint length;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);
+ g_return_val_if_fail (GIMP_IS_ITEM (item), FALSE);
+
+ container = gimp_item_get_container (item);
+
+ g_return_val_if_fail (container != NULL, FALSE);
+
+ length = gimp_container_get_n_children (container);
+
+ return gimp_image_reorder_item (image, item,
+ gimp_item_get_parent (item), length - 1,
+ TRUE, GIMP_ITEM_GET_CLASS (item)->lower_to_bottom_desc);
+}
+
+
+/* layers */
+
+gboolean
+gimp_image_add_layer (GimpImage *image,
+ GimpLayer *layer,
+ GimpLayer *parent,
+ gint position,
+ gboolean push_undo)
+{
+ GimpImagePrivate *private;
+ gboolean old_has_alpha;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);
+
+ private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ /* item and parent are type-checked in GimpItemTree
+ */
+ if (! gimp_item_tree_get_insert_pos (private->layers,
+ (GimpItem *) layer,
+ (GimpItem **) &parent,
+ &position))
+ return FALSE;
+
+ gimp_image_unset_default_new_layer_mode (image);
+
+ /* If there is a floating selection (and this isn't it!),
+ * make sure the insert position is greater than 0
+ */
+ if (parent == NULL && position == 0 &&
+ gimp_image_get_floating_selection (image))
+ position = 1;
+
+ old_has_alpha = gimp_image_has_alpha (image);
+
+ if (push_undo)
+ gimp_image_undo_push_layer_add (image, C_("undo-type", "Add Layer"),
+ layer,
+ gimp_image_get_active_layer (image));
+
+ gimp_item_tree_add_item (private->layers, GIMP_ITEM (layer),
+ GIMP_ITEM (parent), position);
+
+ gimp_image_set_active_layer (image, layer);
+
+ /* If the layer is a floating selection, attach it to the drawable */
+ if (gimp_layer_is_floating_sel (layer))
+ gimp_drawable_attach_floating_sel (gimp_layer_get_floating_sel_drawable (layer),
+ layer);
+
+ if (old_has_alpha != gimp_image_has_alpha (image))
+ private->flush_accum.alpha_changed = TRUE;
+
+ return TRUE;
+}
+
+void
+gimp_image_remove_layer (GimpImage *image,
+ GimpLayer *layer,
+ gboolean push_undo,
+ GimpLayer *new_active)
+{
+ GimpImagePrivate *private;
+ GimpLayer *active_layer;
+ gboolean old_has_alpha;
+ const gchar *undo_desc;
+
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+ g_return_if_fail (GIMP_IS_LAYER (layer));
+ g_return_if_fail (gimp_item_is_attached (GIMP_ITEM (layer)));
+ g_return_if_fail (gimp_item_get_image (GIMP_ITEM (layer)) == image);
+
+ private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ gimp_image_unset_default_new_layer_mode (image);
+
+ if (push_undo)
+ gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_IMAGE_ITEM_REMOVE,
+ C_("undo-type", "Remove Layer"));
+
+ gimp_item_start_move (GIMP_ITEM (layer), push_undo);
+
+ if (gimp_drawable_get_floating_sel (GIMP_DRAWABLE (layer)))
+ {
+ if (! push_undo)
+ {
+ g_warning ("%s() was called from an undo function while the layer "
+ "had a floating selection. Please report this at "
+ "https://www.gimp.org/bugs/", G_STRFUNC);
+ return;
+ }
+
+ gimp_image_remove_layer (image,
+ gimp_drawable_get_floating_sel (GIMP_DRAWABLE (layer)),
+ TRUE, NULL);
+ }
+
+ active_layer = gimp_image_get_active_layer (image);
+
+ old_has_alpha = gimp_image_has_alpha (image);
+
+ if (gimp_layer_is_floating_sel (layer))
+ {
+ undo_desc = C_("undo-type", "Remove Floating Selection");
+
+ gimp_drawable_detach_floating_sel (gimp_layer_get_floating_sel_drawable (layer));
+ }
+ else
+ {
+ undo_desc = C_("undo-type", "Remove Layer");
+ }
+
+ if (push_undo)
+ gimp_image_undo_push_layer_remove (image, undo_desc, layer,
+ gimp_layer_get_parent (layer),
+ gimp_item_get_index (GIMP_ITEM (layer)),
+ active_layer);
+
+ g_object_ref (layer);
+
+ /* Make sure we're not caching any old selection info */
+ if (layer == active_layer)
+ gimp_drawable_invalidate_boundary (GIMP_DRAWABLE (layer));
+
+ private->layer_stack = g_slist_remove (private->layer_stack, layer);
+
+ /* Also remove all children of a group layer from the layer_stack */
+ if (gimp_viewable_get_children (GIMP_VIEWABLE (layer)))
+ {
+ GimpContainer *stack = gimp_viewable_get_children (GIMP_VIEWABLE (layer));
+ GList *children;
+ GList *list;
+
+ children = gimp_item_stack_get_item_list (GIMP_ITEM_STACK (stack));
+
+ for (list = children; list; list = g_list_next (list))
+ {
+ private->layer_stack = g_slist_remove (private->layer_stack,
+ list->data);
+ }
+
+ g_list_free (children);
+ }
+
+ new_active =
+ GIMP_LAYER (gimp_item_tree_remove_item (private->layers,
+ GIMP_ITEM (layer),
+ GIMP_ITEM (new_active)));
+
+ if (gimp_layer_is_floating_sel (layer))
+ {
+ /* If this was the floating selection, activate the underlying drawable
+ */
+ floating_sel_activate_drawable (layer);
+ }
+ else if (active_layer &&
+ (layer == active_layer ||
+ gimp_viewable_is_ancestor (GIMP_VIEWABLE (layer),
+ GIMP_VIEWABLE (active_layer))))
+ {
+ gimp_image_set_active_layer (image, new_active);
+ }
+
+ gimp_item_end_move (GIMP_ITEM (layer), push_undo);
+
+ g_object_unref (layer);
+
+ if (old_has_alpha != gimp_image_has_alpha (image))
+ private->flush_accum.alpha_changed = TRUE;
+
+ if (push_undo)
+ gimp_image_undo_group_end (image);
+}
+
+void
+gimp_image_add_layers (GimpImage *image,
+ GList *layers,
+ GimpLayer *parent,
+ gint position,
+ gint x,
+ gint y,
+ gint width,
+ gint height,
+ const gchar *undo_desc)
+{
+ GimpImagePrivate *private;
+ GList *list;
+ gint layers_x = G_MAXINT;
+ gint layers_y = G_MAXINT;
+ gint layers_width = 0;
+ gint layers_height = 0;
+ gint offset_x;
+ gint offset_y;
+
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+ g_return_if_fail (layers != NULL);
+
+ private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ /* item and parent are type-checked in GimpItemTree
+ */
+ if (! gimp_item_tree_get_insert_pos (private->layers,
+ (GimpItem *) layers->data,
+ (GimpItem **) &parent,
+ &position))
+ return;
+
+ for (list = layers; list; list = g_list_next (list))
+ {
+ GimpItem *item = GIMP_ITEM (list->data);
+ gint off_x, off_y;
+
+ gimp_item_get_offset (item, &off_x, &off_y);
+
+ layers_x = MIN (layers_x, off_x);
+ layers_y = MIN (layers_y, off_y);
+
+ layers_width = MAX (layers_width,
+ off_x + gimp_item_get_width (item) - layers_x);
+ layers_height = MAX (layers_height,
+ off_y + gimp_item_get_height (item) - layers_y);
+ }
+
+ offset_x = x + (width - layers_width) / 2 - layers_x;
+ offset_y = y + (height - layers_height) / 2 - layers_y;
+
+ gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_LAYER_ADD, undo_desc);
+
+ for (list = layers; list; list = g_list_next (list))
+ {
+ GimpItem *new_item = GIMP_ITEM (list->data);
+
+ gimp_item_translate (new_item, offset_x, offset_y, FALSE);
+
+ gimp_image_add_layer (image, GIMP_LAYER (new_item),
+ parent, position, TRUE);
+ position++;
+ }
+
+ if (layers)
+ gimp_image_set_active_layer (image, layers->data);
+
+ gimp_image_undo_group_end (image);
+}
+
+
+/* channels */
+
+gboolean
+gimp_image_add_channel (GimpImage *image,
+ GimpChannel *channel,
+ GimpChannel *parent,
+ gint position,
+ gboolean push_undo)
+{
+ GimpImagePrivate *private;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);
+
+ private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ /* item and parent are type-checked in GimpItemTree
+ */
+ if (! gimp_item_tree_get_insert_pos (private->channels,
+ (GimpItem *) channel,
+ (GimpItem **) &parent,
+ &position))
+ return FALSE;
+
+ if (push_undo)
+ gimp_image_undo_push_channel_add (image, C_("undo-type", "Add Channel"),
+ channel,
+ gimp_image_get_active_channel (image));
+
+ gimp_item_tree_add_item (private->channels, GIMP_ITEM (channel),
+ GIMP_ITEM (parent), position);
+
+ gimp_image_set_active_channel (image, channel);
+
+ return TRUE;
+}
+
+void
+gimp_image_remove_channel (GimpImage *image,
+ GimpChannel *channel,
+ gboolean push_undo,
+ GimpChannel *new_active)
+{
+ GimpImagePrivate *private;
+ GimpChannel *active_channel;
+
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+ g_return_if_fail (GIMP_IS_CHANNEL (channel));
+ g_return_if_fail (gimp_item_is_attached (GIMP_ITEM (channel)));
+ g_return_if_fail (gimp_item_get_image (GIMP_ITEM (channel)) == image);
+
+ if (push_undo)
+ gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_IMAGE_ITEM_REMOVE,
+ C_("undo-type", "Remove Channel"));
+
+ gimp_item_start_move (GIMP_ITEM (channel), push_undo);
+
+ if (gimp_drawable_get_floating_sel (GIMP_DRAWABLE (channel)))
+ {
+ if (! push_undo)
+ {
+ g_warning ("%s() was called from an undo function while the channel "
+ "had a floating selection. Please report this at "
+ "https://www.gimp.org/bugs/", G_STRFUNC);
+ return;
+ }
+
+ gimp_image_remove_layer (image,
+ gimp_drawable_get_floating_sel (GIMP_DRAWABLE (channel)),
+ TRUE, NULL);
+ }
+
+ private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ active_channel = gimp_image_get_active_channel (image);
+
+ if (push_undo)
+ gimp_image_undo_push_channel_remove (image, C_("undo-type", "Remove Channel"), channel,
+ gimp_channel_get_parent (channel),
+ gimp_item_get_index (GIMP_ITEM (channel)),
+ active_channel);
+
+ g_object_ref (channel);
+
+ new_active =
+ GIMP_CHANNEL (gimp_item_tree_remove_item (private->channels,
+ GIMP_ITEM (channel),
+ GIMP_ITEM (new_active)));
+
+ if (active_channel &&
+ (channel == active_channel ||
+ gimp_viewable_is_ancestor (GIMP_VIEWABLE (channel),
+ GIMP_VIEWABLE (active_channel))))
+ {
+ if (new_active)
+ gimp_image_set_active_channel (image, new_active);
+ else
+ gimp_image_unset_active_channel (image);
+ }
+
+ gimp_item_end_move (GIMP_ITEM (channel), push_undo);
+
+ g_object_unref (channel);
+
+ if (push_undo)
+ gimp_image_undo_group_end (image);
+}
+
+
+/* vectors */
+
+gboolean
+gimp_image_add_vectors (GimpImage *image,
+ GimpVectors *vectors,
+ GimpVectors *parent,
+ gint position,
+ gboolean push_undo)
+{
+ GimpImagePrivate *private;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);
+
+ private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ /* item and parent are type-checked in GimpItemTree
+ */
+ if (! gimp_item_tree_get_insert_pos (private->vectors,
+ (GimpItem *) vectors,
+ (GimpItem **) &parent,
+ &position))
+ return FALSE;
+
+ if (push_undo)
+ gimp_image_undo_push_vectors_add (image, C_("undo-type", "Add Path"),
+ vectors,
+ gimp_image_get_active_vectors (image));
+
+ gimp_item_tree_add_item (private->vectors, GIMP_ITEM (vectors),
+ GIMP_ITEM (parent), position);
+
+ gimp_image_set_active_vectors (image, vectors);
+
+ return TRUE;
+}
+
+void
+gimp_image_remove_vectors (GimpImage *image,
+ GimpVectors *vectors,
+ gboolean push_undo,
+ GimpVectors *new_active)
+{
+ GimpImagePrivate *private;
+ GimpVectors *active_vectors;
+
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+ g_return_if_fail (GIMP_IS_VECTORS (vectors));
+ g_return_if_fail (gimp_item_is_attached (GIMP_ITEM (vectors)));
+ g_return_if_fail (gimp_item_get_image (GIMP_ITEM (vectors)) == image);
+
+ private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ if (push_undo)
+ gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_IMAGE_ITEM_REMOVE,
+ C_("undo-type", "Remove Path"));
+
+ gimp_item_start_move (GIMP_ITEM (vectors), push_undo);
+
+ active_vectors = gimp_image_get_active_vectors (image);
+
+ if (push_undo)
+ gimp_image_undo_push_vectors_remove (image, C_("undo-type", "Remove Path"), vectors,
+ gimp_vectors_get_parent (vectors),
+ gimp_item_get_index (GIMP_ITEM (vectors)),
+ active_vectors);
+
+ g_object_ref (vectors);
+
+ new_active =
+ GIMP_VECTORS (gimp_item_tree_remove_item (private->vectors,
+ GIMP_ITEM (vectors),
+ GIMP_ITEM (new_active)));
+
+ if (active_vectors &&
+ (vectors == active_vectors ||
+ gimp_viewable_is_ancestor (GIMP_VIEWABLE (vectors),
+ GIMP_VIEWABLE (active_vectors))))
+ {
+ gimp_image_set_active_vectors (image, new_active);
+ }
+
+ gimp_item_end_move (GIMP_ITEM (vectors), push_undo);
+
+ g_object_unref (vectors);
+
+ if (push_undo)
+ gimp_image_undo_group_end (image);
+}
+
+gboolean
+gimp_image_coords_in_active_pickable (GimpImage *image,
+ const GimpCoords *coords,
+ gboolean show_all,
+ gboolean sample_merged,
+ gboolean selected_only)
+{
+ gint x, y;
+ gboolean in_pickable = FALSE;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);
+
+ x = floor (coords->x);
+ y = floor (coords->y);
+
+ if (sample_merged)
+ {
+ if (show_all || (x >= 0 && x < gimp_image_get_width (image) &&
+ y >= 0 && y < gimp_image_get_height (image)))
+ {
+ in_pickable = TRUE;
+ }
+ }
+ else
+ {
+ GimpDrawable *drawable = gimp_image_get_active_drawable (image);
+
+ if (drawable)
+ {
+ GimpItem *item = GIMP_ITEM (drawable);
+ gint off_x, off_y;
+ gint d_x, d_y;
+
+ gimp_item_get_offset (item, &off_x, &off_y);
+
+ d_x = x - off_x;
+ d_y = y - off_y;
+
+ if (d_x >= 0 && d_x < gimp_item_get_width (item) &&
+ d_y >= 0 && d_y < gimp_item_get_height (item))
+ in_pickable = TRUE;
+ }
+ }
+
+ if (in_pickable && selected_only)
+ {
+ GimpChannel *selection = gimp_image_get_mask (image);
+
+ if (! gimp_channel_is_empty (selection) &&
+ ! gimp_pickable_get_opacity_at (GIMP_PICKABLE (selection),
+ x, y))
+ {
+ in_pickable = FALSE;
+ }
+ }
+
+ return in_pickable;
+}
+
+void
+gimp_image_invalidate_previews (GimpImage *image)
+{
+ GimpItemStack *layers;
+ GimpItemStack *channels;
+
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+
+ layers = GIMP_ITEM_STACK (gimp_image_get_layers (image));
+ channels = GIMP_ITEM_STACK (gimp_image_get_channels (image));
+
+ gimp_item_stack_invalidate_previews (layers);
+ gimp_item_stack_invalidate_previews (channels);
+}
+
+/* Sets the image into a "converting" state, which is there to warn other code
+ * (such as shell render code) that the image properties might be in an
+ * inconsistent state. For instance when converting to another precision with
+ * gimp_image_convert_precision(), the babl format may be updated first, and the
+ * profile later, after all drawables are converted. Rendering the image
+ * in-between would at best render broken previews (at worst, crash, e.g.
+ * because we depend on allocated data which might have become too small).
+ */
+void
+gimp_image_set_converting (GimpImage *image,
+ gboolean converting)
+{
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+
+ g_object_set (image,
+ "converting", converting,
+ NULL);
+}
+
+gboolean
+gimp_image_get_converting (GimpImage *image)
+{
+ GimpImagePrivate *private;
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);
+
+ private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ return private->converting;
+}
diff --git a/app/core/gimpimage.h b/app/core/gimpimage.h
new file mode 100644
index 0000000..50654ab
--- /dev/null
+++ b/app/core/gimpimage.h
@@ -0,0 +1,463 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattisbvf
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_IMAGE_H__
+#define __GIMP_IMAGE_H__
+
+
+#include "gimpviewable.h"
+
+
+#define GIMP_IMAGE_ACTIVE_PARENT ((gpointer) 1)
+
+
+#define GIMP_TYPE_IMAGE (gimp_image_get_type ())
+#define GIMP_IMAGE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_IMAGE, GimpImage))
+#define GIMP_IMAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_IMAGE, GimpImageClass))
+#define GIMP_IS_IMAGE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_IMAGE))
+#define GIMP_IS_IMAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_IMAGE))
+#define GIMP_IMAGE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_IMAGE, GimpImageClass))
+
+
+typedef struct _GimpImageClass GimpImageClass;
+typedef struct _GimpImagePrivate GimpImagePrivate;
+
+struct _GimpImage
+{
+ GimpViewable parent_instance;
+
+ Gimp *gimp; /* the GIMP the image belongs to */
+
+ GimpImagePrivate *priv;
+};
+
+struct _GimpImageClass
+{
+ GimpViewableClass parent_class;
+
+ /* signals */
+ void (* mode_changed) (GimpImage *image);
+ void (* precision_changed) (GimpImage *image);
+ void (* alpha_changed) (GimpImage *image);
+ void (* floating_selection_changed) (GimpImage *image);
+ void (* active_layer_changed) (GimpImage *image);
+ void (* active_channel_changed) (GimpImage *image);
+ void (* active_vectors_changed) (GimpImage *image);
+ void (* linked_items_changed) (GimpImage *image);
+ void (* component_visibility_changed) (GimpImage *image,
+ GimpChannelType channel);
+ void (* component_active_changed) (GimpImage *image,
+ GimpChannelType channel);
+ void (* mask_changed) (GimpImage *image);
+ void (* resolution_changed) (GimpImage *image);
+ void (* size_changed_detailed) (GimpImage *image,
+ gint previous_origin_x,
+ gint previous_origin_y,
+ gint previous_width,
+ gint previous_height);
+ void (* unit_changed) (GimpImage *image);
+ void (* quick_mask_changed) (GimpImage *image);
+ void (* selection_invalidate) (GimpImage *image);
+
+ void (* clean) (GimpImage *image,
+ GimpDirtyMask dirty_mask);
+ void (* dirty) (GimpImage *image,
+ GimpDirtyMask dirty_mask);
+ void (* saving) (GimpImage *image);
+ void (* saved) (GimpImage *image,
+ GFile *file);
+ void (* exported) (GimpImage *image,
+ GFile *file);
+
+ void (* guide_added) (GimpImage *image,
+ GimpGuide *guide);
+ void (* guide_removed) (GimpImage *image,
+ GimpGuide *guide);
+ void (* guide_moved) (GimpImage *image,
+ GimpGuide *guide);
+ void (* sample_point_added) (GimpImage *image,
+ GimpSamplePoint *sample_point);
+ void (* sample_point_removed) (GimpImage *image,
+ GimpSamplePoint *sample_point);
+ void (* sample_point_moved) (GimpImage *image,
+ GimpSamplePoint *sample_point);
+ void (* parasite_attached) (GimpImage *image,
+ const gchar *name);
+ void (* parasite_detached) (GimpImage *image,
+ const gchar *name);
+ void (* colormap_changed) (GimpImage *image,
+ gint color_index);
+ void (* undo_event) (GimpImage *image,
+ GimpUndoEvent event,
+ GimpUndo *undo);
+};
+
+
+GType gimp_image_get_type (void) G_GNUC_CONST;
+
+GimpImage * gimp_image_new (Gimp *gimp,
+ gint width,
+ gint height,
+ GimpImageBaseType base_type,
+ GimpPrecision precision);
+
+gint64 gimp_image_estimate_memsize (GimpImage *image,
+ GimpComponentType component_type,
+ gint width,
+ gint height);
+
+GimpImageBaseType gimp_image_get_base_type (GimpImage *image);
+GimpComponentType gimp_image_get_component_type (GimpImage *image);
+GimpPrecision gimp_image_get_precision (GimpImage *image);
+
+const Babl * gimp_image_get_format (GimpImage *image,
+ GimpImageBaseType base_type,
+ GimpPrecision precision,
+ gboolean with_alpha);
+const Babl * gimp_image_get_layer_format (GimpImage *image,
+ gboolean with_alpha);
+const Babl * gimp_image_get_channel_format (GimpImage *image);
+const Babl * gimp_image_get_mask_format (GimpImage *image);
+
+GimpLayerMode gimp_image_get_default_new_layer_mode
+ (GimpImage *image);
+void gimp_image_unset_default_new_layer_mode
+ (GimpImage *image);
+
+gint gimp_image_get_ID (GimpImage *image);
+GimpImage * gimp_image_get_by_ID (Gimp *gimp,
+ gint id);
+
+GFile * gimp_image_get_file (GimpImage *image);
+GFile * gimp_image_get_untitled_file (GimpImage *image);
+GFile * gimp_image_get_file_or_untitled (GimpImage *image);
+GFile * gimp_image_get_imported_file (GimpImage *image);
+GFile * gimp_image_get_exported_file (GimpImage *image);
+GFile * gimp_image_get_save_a_copy_file (GimpImage *image);
+GFile * gimp_image_get_any_file (GimpImage *image);
+
+void gimp_image_set_file (GimpImage *image,
+ GFile *file);
+void gimp_image_set_imported_file (GimpImage *image,
+ GFile *file);
+void gimp_image_set_exported_file (GimpImage *image,
+ GFile *file);
+void gimp_image_set_save_a_copy_file (GimpImage *image,
+ GFile *file);
+
+const gchar * gimp_image_get_display_name (GimpImage *image);
+const gchar * gimp_image_get_display_path (GimpImage *image);
+
+void gimp_image_set_load_proc (GimpImage *image,
+ GimpPlugInProcedure *proc);
+GimpPlugInProcedure * gimp_image_get_load_proc (GimpImage *image);
+void gimp_image_set_save_proc (GimpImage *image,
+ GimpPlugInProcedure *proc);
+GimpPlugInProcedure * gimp_image_get_save_proc (GimpImage *image);
+void gimp_image_saving (GimpImage *image);
+void gimp_image_saved (GimpImage *image,
+ GFile *file);
+void gimp_image_set_export_proc (GimpImage *image,
+ GimpPlugInProcedure *proc);
+GimpPlugInProcedure * gimp_image_get_export_proc (GimpImage *image);
+void gimp_image_exported (GimpImage *image,
+ GFile *file);
+
+gint gimp_image_get_xcf_version (GimpImage *image,
+ gboolean zlib_compression,
+ gint *gimp_version,
+ const gchar **version_string,
+ gchar **version_reason);
+
+void gimp_image_set_xcf_compression (GimpImage *image,
+ gboolean compression);
+gboolean gimp_image_get_xcf_compression (GimpImage *image);
+
+void gimp_image_set_resolution (GimpImage *image,
+ gdouble xres,
+ gdouble yres);
+void gimp_image_get_resolution (GimpImage *image,
+ gdouble *xres,
+ gdouble *yres);
+void gimp_image_resolution_changed (GimpImage *image);
+
+void gimp_image_set_unit (GimpImage *image,
+ GimpUnit unit);
+GimpUnit gimp_image_get_unit (GimpImage *image);
+void gimp_image_unit_changed (GimpImage *image);
+
+gint gimp_image_get_width (GimpImage *image);
+gint gimp_image_get_height (GimpImage *image);
+
+gboolean gimp_image_has_alpha (GimpImage *image);
+gboolean gimp_image_is_empty (GimpImage *image);
+
+void gimp_image_set_floating_selection (GimpImage *image,
+ GimpLayer *floating_sel);
+GimpLayer * gimp_image_get_floating_selection (GimpImage *image);
+void gimp_image_floating_selection_changed (GimpImage *image);
+
+GimpChannel * gimp_image_get_mask (GimpImage *image);
+void gimp_image_mask_changed (GimpImage *image);
+
+
+/* image components */
+
+const Babl * gimp_image_get_component_format (GimpImage *image,
+ GimpChannelType channel);
+gint gimp_image_get_component_index (GimpImage *image,
+ GimpChannelType channel);
+
+void gimp_image_set_component_active (GimpImage *image,
+ GimpChannelType type,
+ gboolean active);
+gboolean gimp_image_get_component_active (GimpImage *image,
+ GimpChannelType type);
+void gimp_image_get_active_array (GimpImage *image,
+ gboolean *components);
+GimpComponentMask gimp_image_get_active_mask (GimpImage *image);
+
+void gimp_image_set_component_visible (GimpImage *image,
+ GimpChannelType type,
+ gboolean visible);
+gboolean gimp_image_get_component_visible (GimpImage *image,
+ GimpChannelType type);
+void gimp_image_get_visible_array (GimpImage *image,
+ gboolean *components);
+GimpComponentMask gimp_image_get_visible_mask (GimpImage *image);
+
+
+/* emitting image signals */
+
+void gimp_image_mode_changed (GimpImage *image);
+void gimp_image_precision_changed (GimpImage *image);
+void gimp_image_alpha_changed (GimpImage *image);
+void gimp_image_linked_items_changed (GimpImage *image);
+void gimp_image_invalidate (GimpImage *image,
+ gint x,
+ gint y,
+ gint width,
+ gint height);
+void gimp_image_invalidate_all (GimpImage *image);
+void gimp_image_guide_added (GimpImage *image,
+ GimpGuide *guide);
+void gimp_image_guide_removed (GimpImage *image,
+ GimpGuide *guide);
+void gimp_image_guide_moved (GimpImage *image,
+ GimpGuide *guide);
+
+void gimp_image_sample_point_added (GimpImage *image,
+ GimpSamplePoint *sample_point);
+void gimp_image_sample_point_removed (GimpImage *image,
+ GimpSamplePoint *sample_point);
+void gimp_image_sample_point_moved (GimpImage *image,
+ GimpSamplePoint *sample_point);
+void gimp_image_colormap_changed (GimpImage *image,
+ gint col);
+void gimp_image_selection_invalidate (GimpImage *image);
+void gimp_image_quick_mask_changed (GimpImage *image);
+void gimp_image_size_changed_detailed (GimpImage *image,
+ gint previous_origin_x,
+ gint previous_origin_y,
+ gint previous_width,
+ gint previous_height);
+void gimp_image_undo_event (GimpImage *image,
+ GimpUndoEvent event,
+ GimpUndo *undo);
+
+
+/* dirty counters */
+
+gint gimp_image_dirty (GimpImage *image,
+ GimpDirtyMask dirty_mask);
+gint gimp_image_clean (GimpImage *image,
+ GimpDirtyMask dirty_mask);
+void gimp_image_clean_all (GimpImage *image);
+void gimp_image_export_clean_all (GimpImage *image);
+gint gimp_image_is_dirty (GimpImage *image);
+gboolean gimp_image_is_export_dirty (GimpImage *image);
+gint64 gimp_image_get_dirty_time (GimpImage *image);
+
+
+/* flush this image's displays */
+
+void gimp_image_flush (GimpImage *image);
+
+
+/* display / instance counters */
+
+gint gimp_image_get_display_count (GimpImage *image);
+void gimp_image_inc_display_count (GimpImage *image);
+void gimp_image_dec_display_count (GimpImage *image);
+
+gint gimp_image_get_instance_count (GimpImage *image);
+void gimp_image_inc_instance_count (GimpImage *image);
+
+void gimp_image_inc_show_all_count (GimpImage *image);
+void gimp_image_dec_show_all_count (GimpImage *image);
+
+
+/* parasites */
+
+const GimpParasite * gimp_image_parasite_find (GimpImage *image,
+ const gchar *name);
+gchar ** gimp_image_parasite_list (GimpImage *image,
+ gint *count);
+gboolean gimp_image_parasite_validate (GimpImage *image,
+ const GimpParasite *parasite,
+ GError **error);
+void gimp_image_parasite_attach (GimpImage *image,
+ const GimpParasite *parasite,
+ gboolean push_undo);
+void gimp_image_parasite_detach (GimpImage *image,
+ const gchar *name,
+ gboolean push_undo);
+
+
+/* tattoos */
+
+GimpTattoo gimp_image_get_new_tattoo (GimpImage *image);
+gboolean gimp_image_set_tattoo_state (GimpImage *image,
+ GimpTattoo val);
+GimpTattoo gimp_image_get_tattoo_state (GimpImage *image);
+
+
+/* projection */
+
+GimpProjection * gimp_image_get_projection (GimpImage *image);
+
+
+/* layers / channels / vectors */
+
+GimpItemTree * gimp_image_get_layer_tree (GimpImage *image);
+GimpItemTree * gimp_image_get_channel_tree (GimpImage *image);
+GimpItemTree * gimp_image_get_vectors_tree (GimpImage *image);
+
+GimpContainer * gimp_image_get_layers (GimpImage *image);
+GimpContainer * gimp_image_get_channels (GimpImage *image);
+GimpContainer * gimp_image_get_vectors (GimpImage *image);
+
+gint gimp_image_get_n_layers (GimpImage *image);
+gint gimp_image_get_n_channels (GimpImage *image);
+gint gimp_image_get_n_vectors (GimpImage *image);
+
+GList * gimp_image_get_layer_iter (GimpImage *image);
+GList * gimp_image_get_channel_iter (GimpImage *image);
+GList * gimp_image_get_vectors_iter (GimpImage *image);
+
+GList * gimp_image_get_layer_list (GimpImage *image);
+GList * gimp_image_get_channel_list (GimpImage *image);
+GList * gimp_image_get_vectors_list (GimpImage *image);
+
+GimpDrawable * gimp_image_get_active_drawable (GimpImage *image);
+GimpLayer * gimp_image_get_active_layer (GimpImage *image);
+GimpChannel * gimp_image_get_active_channel (GimpImage *image);
+GimpVectors * gimp_image_get_active_vectors (GimpImage *image);
+
+GimpLayer * gimp_image_set_active_layer (GimpImage *image,
+ GimpLayer *layer);
+GimpChannel * gimp_image_set_active_channel (GimpImage *image,
+ GimpChannel *channel);
+GimpChannel * gimp_image_unset_active_channel (GimpImage *image);
+GimpVectors * gimp_image_set_active_vectors (GimpImage *image,
+ GimpVectors *vectors);
+
+GimpLayer * gimp_image_get_layer_by_tattoo (GimpImage *image,
+ GimpTattoo tattoo);
+GimpChannel * gimp_image_get_channel_by_tattoo (GimpImage *image,
+ GimpTattoo tattoo);
+GimpVectors * gimp_image_get_vectors_by_tattoo (GimpImage *image,
+ GimpTattoo tattoo);
+
+GimpLayer * gimp_image_get_layer_by_name (GimpImage *image,
+ const gchar *name);
+GimpChannel * gimp_image_get_channel_by_name (GimpImage *image,
+ const gchar *name);
+GimpVectors * gimp_image_get_vectors_by_name (GimpImage *image,
+ const gchar *name);
+
+gboolean gimp_image_reorder_item (GimpImage *image,
+ GimpItem *item,
+ GimpItem *new_parent,
+ gint new_index,
+ gboolean push_undo,
+ const gchar *undo_desc);
+gboolean gimp_image_raise_item (GimpImage *image,
+ GimpItem *item,
+ GError **error);
+gboolean gimp_image_raise_item_to_top (GimpImage *image,
+ GimpItem *item);
+gboolean gimp_image_lower_item (GimpImage *image,
+ GimpItem *item,
+ GError **error);
+gboolean gimp_image_lower_item_to_bottom (GimpImage *image,
+ GimpItem *item);
+
+gboolean gimp_image_add_layer (GimpImage *image,
+ GimpLayer *layer,
+ GimpLayer *parent,
+ gint position,
+ gboolean push_undo);
+void gimp_image_remove_layer (GimpImage *image,
+ GimpLayer *layer,
+ gboolean push_undo,
+ GimpLayer *new_active);
+
+void gimp_image_add_layers (GimpImage *image,
+ GList *layers,
+ GimpLayer *parent,
+ gint position,
+ gint x,
+ gint y,
+ gint width,
+ gint height,
+ const gchar *undo_desc);
+
+gboolean gimp_image_add_channel (GimpImage *image,
+ GimpChannel *channel,
+ GimpChannel *parent,
+ gint position,
+ gboolean push_undo);
+void gimp_image_remove_channel (GimpImage *image,
+ GimpChannel *channel,
+ gboolean push_undo,
+ GimpChannel *new_active);
+
+gboolean gimp_image_add_vectors (GimpImage *image,
+ GimpVectors *vectors,
+ GimpVectors *parent,
+ gint position,
+ gboolean push_undo);
+void gimp_image_remove_vectors (GimpImage *image,
+ GimpVectors *vectors,
+ gboolean push_undo,
+ GimpVectors *new_active);
+
+gboolean gimp_image_coords_in_active_pickable (GimpImage *image,
+ const GimpCoords *coords,
+ gboolean show_all,
+ gboolean sample_merged,
+ gboolean selected_only);
+
+void gimp_image_invalidate_previews (GimpImage *image);
+
+void gimp_image_set_converting (GimpImage *image,
+ gboolean converting);
+gboolean gimp_image_get_converting (GimpImage *image);
+
+
+#endif /* __GIMP_IMAGE_H__ */
diff --git a/app/core/gimpimagefile.c b/app/core/gimpimagefile.c
new file mode 100644
index 0000000..cf079d8
--- /dev/null
+++ b/app/core/gimpimagefile.c
@@ -0,0 +1,1078 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpimagefile.c
+ *
+ * Copyright (C) 2001-2004 Sven Neumann <sven@gimp.org>
+ * Michael Natterer <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gegl.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+#include "libgimpbase/gimpbase.h"
+#include "libgimpthumb/gimpthumb.h"
+
+#include "core-types.h"
+
+#include "config/gimpcoreconfig.h"
+
+#include "gegl/gimp-babl.h"
+#include "gegl/gimp-gegl-utils.h"
+
+#include "gimp.h"
+#include "gimpcontainer.h"
+#include "gimpcontext.h"
+#include "gimpimage.h"
+#include "gimpimagefile.h"
+#include "gimpmarshal.h"
+#include "gimppickable.h"
+#include "gimpprogress.h"
+
+#include "file/file-open.h"
+
+#include "gimp-intl.h"
+
+
+enum
+{
+ INFO_CHANGED,
+ LAST_SIGNAL
+};
+
+
+typedef struct _GimpImagefilePrivate GimpImagefilePrivate;
+
+struct _GimpImagefilePrivate
+{
+ Gimp *gimp;
+
+ GFile *file;
+ GimpThumbnail *thumbnail;
+ GIcon *icon;
+ GCancellable *icon_cancellable;
+
+ gchar *description;
+ gboolean static_desc;
+};
+
+#define GET_PRIVATE(imagefile) ((GimpImagefilePrivate *) gimp_imagefile_get_instance_private ((GimpImagefile *) (imagefile)))
+
+
+static void gimp_imagefile_dispose (GObject *object);
+static void gimp_imagefile_finalize (GObject *object);
+
+static void gimp_imagefile_name_changed (GimpObject *object);
+
+static GdkPixbuf * gimp_imagefile_get_new_pixbuf (GimpViewable *viewable,
+ GimpContext *context,
+ gint width,
+ gint height);
+static gchar * gimp_imagefile_get_description (GimpViewable *viewable,
+ gchar **tooltip);
+
+static void gimp_imagefile_info_changed (GimpImagefile *imagefile);
+static void gimp_imagefile_notify_thumbnail (GimpImagefile *imagefile,
+ GParamSpec *pspec);
+
+static void gimp_imagefile_icon_callback (GObject *source_object,
+ GAsyncResult *result,
+ gpointer data);
+
+static GdkPixbuf * gimp_imagefile_load_thumb (GimpImagefile *imagefile,
+ gint width,
+ gint height);
+static gboolean gimp_imagefile_save_thumb (GimpImagefile *imagefile,
+ GimpImage *image,
+ gint size,
+ gboolean replace,
+ GError **error);
+
+static void gimp_thumbnail_set_info_from_image (GimpThumbnail *thumbnail,
+ const gchar *mime_type,
+ GimpImage *image);
+static void gimp_thumbnail_set_info (GimpThumbnail *thumbnail,
+ const gchar *mime_type,
+ gint width,
+ gint height,
+ const Babl *format,
+ gint num_layers);
+
+
+G_DEFINE_TYPE_WITH_PRIVATE (GimpImagefile, gimp_imagefile, GIMP_TYPE_VIEWABLE)
+
+#define parent_class gimp_imagefile_parent_class
+
+static guint gimp_imagefile_signals[LAST_SIGNAL] = { 0 };
+
+
+static void
+gimp_imagefile_class_init (GimpImagefileClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpObjectClass *gimp_object_class = GIMP_OBJECT_CLASS (klass);
+ GimpViewableClass *viewable_class = GIMP_VIEWABLE_CLASS (klass);
+ gchar *creator;
+
+ gimp_imagefile_signals[INFO_CHANGED] =
+ g_signal_new ("info-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpImagefileClass, info_changed),
+ NULL, NULL,
+ gimp_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ object_class->dispose = gimp_imagefile_dispose;
+ object_class->finalize = gimp_imagefile_finalize;
+
+ gimp_object_class->name_changed = gimp_imagefile_name_changed;
+
+ viewable_class->name_changed_signal = "info-changed";
+ viewable_class->get_new_pixbuf = gimp_imagefile_get_new_pixbuf;
+ viewable_class->get_description = gimp_imagefile_get_description;
+
+ g_type_class_ref (GIMP_TYPE_IMAGE_TYPE);
+
+ creator = g_strdup_printf ("gimp-%d.%d",
+ GIMP_MAJOR_VERSION, GIMP_MINOR_VERSION);
+
+ gimp_thumb_init (creator, NULL);
+
+ g_free (creator);
+}
+
+static void
+gimp_imagefile_init (GimpImagefile *imagefile)
+{
+ GimpImagefilePrivate *private = GET_PRIVATE (imagefile);
+
+ private->thumbnail = gimp_thumbnail_new ();
+
+ g_signal_connect_object (private->thumbnail, "notify",
+ G_CALLBACK (gimp_imagefile_notify_thumbnail),
+ imagefile, G_CONNECT_SWAPPED);
+}
+
+static void
+gimp_imagefile_dispose (GObject *object)
+{
+ GimpImagefilePrivate *private = GET_PRIVATE (object);
+
+ if (private->icon_cancellable)
+ {
+ g_cancellable_cancel (private->icon_cancellable);
+ g_clear_object (&private->icon_cancellable);
+ }
+
+ G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+gimp_imagefile_finalize (GObject *object)
+{
+ GimpImagefilePrivate *private = GET_PRIVATE (object);
+
+ if (private->description)
+ {
+ if (! private->static_desc)
+ g_free (private->description);
+
+ private->description = NULL;
+ }
+
+ g_clear_object (&private->thumbnail);
+ g_clear_object (&private->icon);
+ g_clear_object (&private->file);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gimp_imagefile_name_changed (GimpObject *object)
+{
+ GimpImagefilePrivate *private = GET_PRIVATE (object);
+
+ if (GIMP_OBJECT_CLASS (parent_class)->name_changed)
+ GIMP_OBJECT_CLASS (parent_class)->name_changed (object);
+
+ gimp_thumbnail_set_uri (private->thumbnail, gimp_object_get_name (object));
+
+ g_clear_object (&private->file);
+
+ if (gimp_object_get_name (object))
+ private->file = g_file_new_for_uri (gimp_object_get_name (object));
+}
+
+static GdkPixbuf *
+gimp_imagefile_get_new_pixbuf (GimpViewable *viewable,
+ GimpContext *context,
+ gint width,
+ gint height)
+{
+ GimpImagefile *imagefile = GIMP_IMAGEFILE (viewable);
+
+ if (! gimp_object_get_name (imagefile))
+ return NULL;
+
+ return gimp_imagefile_load_thumb (imagefile, width, height);
+}
+
+static gchar *
+gimp_imagefile_get_description (GimpViewable *viewable,
+ gchar **tooltip)
+{
+ GimpImagefile *imagefile = GIMP_IMAGEFILE (viewable);
+ GimpImagefilePrivate *private = GET_PRIVATE (imagefile);
+ GimpThumbnail *thumbnail = private->thumbnail;
+ gchar *basename;
+
+ if (! private->file)
+ return NULL;
+
+ if (tooltip)
+ {
+ const gchar *name;
+ const gchar *desc;
+
+ name = gimp_file_get_utf8_name (private->file);
+ desc = gimp_imagefile_get_desc_string (imagefile);
+
+ if (desc)
+ *tooltip = g_strdup_printf ("%s\n%s", name, desc);
+ else
+ *tooltip = g_strdup (name);
+ }
+
+ basename = g_path_get_basename (gimp_file_get_utf8_name (private->file));
+
+ if (thumbnail->image_width > 0 && thumbnail->image_height > 0)
+ {
+ gchar *tmp = basename;
+
+ basename = g_strdup_printf ("%s (%d × %d)",
+ tmp,
+ thumbnail->image_width,
+ thumbnail->image_height);
+ g_free (tmp);
+ }
+
+ return basename;
+}
+
+
+/* public functions */
+
+GimpImagefile *
+gimp_imagefile_new (Gimp *gimp,
+ GFile *file)
+{
+ GimpImagefile *imagefile;
+
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
+ g_return_val_if_fail (file == NULL || G_IS_FILE (file), NULL);
+
+ imagefile = g_object_new (GIMP_TYPE_IMAGEFILE, NULL);
+
+ GET_PRIVATE (imagefile)->gimp = gimp;
+
+ if (file)
+ {
+ gimp_object_take_name (GIMP_OBJECT (imagefile), g_file_get_uri (file));
+
+ /* file member gets created by gimp_imagefile_name_changed() */
+ }
+
+ return imagefile;
+}
+
+GFile *
+gimp_imagefile_get_file (GimpImagefile *imagefile)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGEFILE (imagefile), NULL);
+
+ return GET_PRIVATE (imagefile)->file;
+}
+
+void
+gimp_imagefile_set_file (GimpImagefile *imagefile,
+ GFile *file)
+{
+ g_return_if_fail (GIMP_IS_IMAGEFILE (imagefile));
+ g_return_if_fail (file == NULL || G_IS_FILE (file));
+
+ if (GET_PRIVATE (imagefile)->file != file)
+ {
+ gimp_object_take_name (GIMP_OBJECT (imagefile),
+ file ? g_file_get_uri (file) : NULL);
+ }
+}
+
+GimpThumbnail *
+gimp_imagefile_get_thumbnail (GimpImagefile *imagefile)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGEFILE (imagefile), NULL);
+
+ return GET_PRIVATE (imagefile)->thumbnail;
+}
+
+GIcon *
+gimp_imagefile_get_gicon (GimpImagefile *imagefile)
+{
+ GimpImagefilePrivate *private;
+
+ g_return_val_if_fail (GIMP_IS_IMAGEFILE (imagefile), NULL);
+
+ private = GET_PRIVATE (imagefile);
+
+ if (private->icon)
+ return private->icon;
+
+ if (private->file && ! private->icon_cancellable)
+ {
+ private->icon_cancellable = g_cancellable_new ();
+
+ g_file_query_info_async (private->file, "standard::icon",
+ G_FILE_QUERY_INFO_NONE,
+ G_PRIORITY_DEFAULT,
+ private->icon_cancellable,
+ gimp_imagefile_icon_callback,
+ imagefile);
+ }
+
+ return NULL;
+}
+
+void
+gimp_imagefile_set_mime_type (GimpImagefile *imagefile,
+ const gchar *mime_type)
+{
+ g_return_if_fail (GIMP_IS_IMAGEFILE (imagefile));
+
+ g_object_set (GET_PRIVATE (imagefile)->thumbnail,
+ "image-mimetype", mime_type,
+ NULL);
+}
+
+void
+gimp_imagefile_update (GimpImagefile *imagefile)
+{
+ GimpImagefilePrivate *private;
+ gchar *uri;
+
+ g_return_if_fail (GIMP_IS_IMAGEFILE (imagefile));
+
+ private = GET_PRIVATE (imagefile);
+
+ gimp_viewable_invalidate_preview (GIMP_VIEWABLE (imagefile));
+
+ g_object_get (private->thumbnail,
+ "image-uri", &uri,
+ NULL);
+
+ if (uri)
+ {
+ GimpImagefile *documents_imagefile = (GimpImagefile *)
+ gimp_container_get_child_by_name (private->gimp->documents, uri);
+
+ if (documents_imagefile != imagefile &&
+ GIMP_IS_IMAGEFILE (documents_imagefile))
+ gimp_viewable_invalidate_preview (GIMP_VIEWABLE (documents_imagefile));
+
+ g_free (uri);
+ }
+}
+
+gboolean
+gimp_imagefile_create_thumbnail (GimpImagefile *imagefile,
+ GimpContext *context,
+ GimpProgress *progress,
+ gint size,
+ gboolean replace,
+ GError **error)
+{
+ GimpImagefilePrivate *private;
+ GimpThumbnail *thumbnail;
+ GimpThumbState image_state;
+
+ g_return_val_if_fail (GIMP_IS_IMAGEFILE (imagefile), FALSE);
+ g_return_val_if_fail (GIMP_IS_CONTEXT (context), FALSE);
+ g_return_val_if_fail (progress == NULL || GIMP_IS_PROGRESS (progress), FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ /* thumbnailing is disabled, we successfully did nothing */
+ if (size < 1)
+ return TRUE;
+
+ private = GET_PRIVATE (imagefile);
+
+ thumbnail = private->thumbnail;
+
+ gimp_thumbnail_set_uri (thumbnail,
+ gimp_object_get_name (imagefile));
+
+ image_state = gimp_thumbnail_peek_image (thumbnail);
+
+ if (image_state == GIMP_THUMB_STATE_REMOTE ||
+ image_state >= GIMP_THUMB_STATE_EXISTS)
+ {
+ GimpImage *image;
+ gboolean success;
+ gint width = 0;
+ gint height = 0;
+ const gchar *mime_type = NULL;
+ const Babl *format = NULL;
+ gint num_layers = -1;
+
+ /* we only want to attempt thumbnailing on readable, regular files */
+ if (g_file_is_native (private->file))
+ {
+ GFileInfo *file_info;
+ gboolean regular;
+ gboolean readable;
+
+ file_info = g_file_query_info (private->file,
+ G_FILE_ATTRIBUTE_STANDARD_TYPE ","
+ G_FILE_ATTRIBUTE_ACCESS_CAN_READ,
+ G_FILE_QUERY_INFO_NONE,
+ NULL, NULL);
+
+ regular = (g_file_info_get_file_type (file_info) == G_FILE_TYPE_REGULAR);
+ readable = g_file_info_get_attribute_boolean (file_info,
+ G_FILE_ATTRIBUTE_ACCESS_CAN_READ);
+
+ g_object_unref (file_info);
+
+ if (! (regular && readable))
+ return TRUE;
+ }
+
+ g_object_ref (imagefile);
+
+ /* don't pass the error, we're only interested in errors from
+ * actual thumbnail saving
+ */
+ image = file_open_thumbnail (private->gimp, context, progress,
+ private->file, size,
+ &mime_type, &width, &height,
+ &format, &num_layers, NULL);
+
+ if (image)
+ {
+ gimp_thumbnail_set_info (private->thumbnail,
+ mime_type, width, height,
+ format, num_layers);
+ }
+ else
+ {
+ GimpPDBStatusType status;
+
+ /* don't pass the error, we're only interested in errors
+ * from actual thumbnail saving
+ */
+ image = file_open_image (private->gimp, context, progress,
+ private->file,
+ private->file,
+ FALSE, NULL, GIMP_RUN_NONINTERACTIVE,
+ &status, &mime_type, NULL);
+
+ if (image)
+ gimp_thumbnail_set_info_from_image (private->thumbnail,
+ mime_type, image);
+ }
+
+ if (image)
+ {
+ success = gimp_imagefile_save_thumb (imagefile,
+ image, size, replace,
+ error);
+
+ g_object_unref (image);
+ }
+ else
+ {
+ success = gimp_thumbnail_save_failure (thumbnail,
+ "GIMP " GIMP_VERSION,
+ error);
+ gimp_imagefile_update (imagefile);
+ }
+
+ g_object_unref (imagefile);
+
+ if (! success)
+ {
+ g_object_set (thumbnail,
+ "thumb-state", GIMP_THUMB_STATE_FAILED,
+ NULL);
+ }
+
+ return success;
+ }
+
+ return TRUE;
+}
+
+/* The weak version doesn't ref the imagefile but deals gracefully
+ * with an imagefile that is destroyed while the thumbnail is
+ * created. This allows one to use this function w/o the need to
+ * block the user interface.
+ */
+void
+gimp_imagefile_create_thumbnail_weak (GimpImagefile *imagefile,
+ GimpContext *context,
+ GimpProgress *progress,
+ gint size,
+ gboolean replace)
+{
+ GimpImagefilePrivate *private;
+ GimpImagefile *local;
+
+ g_return_if_fail (GIMP_IS_IMAGEFILE (imagefile));
+
+ if (size < 1)
+ return;
+
+ private = GET_PRIVATE (imagefile);
+
+ if (! private->file)
+ return;
+
+ local = gimp_imagefile_new (private->gimp, private->file);
+
+ g_object_add_weak_pointer (G_OBJECT (imagefile), (gpointer) &imagefile);
+
+ if (! gimp_imagefile_create_thumbnail (local, context, progress, size, replace,
+ NULL))
+ {
+ /* The weak version works on a local copy so the thumbnail
+ * status of the actual image is not properly updated in case of
+ * creation failure, thus it would end up in a generic
+ * GIMP_THUMB_STATE_NOT_FOUND, which is less informative.
+ */
+ g_object_set (private->thumbnail,
+ "thumb-state", GIMP_THUMB_STATE_FAILED,
+ NULL);
+ }
+
+ if (imagefile)
+ {
+ GFile *file = gimp_imagefile_get_file (imagefile);
+
+ if (file && g_file_equal (file, gimp_imagefile_get_file (local)))
+ {
+ gimp_imagefile_update (imagefile);
+ }
+
+ g_object_remove_weak_pointer (G_OBJECT (imagefile),
+ (gpointer) &imagefile);
+ }
+
+ g_object_unref (local);
+}
+
+gboolean
+gimp_imagefile_check_thumbnail (GimpImagefile *imagefile)
+{
+ GimpImagefilePrivate *private;
+ gint size;
+
+ g_return_val_if_fail (GIMP_IS_IMAGEFILE (imagefile), FALSE);
+
+ private = GET_PRIVATE (imagefile);
+
+ size = private->gimp->config->thumbnail_size;
+
+ if (size > 0)
+ {
+ GimpThumbState state;
+
+ state = gimp_thumbnail_check_thumb (private->thumbnail, size);
+
+ return (state == GIMP_THUMB_STATE_OK);
+ }
+
+ return TRUE;
+}
+
+gboolean
+gimp_imagefile_save_thumbnail (GimpImagefile *imagefile,
+ const gchar *mime_type,
+ GimpImage *image,
+ GError **error)
+{
+ GimpImagefilePrivate *private;
+ gint size;
+ gboolean success = TRUE;
+
+ g_return_val_if_fail (GIMP_IS_IMAGEFILE (imagefile), FALSE);
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ private = GET_PRIVATE (imagefile);
+
+ size = private->gimp->config->thumbnail_size;
+
+ if (size > 0)
+ {
+ gimp_thumbnail_set_info_from_image (private->thumbnail,
+ mime_type, image);
+
+ success = gimp_imagefile_save_thumb (imagefile,
+ image, size, FALSE,
+ error);
+ }
+
+ return success;
+}
+
+
+/* private functions */
+
+static void
+gimp_imagefile_info_changed (GimpImagefile *imagefile)
+{
+ GimpImagefilePrivate *private = GET_PRIVATE (imagefile);
+
+ if (private->description)
+ {
+ if (! private->static_desc)
+ g_free (private->description);
+
+ private->description = NULL;
+ }
+
+ g_clear_object (&private->icon);
+
+ g_signal_emit (imagefile, gimp_imagefile_signals[INFO_CHANGED], 0);
+}
+
+static void
+gimp_imagefile_notify_thumbnail (GimpImagefile *imagefile,
+ GParamSpec *pspec)
+{
+ if (strcmp (pspec->name, "image-state") == 0 ||
+ strcmp (pspec->name, "thumb-state") == 0)
+ {
+ gimp_imagefile_info_changed (imagefile);
+ }
+}
+
+static void
+gimp_imagefile_icon_callback (GObject *source_object,
+ GAsyncResult *result,
+ gpointer data)
+{
+ GimpImagefile *imagefile;
+ GimpImagefilePrivate *private;
+ GFile *file = G_FILE (source_object);
+ GError *error = NULL;
+ GFileInfo *file_info;
+
+ file_info = g_file_query_info_finish (file, result, &error);
+
+ if (error)
+ {
+ /* we were cancelled from dispose() and the imagefile is
+ * long gone, bail out
+ */
+ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ {
+ g_clear_error (&error);
+ return;
+ }
+
+#ifdef GIMP_UNSTABLE
+ g_printerr ("%s: %s\n", G_STRFUNC, error->message);
+#endif
+
+ g_clear_error (&error);
+ }
+
+ imagefile = GIMP_IMAGEFILE (data);
+ private = GET_PRIVATE (imagefile);
+
+ if (file_info)
+ {
+ private->icon = g_object_ref (g_file_info_get_icon (file_info));
+ g_object_unref (file_info);
+ }
+
+ g_clear_object (&private->icon_cancellable);
+
+ if (private->icon)
+ gimp_viewable_invalidate_preview (GIMP_VIEWABLE (imagefile));
+}
+
+const gchar *
+gimp_imagefile_get_desc_string (GimpImagefile *imagefile)
+{
+ GimpImagefilePrivate *private;
+ GimpThumbnail *thumbnail;
+
+ g_return_val_if_fail (GIMP_IS_IMAGEFILE (imagefile), NULL);
+
+ private = GET_PRIVATE (imagefile);
+
+ if (private->description)
+ return (const gchar *) private->description;
+
+ thumbnail = private->thumbnail;
+
+ switch (thumbnail->image_state)
+ {
+ case GIMP_THUMB_STATE_UNKNOWN:
+ private->description = NULL;
+ private->static_desc = TRUE;
+ break;
+
+ case GIMP_THUMB_STATE_FOLDER:
+ private->description = (gchar *) _("Folder");
+ private->static_desc = TRUE;
+ break;
+
+ case GIMP_THUMB_STATE_SPECIAL:
+ private->description = (gchar *) _("Special File");
+ private->static_desc = TRUE;
+ break;
+
+ case GIMP_THUMB_STATE_NOT_FOUND:
+ private->description =
+ (gchar *) g_strerror (thumbnail->image_not_found_errno);
+ private->static_desc = TRUE;
+ break;
+
+ default:
+ {
+ GString *str = g_string_new (NULL);
+
+ if (thumbnail->image_state == GIMP_THUMB_STATE_REMOTE)
+ {
+ g_string_append (str, _("Remote File"));
+ }
+
+ if (thumbnail->image_filesize > 0)
+ {
+ gchar *size = g_format_size (thumbnail->image_filesize);
+
+ if (str->len > 0)
+ g_string_append_c (str, '\n');
+
+ g_string_append (str, size);
+ g_free (size);
+ }
+
+ switch (thumbnail->thumb_state)
+ {
+ case GIMP_THUMB_STATE_NOT_FOUND:
+ if (str->len > 0)
+ g_string_append_c (str, '\n');
+ g_string_append (str, _("Click to create preview"));
+ break;
+
+ case GIMP_THUMB_STATE_EXISTS:
+ if (str->len > 0)
+ g_string_append_c (str, '\n');
+ g_string_append (str, _("Loading preview..."));
+ break;
+
+ case GIMP_THUMB_STATE_OLD:
+ if (str->len > 0)
+ g_string_append_c (str, '\n');
+ g_string_append (str, _("Preview is out of date"));
+ break;
+
+ case GIMP_THUMB_STATE_FAILED:
+ if (str->len > 0)
+ g_string_append_c (str, '\n');
+ g_string_append (str, _("Cannot create preview"));
+ break;
+
+ case GIMP_THUMB_STATE_OK:
+ {
+ if (thumbnail->image_state == GIMP_THUMB_STATE_REMOTE)
+ {
+ if (str->len > 0)
+ g_string_append_c (str, '\n');
+
+ g_string_append (str, _("(Preview may be out of date)"));
+ }
+
+ if (thumbnail->image_width > 0 && thumbnail->image_height > 0)
+ {
+ if (str->len > 0)
+ g_string_append_c (str, '\n');
+
+ g_string_append_printf (str,
+ ngettext ("%d × %d pixel",
+ "%d × %d pixels",
+ thumbnail->image_height),
+ thumbnail->image_width,
+ thumbnail->image_height);
+ }
+
+ if (thumbnail->image_type)
+ {
+ if (str->len > 0)
+ g_string_append_c (str, '\n');
+
+ g_string_append (str, gettext (thumbnail->image_type));
+ }
+
+ if (thumbnail->image_num_layers > 0)
+ {
+ if (thumbnail->image_type)
+ g_string_append_len (str, ", ", 2);
+ else if (str->len > 0)
+ g_string_append_c (str, '\n');
+
+ g_string_append_printf (str,
+ ngettext ("%d layer",
+ "%d layers",
+ thumbnail->image_num_layers),
+ thumbnail->image_num_layers);
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ private->description = g_string_free (str, FALSE);
+ private->static_desc = FALSE;
+ }
+ }
+
+ return (const gchar *) private->description;
+}
+
+static GdkPixbuf *
+gimp_imagefile_load_thumb (GimpImagefile *imagefile,
+ gint width,
+ gint height)
+{
+ GimpImagefilePrivate *private = GET_PRIVATE (imagefile);
+ GimpThumbnail *thumbnail = private->thumbnail;
+ GdkPixbuf *pixbuf = NULL;
+ GError *error = NULL;
+ gint size = MAX (width, height);
+ gint pixbuf_width;
+ gint pixbuf_height;
+ gint preview_width;
+ gint preview_height;
+
+ if (gimp_thumbnail_peek_thumb (thumbnail, size) < GIMP_THUMB_STATE_EXISTS)
+ return NULL;
+
+ if (thumbnail->image_state == GIMP_THUMB_STATE_NOT_FOUND)
+ return NULL;
+
+ pixbuf = gimp_thumbnail_load_thumb (thumbnail, size, &error);
+
+ if (! pixbuf)
+ {
+ if (error)
+ {
+ gimp_message (private->gimp, NULL, GIMP_MESSAGE_ERROR,
+ _("Could not open thumbnail '%s': %s"),
+ thumbnail->thumb_filename, error->message);
+ g_clear_error (&error);
+ }
+
+ return NULL;
+ }
+
+ pixbuf_width = gdk_pixbuf_get_width (pixbuf);
+ pixbuf_height = gdk_pixbuf_get_height (pixbuf);
+
+ gimp_viewable_calc_preview_size (pixbuf_width,
+ pixbuf_height,
+ width,
+ height,
+ TRUE, 1.0, 1.0,
+ &preview_width,
+ &preview_height,
+ NULL);
+
+ if (preview_width < pixbuf_width || preview_height < pixbuf_height)
+ {
+ GdkPixbuf *scaled = gdk_pixbuf_scale_simple (pixbuf,
+ preview_width,
+ preview_height,
+ GDK_INTERP_BILINEAR);
+ g_object_unref (pixbuf);
+ pixbuf = scaled;
+
+ pixbuf_width = preview_width;
+ pixbuf_height = preview_height;
+ }
+
+ if (gdk_pixbuf_get_n_channels (pixbuf) != 3)
+ {
+ GdkPixbuf *tmp = gdk_pixbuf_new (GDK_COLORSPACE_RGB, FALSE, 8,
+ pixbuf_width, pixbuf_height);
+
+ gdk_pixbuf_composite_color (pixbuf, tmp,
+ 0, 0, pixbuf_width, pixbuf_height,
+ 0.0, 0.0, 1.0, 1.0,
+ GDK_INTERP_NEAREST, 255,
+ 0, 0, GIMP_CHECK_SIZE_SM,
+ 0x66666666, 0x99999999);
+
+ g_object_unref (pixbuf);
+ pixbuf = tmp;
+ }
+
+ return pixbuf;
+}
+
+static gboolean
+gimp_imagefile_save_thumb (GimpImagefile *imagefile,
+ GimpImage *image,
+ gint size,
+ gboolean replace,
+ GError **error)
+{
+ GimpImagefilePrivate *private = GET_PRIVATE (imagefile);
+ GimpThumbnail *thumbnail = private->thumbnail;
+ GdkPixbuf *pixbuf;
+ gint width, height;
+ gboolean success = FALSE;
+
+ if (size < 1)
+ return TRUE;
+
+ if (gimp_image_get_width (image) <= size &&
+ gimp_image_get_height (image) <= size)
+ {
+ width = gimp_image_get_width (image);
+ height = gimp_image_get_height (image);
+
+ size = MAX (width, height);
+ }
+ else
+ {
+ if (gimp_image_get_width (image) < gimp_image_get_height (image))
+ {
+ height = size;
+ width = MAX (1, (size * gimp_image_get_width (image) /
+ gimp_image_get_height (image)));
+ }
+ else
+ {
+ width = size;
+ height = MAX (1, (size * gimp_image_get_height (image) /
+ gimp_image_get_width (image)));
+ }
+ }
+
+ /* we need the projection constructed NOW, not some time later */
+ gimp_pickable_flush (GIMP_PICKABLE (image));
+
+ pixbuf = gimp_viewable_get_new_pixbuf (GIMP_VIEWABLE (image),
+ /* random context, unused */
+ gimp_get_user_context (image->gimp),
+ width, height);
+
+ /* when layer previews are disabled, we won't get a pixbuf */
+ if (! pixbuf)
+ return TRUE;
+
+ success = gimp_thumbnail_save_thumb (thumbnail,
+ pixbuf,
+ "GIMP " GIMP_VERSION,
+ error);
+
+ g_object_unref (pixbuf);
+
+ if (success)
+ {
+ if (replace)
+ gimp_thumbnail_delete_others (thumbnail, size);
+ else
+ gimp_thumbnail_delete_failure (thumbnail);
+
+ gimp_imagefile_update (imagefile);
+ }
+
+ return success;
+}
+
+static void
+gimp_thumbnail_set_info_from_image (GimpThumbnail *thumbnail,
+ const gchar *mime_type,
+ GimpImage *image)
+{
+ const Babl *format;
+
+ /* peek the thumbnail to make sure that mtime and filesize are set */
+ gimp_thumbnail_peek_image (thumbnail);
+
+ format = gimp_image_get_layer_format (image,
+ gimp_image_has_alpha (image));
+
+ g_object_set (thumbnail,
+ "image-mimetype", mime_type,
+ "image-width", gimp_image_get_width (image),
+ "image-height", gimp_image_get_height (image),
+ "image-type", gimp_babl_format_get_description (format),
+ "image-num-layers", gimp_image_get_n_layers (image),
+ NULL);
+}
+
+/**
+ * gimp_thumbnail_set_info:
+ * @thumbnail: #GimpThumbnail object
+ * @mime_type: MIME type of the image associated with this thumbnail
+ * @width: width of the image associated with this thumbnail
+ * @height: height of the image associated with this thumbnail
+ * @format: format of the image (or NULL if the type is not known)
+ * @num_layers: number of layers in the image
+ * (or -1 if the number of layers is not known)
+ *
+ * Set information about the image associated with the @thumbnail object.
+ */
+static void
+gimp_thumbnail_set_info (GimpThumbnail *thumbnail,
+ const gchar *mime_type,
+ gint width,
+ gint height,
+ const Babl *format,
+ gint num_layers)
+{
+ /* peek the thumbnail to make sure that mtime and filesize are set */
+ gimp_thumbnail_peek_image (thumbnail);
+
+ g_object_set (thumbnail,
+ "image-mimetype", mime_type,
+ "image-width", width,
+ "image-height", height,
+ NULL);
+
+ if (format)
+ g_object_set (thumbnail,
+ "image-type", gimp_babl_format_get_description (format),
+ NULL);
+
+ if (num_layers != -1)
+ g_object_set (thumbnail,
+ "image-num-layers", num_layers,
+ NULL);
+}
diff --git a/app/core/gimpimagefile.h b/app/core/gimpimagefile.h
new file mode 100644
index 0000000..9ea29b0
--- /dev/null
+++ b/app/core/gimpimagefile.h
@@ -0,0 +1,90 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpimagefile.h
+ *
+ * Thumbnail handling according to the Thumbnail Managing Standard.
+ * https://specifications.freedesktop.org/thumbnail-spec/
+ *
+ * Copyright (C) 2001-2002 Sven Neumann <sven@gimp.org>
+ * Michael Natterer <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_IMAGEFILE_H__
+#define __GIMP_IMAGEFILE_H__
+
+
+#include "gimpviewable.h"
+
+
+#define GIMP_TYPE_IMAGEFILE (gimp_imagefile_get_type ())
+#define GIMP_IMAGEFILE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_IMAGEFILE, GimpImagefile))
+#define GIMP_IMAGEFILE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_IMAGEFILE, GimpImagefileClass))
+#define GIMP_IS_IMAGEFILE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_IMAGEFILE))
+#define GIMP_IS_IMAGEFILE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_IMAGEFILE))
+#define GIMP_IMAGEFILE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_IMAGEFILE, GimpImagefileClass))
+
+
+typedef struct _GimpImagefileClass GimpImagefileClass;
+
+struct _GimpImagefile
+{
+ GimpViewable parent_instance;
+};
+
+struct _GimpImagefileClass
+{
+ GimpViewableClass parent_class;
+
+ void (* info_changed) (GimpImagefile *imagefile);
+};
+
+
+GType gimp_imagefile_get_type (void) G_GNUC_CONST;
+
+GimpImagefile * gimp_imagefile_new (Gimp *gimp,
+ GFile *file);
+
+GFile * gimp_imagefile_get_file (GimpImagefile *imagefile);
+void gimp_imagefile_set_file (GimpImagefile *imagefile,
+ GFile *file);
+
+GimpThumbnail * gimp_imagefile_get_thumbnail (GimpImagefile *imagefile);
+GIcon * gimp_imagefile_get_gicon (GimpImagefile *imagefile);
+
+void gimp_imagefile_set_mime_type (GimpImagefile *imagefile,
+ const gchar *mime_type);
+void gimp_imagefile_update (GimpImagefile *imagefile);
+gboolean gimp_imagefile_create_thumbnail (GimpImagefile *imagefile,
+ GimpContext *context,
+ GimpProgress *progress,
+ gint size,
+ gboolean replace,
+ GError **error);
+void gimp_imagefile_create_thumbnail_weak (GimpImagefile *imagefile,
+ GimpContext *context,
+ GimpProgress *progress,
+ gint size,
+ gboolean replace);
+gboolean gimp_imagefile_check_thumbnail (GimpImagefile *imagefile);
+gboolean gimp_imagefile_save_thumbnail (GimpImagefile *imagefile,
+ const gchar *mime_type,
+ GimpImage *image,
+ GError **error);
+const gchar * gimp_imagefile_get_desc_string (GimpImagefile *imagefile);
+
+
+#endif /* __GIMP_IMAGEFILE_H__ */
diff --git a/app/core/gimpimageproxy.c b/app/core/gimpimageproxy.c
new file mode 100644
index 0000000..6007d6c
--- /dev/null
+++ b/app/core/gimpimageproxy.c
@@ -0,0 +1,877 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpimageproxy.c
+ * Copyright (C) 2019 Ell
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <cairo.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpcolor/gimpcolor.h"
+
+#include "core-types.h"
+
+#include "gegl/gimp-babl.h"
+#include "gegl/gimp-gegl-loops.h"
+
+#include "gimpimage.h"
+#include "gimpimage-color-profile.h"
+#include "gimpimage-preview.h"
+#include "gimpimageproxy.h"
+#include "gimppickable.h"
+#include "gimpprojectable.h"
+#include "gimptempbuf.h"
+
+
+enum
+{
+ PROP_0,
+ PROP_IMAGE,
+ PROP_SHOW_ALL,
+ PROP_BUFFER
+};
+
+
+struct _GimpImageProxyPrivate
+{
+ GimpImage *image;
+ gboolean show_all;
+
+ GeglRectangle bounding_box;
+ gboolean frozen;
+};
+
+
+/* local function prototypes */
+
+static void gimp_image_proxy_pickable_iface_init (GimpPickableInterface *iface);
+static void gimp_image_proxy_color_managed_iface_init (GimpColorManagedInterface *iface);
+
+static void gimp_image_proxy_finalize (GObject *object);
+static void gimp_image_proxy_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_image_proxy_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static gboolean gimp_image_proxy_get_size (GimpViewable *viewable,
+ gint *width,
+ gint *height);
+static void gimp_image_proxy_get_preview_size (GimpViewable *viewable,
+ gint size,
+ gboolean is_popup,
+ gboolean dot_for_dot,
+ gint *width,
+ gint *height);
+static gboolean gimp_image_proxy_get_popup_size (GimpViewable *viewable,
+ gint width,
+ gint height,
+ gboolean dot_for_dot,
+ gint *popup_width,
+ gint *popup_height);
+static GimpTempBuf * gimp_image_proxy_get_new_preview (GimpViewable *viewable,
+ GimpContext *context,
+ gint width,
+ gint height);
+static GdkPixbuf * gimp_image_proxy_get_new_pixbuf (GimpViewable *viewable,
+ GimpContext *context,
+ gint width,
+ gint height);
+static gchar * gimp_image_proxy_get_description (GimpViewable *viewable,
+ gchar **tooltip);
+
+static void gimp_image_proxy_flush (GimpPickable *pickable);
+static const Babl * gimp_image_proxy_get_format (GimpPickable *pickable);
+static const Babl * gimp_image_proxy_get_format_with_alpha (GimpPickable *pickable);
+static GeglBuffer * gimp_image_proxy_get_buffer (GimpPickable *pickable);
+static gboolean gimp_image_proxy_get_pixel_at (GimpPickable *pickable,
+ gint x,
+ gint y,
+ const Babl *format,
+ gpointer pixel);
+static gdouble gimp_image_proxy_get_opacity_at (GimpPickable *pickable,
+ gint x,
+ gint y);
+static void gimp_image_proxy_get_pixel_average (GimpPickable *pickable,
+ const GeglRectangle *rect,
+ const Babl *format,
+ gpointer pixel);
+static void gimp_image_proxy_pixel_to_srgb (GimpPickable *pickable,
+ const Babl *format,
+ gpointer pixel,
+ GimpRGB *color);
+static void gimp_image_proxy_srgb_to_pixel (GimpPickable *pickable,
+ const GimpRGB *color,
+ const Babl *format,
+ gpointer pixel);
+
+static const guint8 * gimp_image_proxy_get_icc_profile (GimpColorManaged *managed,
+ gsize *len);
+static GimpColorProfile * gimp_image_proxy_get_color_profile (GimpColorManaged *managed);
+static void gimp_image_proxy_profile_changed (GimpColorManaged *managed);
+
+static void gimp_image_proxy_image_frozen_notify (GimpImage *image,
+ const GParamSpec *pspec,
+ GimpImageProxy *image_proxy);
+static void gimp_image_proxy_image_invalidate_preview (GimpImage *image,
+ GimpImageProxy *image_proxy);
+static void gimp_image_proxy_image_size_changed (GimpImage *image,
+ GimpImageProxy *image_proxy);
+static void gimp_image_proxy_image_bounds_changed (GimpImage *image,
+ gint old_x,
+ gint old_y,
+ GimpImageProxy *image_proxy);
+static void gimp_image_proxy_image_profile_changed (GimpImage *image,
+ GimpImageProxy *image_proxy);
+
+static void gimp_image_proxy_set_image (GimpImageProxy *image_proxy,
+ GimpImage *image);
+static GimpPickable * gimp_image_proxy_get_pickable (GimpImageProxy *image_proxy);
+static void gimp_image_proxy_update_bounding_box (GimpImageProxy *image_proxy);
+static void gimp_image_proxy_update_frozen (GimpImageProxy *image_proxy);
+
+
+G_DEFINE_TYPE_WITH_CODE (GimpImageProxy, gimp_image_proxy, GIMP_TYPE_VIEWABLE,
+ G_ADD_PRIVATE (GimpImageProxy)
+ G_IMPLEMENT_INTERFACE (GIMP_TYPE_PICKABLE,
+ gimp_image_proxy_pickable_iface_init)
+ G_IMPLEMENT_INTERFACE (GIMP_TYPE_COLOR_MANAGED,
+ gimp_image_proxy_color_managed_iface_init))
+
+#define parent_class gimp_image_proxy_parent_class
+
+
+/* private functions */
+
+
+static void
+gimp_image_proxy_class_init (GimpImageProxyClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpViewableClass *viewable_class = GIMP_VIEWABLE_CLASS (klass);
+
+ object_class->finalize = gimp_image_proxy_finalize;
+ object_class->set_property = gimp_image_proxy_set_property;
+ object_class->get_property = gimp_image_proxy_get_property;
+
+ viewable_class->default_icon_name = "gimp-image";
+ viewable_class->get_size = gimp_image_proxy_get_size;
+ viewable_class->get_preview_size = gimp_image_proxy_get_preview_size;
+ viewable_class->get_popup_size = gimp_image_proxy_get_popup_size;
+ viewable_class->get_new_preview = gimp_image_proxy_get_new_preview;
+ viewable_class->get_new_pixbuf = gimp_image_proxy_get_new_pixbuf;
+ viewable_class->get_description = gimp_image_proxy_get_description;
+
+ g_object_class_install_property (object_class, PROP_IMAGE,
+ g_param_spec_object ("image",
+ NULL, NULL,
+ GIMP_TYPE_IMAGE,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+
+ g_object_class_install_property (object_class, PROP_SHOW_ALL,
+ g_param_spec_boolean ("show-all",
+ NULL, NULL,
+ FALSE,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_override_property (object_class, PROP_BUFFER, "buffer");
+}
+
+static void
+gimp_image_proxy_pickable_iface_init (GimpPickableInterface *iface)
+{
+ iface->flush = gimp_image_proxy_flush;
+ iface->get_image = (gpointer) gimp_image_proxy_get_image;
+ iface->get_format = gimp_image_proxy_get_format;
+ iface->get_format_with_alpha = gimp_image_proxy_get_format_with_alpha;
+ iface->get_buffer = gimp_image_proxy_get_buffer;
+ iface->get_pixel_at = gimp_image_proxy_get_pixel_at;
+ iface->get_opacity_at = gimp_image_proxy_get_opacity_at;
+ iface->get_pixel_average = gimp_image_proxy_get_pixel_average;
+ iface->pixel_to_srgb = gimp_image_proxy_pixel_to_srgb;
+ iface->srgb_to_pixel = gimp_image_proxy_srgb_to_pixel;
+}
+
+static void
+gimp_image_proxy_color_managed_iface_init (GimpColorManagedInterface *iface)
+{
+ iface->get_icc_profile = gimp_image_proxy_get_icc_profile;
+ iface->get_color_profile = gimp_image_proxy_get_color_profile;
+ iface->profile_changed = gimp_image_proxy_profile_changed;
+}
+
+static void
+gimp_image_proxy_init (GimpImageProxy *image_proxy)
+{
+ image_proxy->priv = gimp_image_proxy_get_instance_private (image_proxy);
+}
+
+static void
+gimp_image_proxy_finalize (GObject *object)
+{
+ GimpImageProxy *image_proxy = GIMP_IMAGE_PROXY (object);
+
+ gimp_image_proxy_set_image (image_proxy, NULL);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gimp_image_proxy_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpImageProxy *image_proxy = GIMP_IMAGE_PROXY (object);
+
+ switch (property_id)
+ {
+ case PROP_IMAGE:
+ gimp_image_proxy_set_image (image_proxy,
+ g_value_get_object (value));
+ break;
+
+ case PROP_SHOW_ALL:
+ gimp_image_proxy_set_show_all (image_proxy,
+ g_value_get_boolean (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_image_proxy_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpImageProxy *image_proxy = GIMP_IMAGE_PROXY (object);
+
+ switch (property_id)
+ {
+ case PROP_IMAGE:
+ g_value_set_object (value,
+ gimp_image_proxy_get_image (image_proxy));
+ break;
+
+ case PROP_SHOW_ALL:
+ g_value_set_boolean (value,
+ gimp_image_proxy_get_show_all (image_proxy));
+ break;
+
+ case PROP_BUFFER:
+ g_value_set_object (value,
+ gimp_pickable_get_buffer (
+ GIMP_PICKABLE (image_proxy)));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static gboolean
+gimp_image_proxy_get_size (GimpViewable *viewable,
+ gint *width,
+ gint *height)
+{
+ GimpImageProxy *image_proxy = GIMP_IMAGE_PROXY (viewable);
+
+ *width = image_proxy->priv->bounding_box.width;
+ *height = image_proxy->priv->bounding_box.height;
+
+ return TRUE;
+}
+
+static void
+gimp_image_proxy_get_preview_size (GimpViewable *viewable,
+ gint size,
+ gboolean is_popup,
+ gboolean dot_for_dot,
+ gint *width,
+ gint *height)
+{
+ GimpImageProxy *image_proxy = GIMP_IMAGE_PROXY (viewable);
+ GimpImage *image = image_proxy->priv->image;
+ gdouble xres;
+ gdouble yres;
+ gint viewable_width;
+ gint viewable_height;
+
+ gimp_image_get_resolution (image, &xres, &yres);
+
+ gimp_viewable_get_size (viewable, &viewable_width, &viewable_height);
+
+ gimp_viewable_calc_preview_size (viewable_width,
+ viewable_height,
+ size,
+ size,
+ dot_for_dot,
+ xres,
+ yres,
+ width,
+ height,
+ NULL);
+}
+
+static gboolean
+gimp_image_proxy_get_popup_size (GimpViewable *viewable,
+ gint width,
+ gint height,
+ gboolean dot_for_dot,
+ gint *popup_width,
+ gint *popup_height)
+{
+ gint viewable_width;
+ gint viewable_height;
+
+ gimp_viewable_get_size (viewable, &viewable_width, &viewable_height);
+
+ if (viewable_width > width || viewable_height > height)
+ {
+ gboolean scaling_up;
+
+ gimp_viewable_calc_preview_size (viewable_width,
+ viewable_height,
+ width * 2,
+ height * 2,
+ dot_for_dot, 1.0, 1.0,
+ popup_width,
+ popup_height,
+ &scaling_up);
+
+ if (scaling_up)
+ {
+ *popup_width = viewable_width;
+ *popup_height = viewable_height;
+ }
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static GimpTempBuf *
+gimp_image_proxy_get_new_preview (GimpViewable *viewable,
+ GimpContext *context,
+ gint width,
+ gint height)
+{
+ GimpImageProxy *image_proxy = GIMP_IMAGE_PROXY (viewable);
+ GimpImage *image = image_proxy->priv->image;
+ GimpPickable *pickable;
+ const Babl *format;
+ GeglRectangle bounding_box;
+ GimpTempBuf *buf;
+ gdouble scale_x;
+ gdouble scale_y;
+ gdouble scale;
+
+ pickable = gimp_image_proxy_get_pickable (image_proxy);
+ bounding_box = gimp_image_proxy_get_bounding_box (image_proxy);
+
+ scale_x = (gdouble) width / (gdouble) bounding_box.width;
+ scale_y = (gdouble) height / (gdouble) bounding_box.height;
+
+ scale = MIN (scale_x, scale_y);
+
+ format = gimp_image_get_preview_format (image);
+
+ buf = gimp_temp_buf_new (width, height, format);
+
+ gegl_buffer_get (gimp_pickable_get_buffer (pickable),
+ GEGL_RECTANGLE (bounding_box.x * scale,
+ bounding_box.y * scale,
+ width,
+ height),
+ scale,
+ gimp_temp_buf_get_format (buf),
+ gimp_temp_buf_get_data (buf),
+ GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_CLAMP);
+
+ return buf;
+}
+
+static GdkPixbuf *
+gimp_image_proxy_get_new_pixbuf (GimpViewable *viewable,
+ GimpContext *context,
+ gint width,
+ gint height)
+{
+ GimpImageProxy *image_proxy = GIMP_IMAGE_PROXY (viewable);
+ GimpImage *image = image_proxy->priv->image;
+ GimpPickable *pickable;
+ GeglRectangle bounding_box;
+ GdkPixbuf *pixbuf;
+ gdouble scale_x;
+ gdouble scale_y;
+ gdouble scale;
+ GimpColorTransform *transform;
+
+ pickable = gimp_image_proxy_get_pickable (image_proxy);
+ bounding_box = gimp_image_proxy_get_bounding_box (image_proxy);
+
+ scale_x = (gdouble) width / (gdouble) bounding_box.width;
+ scale_y = (gdouble) height / (gdouble) bounding_box.height;
+
+ scale = MIN (scale_x, scale_y);
+
+ pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8,
+ width, height);
+
+ transform = gimp_image_get_color_transform_to_srgb_u8 (image);
+
+ if (transform)
+ {
+ GimpTempBuf *temp_buf;
+ GeglBuffer *src_buf;
+ GeglBuffer *dest_buf;
+
+ temp_buf = gimp_temp_buf_new (width, height,
+ gimp_pickable_get_format (pickable));
+
+ gegl_buffer_get (gimp_pickable_get_buffer (pickable),
+ GEGL_RECTANGLE (bounding_box.x * scale,
+ bounding_box.y * scale,
+ width,
+ height),
+ scale,
+ gimp_temp_buf_get_format (temp_buf),
+ gimp_temp_buf_get_data (temp_buf),
+ GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_CLAMP);
+
+ src_buf = gimp_temp_buf_create_buffer (temp_buf);
+ dest_buf = gimp_pixbuf_create_buffer (pixbuf);
+
+ gimp_temp_buf_unref (temp_buf);
+
+ gimp_color_transform_process_buffer (transform,
+ src_buf,
+ GEGL_RECTANGLE (0, 0,
+ width, height),
+ dest_buf,
+ GEGL_RECTANGLE (0, 0, 0, 0));
+
+ g_object_unref (src_buf);
+ g_object_unref (dest_buf);
+ }
+ else
+ {
+ gegl_buffer_get (gimp_pickable_get_buffer (pickable),
+ GEGL_RECTANGLE (bounding_box.x * scale,
+ bounding_box.y * scale,
+ width,
+ height),
+ scale,
+ gimp_pixbuf_get_format (pixbuf),
+ gdk_pixbuf_get_pixels (pixbuf),
+ gdk_pixbuf_get_rowstride (pixbuf),
+ GEGL_ABYSS_CLAMP);
+ }
+
+ return pixbuf;
+}
+
+static gchar *
+gimp_image_proxy_get_description (GimpViewable *viewable,
+ gchar **tooltip)
+{
+ GimpImageProxy *image_proxy = GIMP_IMAGE_PROXY (viewable);
+ GimpImage *image = image_proxy->priv->image;
+
+ if (tooltip)
+ *tooltip = g_strdup (gimp_image_get_display_path (image));
+
+ return g_strdup_printf ("%s-%d",
+ gimp_image_get_display_name (image),
+ gimp_image_get_ID (image));
+}
+
+static void
+gimp_image_proxy_flush (GimpPickable *pickable)
+{
+ GimpImageProxy *image_proxy = GIMP_IMAGE_PROXY (pickable);
+ GimpPickable *proxy_pickable;
+
+ proxy_pickable = gimp_image_proxy_get_pickable (image_proxy);
+
+ gimp_pickable_flush (proxy_pickable);
+}
+
+static const Babl *
+gimp_image_proxy_get_format (GimpPickable *pickable)
+{
+ GimpImageProxy *image_proxy = GIMP_IMAGE_PROXY (pickable);
+ GimpPickable *proxy_pickable;
+
+ proxy_pickable = gimp_image_proxy_get_pickable (image_proxy);
+
+ return gimp_pickable_get_format (proxy_pickable);
+}
+
+static const Babl *
+gimp_image_proxy_get_format_with_alpha (GimpPickable *pickable)
+{
+ GimpImageProxy *image_proxy = GIMP_IMAGE_PROXY (pickable);
+ GimpPickable *proxy_pickable;
+
+ proxy_pickable = gimp_image_proxy_get_pickable (image_proxy);
+
+ return gimp_pickable_get_format_with_alpha (proxy_pickable);
+}
+
+static GeglBuffer *
+gimp_image_proxy_get_buffer (GimpPickable *pickable)
+{
+ GimpImageProxy *image_proxy = GIMP_IMAGE_PROXY (pickable);
+ GimpPickable *proxy_pickable;
+
+ proxy_pickable = gimp_image_proxy_get_pickable (image_proxy);
+
+ return gimp_pickable_get_buffer (proxy_pickable);
+}
+
+static gboolean
+gimp_image_proxy_get_pixel_at (GimpPickable *pickable,
+ gint x,
+ gint y,
+ const Babl *format,
+ gpointer pixel)
+{
+ GimpImageProxy *image_proxy = GIMP_IMAGE_PROXY (pickable);
+ GimpPickable *proxy_pickable;
+
+ proxy_pickable = gimp_image_proxy_get_pickable (image_proxy);
+
+ return gimp_pickable_get_pixel_at (proxy_pickable, x, y, format, pixel);
+}
+
+static gdouble
+gimp_image_proxy_get_opacity_at (GimpPickable *pickable,
+ gint x,
+ gint y)
+{
+ GimpImageProxy *image_proxy = GIMP_IMAGE_PROXY (pickable);
+ GimpPickable *proxy_pickable;
+
+ proxy_pickable = gimp_image_proxy_get_pickable (image_proxy);
+
+ return gimp_pickable_get_opacity_at (proxy_pickable, x, y);
+}
+
+static void
+gimp_image_proxy_get_pixel_average (GimpPickable *pickable,
+ const GeglRectangle *rect,
+ const Babl *format,
+ gpointer pixel)
+{
+ GimpImageProxy *image_proxy = GIMP_IMAGE_PROXY (pickable);
+ GimpPickable *proxy_pickable;
+
+ proxy_pickable = gimp_image_proxy_get_pickable (image_proxy);
+
+ gimp_pickable_get_pixel_average (proxy_pickable, rect, format, pixel);
+}
+
+static void
+gimp_image_proxy_pixel_to_srgb (GimpPickable *pickable,
+ const Babl *format,
+ gpointer pixel,
+ GimpRGB *color)
+{
+ GimpImageProxy *image_proxy = GIMP_IMAGE_PROXY (pickable);
+ GimpPickable *proxy_pickable;
+
+ proxy_pickable = gimp_image_proxy_get_pickable (image_proxy);
+
+ gimp_pickable_pixel_to_srgb (proxy_pickable, format, pixel, color);
+}
+
+static void
+gimp_image_proxy_srgb_to_pixel (GimpPickable *pickable,
+ const GimpRGB *color,
+ const Babl *format,
+ gpointer pixel)
+{
+ GimpImageProxy *image_proxy = GIMP_IMAGE_PROXY (pickable);
+ GimpPickable *proxy_pickable;
+
+ proxy_pickable = gimp_image_proxy_get_pickable (image_proxy);
+
+ gimp_pickable_srgb_to_pixel (proxy_pickable, color, format, pixel);
+}
+
+static const guint8 *
+gimp_image_proxy_get_icc_profile (GimpColorManaged *managed,
+ gsize *len)
+{
+ GimpImageProxy *image_proxy = GIMP_IMAGE_PROXY (managed);
+
+ return gimp_color_managed_get_icc_profile (
+ GIMP_COLOR_MANAGED (image_proxy->priv->image),
+ len);
+}
+
+static GimpColorProfile *
+gimp_image_proxy_get_color_profile (GimpColorManaged *managed)
+{
+ GimpImageProxy *image_proxy = GIMP_IMAGE_PROXY (managed);
+
+ return gimp_color_managed_get_color_profile (
+ GIMP_COLOR_MANAGED (image_proxy->priv->image));
+}
+
+static void
+gimp_image_proxy_profile_changed (GimpColorManaged *managed)
+{
+ gimp_viewable_invalidate_preview (GIMP_VIEWABLE (managed));
+}
+
+static void
+gimp_image_proxy_image_frozen_notify (GimpImage *image,
+ const GParamSpec *pspec,
+ GimpImageProxy *image_proxy)
+{
+ gimp_image_proxy_update_frozen (image_proxy);
+}
+
+static void
+gimp_image_proxy_image_invalidate_preview (GimpImage *image,
+ GimpImageProxy *image_proxy)
+{
+ gimp_viewable_invalidate_preview (GIMP_VIEWABLE (image_proxy));
+}
+
+static void
+gimp_image_proxy_image_size_changed (GimpImage *image,
+ GimpImageProxy *image_proxy)
+{
+ gimp_image_proxy_update_bounding_box (image_proxy);
+}
+
+static void
+gimp_image_proxy_image_bounds_changed (GimpImage *image,
+ gint old_x,
+ gint old_y,
+ GimpImageProxy *image_proxy)
+{
+ gimp_image_proxy_update_bounding_box (image_proxy);
+}
+
+static void
+gimp_image_proxy_image_profile_changed (GimpImage *image,
+ GimpImageProxy *image_proxy)
+{
+ gimp_color_managed_profile_changed (GIMP_COLOR_MANAGED (image_proxy));
+}
+
+static void
+gimp_image_proxy_set_image (GimpImageProxy *image_proxy,
+ GimpImage *image)
+{
+ if (image_proxy->priv->image)
+ {
+ g_signal_handlers_disconnect_by_func (
+ image_proxy->priv->image,
+ gimp_image_proxy_image_frozen_notify,
+ image_proxy);
+ g_signal_handlers_disconnect_by_func (
+ image_proxy->priv->image,
+ gimp_image_proxy_image_invalidate_preview,
+ image_proxy);
+ g_signal_handlers_disconnect_by_func (
+ image_proxy->priv->image,
+ gimp_image_proxy_image_size_changed,
+ image_proxy);
+ g_signal_handlers_disconnect_by_func (
+ image_proxy->priv->image,
+ gimp_image_proxy_image_bounds_changed,
+ image_proxy);
+ g_signal_handlers_disconnect_by_func (
+ image_proxy->priv->image,
+ gimp_image_proxy_image_profile_changed,
+ image_proxy);
+
+ g_object_unref (image_proxy->priv->image);
+ }
+
+ image_proxy->priv->image = image;
+
+ if (image_proxy->priv->image)
+ {
+ g_object_ref (image_proxy->priv->image);
+
+ g_signal_connect (
+ image_proxy->priv->image, "notify::frozen",
+ G_CALLBACK (gimp_image_proxy_image_frozen_notify),
+ image_proxy);
+ g_signal_connect (
+ image_proxy->priv->image, "invalidate-preview",
+ G_CALLBACK (gimp_image_proxy_image_invalidate_preview),
+ image_proxy);
+ g_signal_connect (
+ image_proxy->priv->image, "size-changed",
+ G_CALLBACK (gimp_image_proxy_image_size_changed),
+ image_proxy);
+ g_signal_connect (
+ image_proxy->priv->image, "bounds-changed",
+ G_CALLBACK (gimp_image_proxy_image_bounds_changed),
+ image_proxy);
+ g_signal_connect (
+ image_proxy->priv->image, "profile-changed",
+ G_CALLBACK (gimp_image_proxy_image_profile_changed),
+ image_proxy);
+
+ gimp_image_proxy_update_bounding_box (image_proxy);
+ gimp_image_proxy_update_frozen (image_proxy);
+
+ gimp_viewable_invalidate_preview (GIMP_VIEWABLE (image_proxy));
+ }
+}
+
+static GimpPickable *
+gimp_image_proxy_get_pickable (GimpImageProxy *image_proxy)
+{
+ GimpImage *image = image_proxy->priv->image;
+
+ if (! image_proxy->priv->show_all)
+ return GIMP_PICKABLE (image);
+ else
+ return GIMP_PICKABLE (gimp_image_get_projection (image));
+}
+
+static void
+gimp_image_proxy_update_bounding_box (GimpImageProxy *image_proxy)
+{
+ GimpImage *image = image_proxy->priv->image;
+ GeglRectangle bounding_box;
+
+ if (gimp_viewable_preview_is_frozen (GIMP_VIEWABLE (image_proxy)))
+ return;
+
+ if (! image_proxy->priv->show_all)
+ {
+ bounding_box.x = 0;
+ bounding_box.y = 0;
+ bounding_box.width = gimp_image_get_width (image);
+ bounding_box.height = gimp_image_get_height (image);
+ }
+ else
+ {
+ bounding_box = gimp_projectable_get_bounding_box (
+ GIMP_PROJECTABLE (image));
+ }
+
+ if (! gegl_rectangle_equal (&bounding_box,
+ &image_proxy->priv->bounding_box))
+ {
+ image_proxy->priv->bounding_box = bounding_box;
+
+ gimp_viewable_size_changed (GIMP_VIEWABLE (image_proxy));
+ }
+}
+
+static void
+gimp_image_proxy_update_frozen (GimpImageProxy *image_proxy)
+{
+ gboolean frozen;
+
+ frozen = gimp_viewable_preview_is_frozen (
+ GIMP_VIEWABLE (image_proxy->priv->image));
+
+ if (frozen != image_proxy->priv->frozen)
+ {
+ image_proxy->priv->frozen = frozen;
+
+ if (frozen)
+ {
+ gimp_viewable_preview_freeze (GIMP_VIEWABLE (image_proxy));
+ }
+ else
+ {
+ gimp_viewable_preview_thaw (GIMP_VIEWABLE (image_proxy));
+
+ gimp_image_proxy_update_bounding_box (image_proxy);
+ }
+ }
+}
+
+
+/* public functions */
+
+
+GimpImageProxy *
+gimp_image_proxy_new (GimpImage *image)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+
+ return g_object_new (GIMP_TYPE_IMAGE_PROXY,
+ "image", image,
+ NULL);
+}
+
+GimpImage *
+gimp_image_proxy_get_image (GimpImageProxy *image_proxy)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE_PROXY (image_proxy), NULL);
+
+ return image_proxy->priv->image;
+}
+
+void
+gimp_image_proxy_set_show_all (GimpImageProxy *image_proxy,
+ gboolean show_all)
+{
+ g_return_if_fail (GIMP_IS_IMAGE_PROXY (image_proxy));
+
+ if (show_all != image_proxy->priv->show_all)
+ {
+ image_proxy->priv->show_all = show_all;
+
+ gimp_image_proxy_update_bounding_box (image_proxy);
+ }
+}
+
+gboolean
+gimp_image_proxy_get_show_all (GimpImageProxy *image_proxy)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE_PROXY (image_proxy), FALSE);
+
+ return image_proxy->priv->show_all;
+}
+
+GeglRectangle
+gimp_image_proxy_get_bounding_box (GimpImageProxy *image_proxy)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE_PROXY (image_proxy),
+ *GEGL_RECTANGLE (0, 0, 0, 0));
+
+ return image_proxy->priv->bounding_box;
+}
diff --git a/app/core/gimpimageproxy.h b/app/core/gimpimageproxy.h
new file mode 100644
index 0000000..a6c9922
--- /dev/null
+++ b/app/core/gimpimageproxy.h
@@ -0,0 +1,65 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpimageproxy.h
+ * Copyright (C) 2019 Ell
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_IMAGE_PROXY_H__
+#define __GIMP_IMAGE_PROXY_H__
+
+
+#include "gimpviewable.h"
+
+
+#define GIMP_TYPE_IMAGE_PROXY (gimp_image_proxy_get_type ())
+#define GIMP_IMAGE_PROXY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_IMAGE_PROXY, GimpImageProxy))
+#define GIMP_IMAGE_PROXY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_IMAGE_PROXY, GimpImageProxyClass))
+#define GIMP_IS_IMAGE_PROXY(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_IMAGE_PROXY))
+#define GIMP_IS_IMAGE_PROXY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_IMAGE_PROXY))
+#define GIMP_IMAGE_PROXY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_IMAGE_PROXY, GimpImageProxyClass))
+
+
+typedef struct _GimpImageProxyPrivate GimpImageProxyPrivate;
+typedef struct _GimpImageProxyClass GimpImageProxyClass;
+
+struct _GimpImageProxy
+{
+ GimpViewable parent_instance;
+
+ GimpImageProxyPrivate *priv;
+};
+
+struct _GimpImageProxyClass
+{
+ GimpViewableClass parent_class;
+};
+
+
+GType gimp_image_proxy_get_type (void) G_GNUC_CONST;
+
+GimpImageProxy * gimp_image_proxy_new (GimpImage *image);
+
+GimpImage * gimp_image_proxy_get_image (GimpImageProxy *image_proxy);
+
+void gimp_image_proxy_set_show_all (GimpImageProxy *image_proxy,
+ gboolean show_all);
+gboolean gimp_image_proxy_get_show_all (GimpImageProxy *image_proxy);
+
+GeglRectangle gimp_image_proxy_get_bounding_box (GimpImageProxy *image_proxy);
+
+
+#endif /* __GIMP_IMAGE_PROXY_H__ */
diff --git a/app/core/gimpimageundo.c b/app/core/gimpimageundo.c
new file mode 100644
index 0000000..8aa9e11
--- /dev/null
+++ b/app/core/gimpimageundo.c
@@ -0,0 +1,535 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <cairo.h>
+#include <gegl.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+#include "libgimpbase/gimpbase.h"
+#include "libgimpcolor/gimpcolor.h"
+#include "libgimpconfig/gimpconfig.h"
+
+#include "core-types.h"
+
+#include "gimp-memsize.h"
+#include "gimpdrawable.h"
+#include "gimpgrid.h"
+#include "gimpimage.h"
+#include "gimpimage-color-profile.h"
+#include "gimpimage-colormap.h"
+#include "gimpimage-grid.h"
+#include "gimpimage-metadata.h"
+#include "gimpimage-private.h"
+#include "gimpimageundo.h"
+
+
+enum
+{
+ PROP_0,
+ PROP_PREVIOUS_ORIGIN_X,
+ PROP_PREVIOUS_ORIGIN_Y,
+ PROP_PREVIOUS_WIDTH,
+ PROP_PREVIOUS_HEIGHT,
+ PROP_GRID,
+ PROP_PARASITE_NAME
+};
+
+
+static void gimp_image_undo_constructed (GObject *object);
+static void gimp_image_undo_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_image_undo_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static gint64 gimp_image_undo_get_memsize (GimpObject *object,
+ gint64 *gui_size);
+
+static void gimp_image_undo_pop (GimpUndo *undo,
+ GimpUndoMode undo_mode,
+ GimpUndoAccumulator *accum);
+static void gimp_image_undo_free (GimpUndo *undo,
+ GimpUndoMode undo_mode);
+
+
+G_DEFINE_TYPE (GimpImageUndo, gimp_image_undo, GIMP_TYPE_UNDO)
+
+#define parent_class gimp_image_undo_parent_class
+
+
+static void
+gimp_image_undo_class_init (GimpImageUndoClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpObjectClass *gimp_object_class = GIMP_OBJECT_CLASS (klass);
+ GimpUndoClass *undo_class = GIMP_UNDO_CLASS (klass);
+
+ object_class->constructed = gimp_image_undo_constructed;
+ object_class->set_property = gimp_image_undo_set_property;
+ object_class->get_property = gimp_image_undo_get_property;
+
+ gimp_object_class->get_memsize = gimp_image_undo_get_memsize;
+
+ undo_class->pop = gimp_image_undo_pop;
+ undo_class->free = gimp_image_undo_free;
+
+ g_object_class_install_property (object_class, PROP_PREVIOUS_ORIGIN_X,
+ g_param_spec_int ("previous-origin-x",
+ NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE,
+ 0,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_PREVIOUS_ORIGIN_Y,
+ g_param_spec_int ("previous-origin-y",
+ NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE,
+ 0,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_PREVIOUS_WIDTH,
+ g_param_spec_int ("previous-width",
+ NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE,
+ 0,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_PREVIOUS_HEIGHT,
+ g_param_spec_int ("previous-height",
+ NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE,
+ 0,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_GRID,
+ g_param_spec_object ("grid", NULL, NULL,
+ GIMP_TYPE_GRID,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+
+ g_object_class_install_property (object_class, PROP_PARASITE_NAME,
+ g_param_spec_string ("parasite-name",
+ NULL, NULL,
+ NULL,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+}
+
+static void
+gimp_image_undo_init (GimpImageUndo *undo)
+{
+}
+
+static void
+gimp_image_undo_constructed (GObject *object)
+{
+ GimpImageUndo *image_undo = GIMP_IMAGE_UNDO (object);
+ GimpImage *image;
+
+ G_OBJECT_CLASS (parent_class)->constructed (object);
+
+ image = GIMP_UNDO (object)->image;
+
+ switch (GIMP_UNDO (object)->undo_type)
+ {
+ case GIMP_UNDO_IMAGE_TYPE:
+ image_undo->base_type = gimp_image_get_base_type (image);
+ break;
+
+ case GIMP_UNDO_IMAGE_PRECISION:
+ image_undo->precision = gimp_image_get_precision (image);
+ break;
+
+ case GIMP_UNDO_IMAGE_SIZE:
+ image_undo->width = gimp_image_get_width (image);
+ image_undo->height = gimp_image_get_height (image);
+ break;
+
+ case GIMP_UNDO_IMAGE_RESOLUTION:
+ gimp_image_get_resolution (image,
+ &image_undo->xresolution,
+ &image_undo->yresolution);
+ image_undo->resolution_unit = gimp_image_get_unit (image);
+ break;
+
+ case GIMP_UNDO_IMAGE_GRID:
+ gimp_assert (GIMP_IS_GRID (image_undo->grid));
+ break;
+
+ case GIMP_UNDO_IMAGE_COLORMAP:
+ image_undo->num_colors = gimp_image_get_colormap_size (image);
+ image_undo->colormap = g_memdup (gimp_image_get_colormap (image),
+ GIMP_IMAGE_COLORMAP_SIZE);
+ break;
+
+ case GIMP_UNDO_IMAGE_COLOR_MANAGED:
+ image_undo->is_color_managed = gimp_image_get_is_color_managed (image);
+ break;
+
+ case GIMP_UNDO_IMAGE_METADATA:
+ image_undo->metadata =
+ gimp_metadata_duplicate (gimp_image_get_metadata (image));
+ break;
+
+ case GIMP_UNDO_PARASITE_ATTACH:
+ case GIMP_UNDO_PARASITE_REMOVE:
+ gimp_assert (image_undo->parasite_name != NULL);
+
+ image_undo->parasite = gimp_parasite_copy
+ (gimp_image_parasite_find (image, image_undo->parasite_name));
+ break;
+
+ default:
+ g_return_if_reached ();
+ }
+}
+
+static void
+gimp_image_undo_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpImageUndo *image_undo = GIMP_IMAGE_UNDO (object);
+
+ switch (property_id)
+ {
+ case PROP_PREVIOUS_ORIGIN_X:
+ image_undo->previous_origin_x = g_value_get_int (value);
+ break;
+ case PROP_PREVIOUS_ORIGIN_Y:
+ image_undo->previous_origin_y = g_value_get_int (value);
+ break;
+ case PROP_PREVIOUS_WIDTH:
+ image_undo->previous_width = g_value_get_int (value);
+ break;
+ case PROP_PREVIOUS_HEIGHT:
+ image_undo->previous_height = g_value_get_int (value);
+ break;
+ case PROP_GRID:
+ {
+ GimpGrid *grid = g_value_get_object (value);
+
+ if (grid)
+ image_undo->grid = gimp_config_duplicate (GIMP_CONFIG (grid));
+ }
+ break;
+ case PROP_PARASITE_NAME:
+ image_undo->parasite_name = g_value_dup_string (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_image_undo_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpImageUndo *image_undo = GIMP_IMAGE_UNDO (object);
+
+ switch (property_id)
+ {
+ case PROP_PREVIOUS_ORIGIN_X:
+ g_value_set_int (value, image_undo->previous_origin_x);
+ break;
+ case PROP_PREVIOUS_ORIGIN_Y:
+ g_value_set_int (value, image_undo->previous_origin_y);
+ break;
+ case PROP_PREVIOUS_WIDTH:
+ g_value_set_int (value, image_undo->previous_width);
+ break;
+ case PROP_PREVIOUS_HEIGHT:
+ g_value_set_int (value, image_undo->previous_height);
+ break;
+ case PROP_GRID:
+ g_value_set_object (value, image_undo->grid);
+ break;
+ case PROP_PARASITE_NAME:
+ g_value_set_string (value, image_undo->parasite_name);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static gint64
+gimp_image_undo_get_memsize (GimpObject *object,
+ gint64 *gui_size)
+{
+ GimpImageUndo *image_undo = GIMP_IMAGE_UNDO (object);
+ gint64 memsize = 0;
+
+ if (image_undo->colormap)
+ memsize += GIMP_IMAGE_COLORMAP_SIZE;
+
+ if (image_undo->metadata)
+ memsize += gimp_g_object_get_memsize (G_OBJECT (image_undo->metadata));
+
+ memsize += gimp_object_get_memsize (GIMP_OBJECT (image_undo->grid),
+ gui_size);
+ memsize += gimp_string_get_memsize (image_undo->parasite_name);
+ memsize += gimp_parasite_get_memsize (image_undo->parasite, gui_size);
+
+ return memsize + GIMP_OBJECT_CLASS (parent_class)->get_memsize (object,
+ gui_size);
+}
+
+static void
+gimp_image_undo_pop (GimpUndo *undo,
+ GimpUndoMode undo_mode,
+ GimpUndoAccumulator *accum)
+{
+ GimpImageUndo *image_undo = GIMP_IMAGE_UNDO (undo);
+ GimpImage *image = undo->image;
+ GimpImagePrivate *private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ GIMP_UNDO_CLASS (parent_class)->pop (undo, undo_mode, accum);
+
+ switch (undo->undo_type)
+ {
+ case GIMP_UNDO_IMAGE_TYPE:
+ {
+ GimpImageBaseType base_type;
+
+ base_type = image_undo->base_type;
+ image_undo->base_type = gimp_image_get_base_type (image);
+ g_object_set (image, "base-type", base_type, NULL);
+
+ gimp_image_colormap_changed (image, -1);
+
+ if (image_undo->base_type != gimp_image_get_base_type (image))
+ {
+ if ((image_undo->base_type == GIMP_GRAY) ||
+ (gimp_image_get_base_type (image) == GIMP_GRAY))
+ {
+ /* in case there was no profile undo, we need to emit
+ * profile-changed anyway
+ */
+ gimp_color_managed_profile_changed (GIMP_COLOR_MANAGED (image));
+ }
+
+ accum->mode_changed = TRUE;
+ }
+ }
+ break;
+
+ case GIMP_UNDO_IMAGE_PRECISION:
+ {
+ GimpPrecision precision;
+
+ precision = image_undo->precision;
+ image_undo->precision = gimp_image_get_precision (image);
+ g_object_set (image, "precision", precision, NULL);
+
+ if (image_undo->precision != gimp_image_get_precision (image))
+ accum->precision_changed = TRUE;
+ }
+ break;
+
+ case GIMP_UNDO_IMAGE_SIZE:
+ {
+ gint width;
+ gint height;
+ gint previous_origin_x;
+ gint previous_origin_y;
+ gint previous_width;
+ gint previous_height;
+
+ width = image_undo->width;
+ height = image_undo->height;
+ previous_origin_x = image_undo->previous_origin_x;
+ previous_origin_y = image_undo->previous_origin_y;
+ previous_width = image_undo->previous_width;
+ previous_height = image_undo->previous_height;
+
+ /* Transform to a redo */
+ image_undo->width = gimp_image_get_width (image);
+ image_undo->height = gimp_image_get_height (image);
+ image_undo->previous_origin_x = -previous_origin_x;
+ image_undo->previous_origin_y = -previous_origin_y;
+ image_undo->previous_width = width;
+ image_undo->previous_height = height;
+
+ g_object_set (image,
+ "width", width,
+ "height", height,
+ NULL);
+
+ gimp_drawable_invalidate_boundary
+ (GIMP_DRAWABLE (gimp_image_get_mask (image)));
+
+ if (gimp_image_get_width (image) != image_undo->width ||
+ gimp_image_get_height (image) != image_undo->height)
+ {
+ accum->size_changed = TRUE;
+ accum->previous_origin_x = previous_origin_x;
+ accum->previous_origin_y = previous_origin_y;
+ accum->previous_width = previous_width;
+ accum->previous_height = previous_height;
+ }
+ }
+ break;
+
+ case GIMP_UNDO_IMAGE_RESOLUTION:
+ {
+ gdouble xres;
+ gdouble yres;
+
+ gimp_image_get_resolution (image, &xres, &yres);
+
+ if (ABS (image_undo->xresolution - xres) >= 1e-5 ||
+ ABS (image_undo->yresolution - yres) >= 1e-5)
+ {
+ private->xresolution = image_undo->xresolution;
+ private->yresolution = image_undo->yresolution;
+
+ image_undo->xresolution = xres;
+ image_undo->yresolution = yres;
+
+ accum->resolution_changed = TRUE;
+ }
+ }
+
+ if (image_undo->resolution_unit != gimp_image_get_unit (image))
+ {
+ GimpUnit unit;
+
+ unit = gimp_image_get_unit (image);
+ private->resolution_unit = image_undo->resolution_unit;
+ image_undo->resolution_unit = unit;
+
+ accum->unit_changed = TRUE;
+ }
+ break;
+
+ case GIMP_UNDO_IMAGE_GRID:
+ {
+ GimpGrid *grid;
+
+ grid = gimp_config_duplicate (GIMP_CONFIG (gimp_image_get_grid (image)));
+
+ gimp_image_set_grid (image, image_undo->grid, FALSE);
+
+ g_object_unref (image_undo->grid);
+ image_undo->grid = grid;
+ }
+ break;
+
+ case GIMP_UNDO_IMAGE_COLORMAP:
+ {
+ guchar *colormap;
+ gint num_colors;
+
+ num_colors = gimp_image_get_colormap_size (image);
+ colormap = g_memdup (gimp_image_get_colormap (image),
+ GIMP_IMAGE_COLORMAP_SIZE);
+
+ if (image_undo->colormap)
+ gimp_image_set_colormap (image,
+ image_undo->colormap, image_undo->num_colors,
+ FALSE);
+ else
+ gimp_image_unset_colormap (image, FALSE);
+
+ if (image_undo->colormap)
+ g_free (image_undo->colormap);
+
+ image_undo->num_colors = num_colors;
+ image_undo->colormap = colormap;
+ }
+ break;
+
+ case GIMP_UNDO_IMAGE_COLOR_MANAGED:
+ {
+ gboolean is_color_managed;
+
+ is_color_managed = gimp_image_get_is_color_managed (image);
+ gimp_image_set_is_color_managed (image, image_undo->is_color_managed,
+ FALSE);
+ image_undo->is_color_managed = is_color_managed;
+ }
+ break;
+
+ case GIMP_UNDO_IMAGE_METADATA:
+ {
+ GimpMetadata *metadata;
+
+ metadata = gimp_metadata_duplicate (gimp_image_get_metadata (image));
+
+ gimp_image_set_metadata (image, image_undo->metadata, FALSE);
+
+ if (image_undo->metadata)
+ g_object_unref (image_undo->metadata);
+ image_undo->metadata = metadata;
+ }
+ break;
+
+ case GIMP_UNDO_PARASITE_ATTACH:
+ case GIMP_UNDO_PARASITE_REMOVE:
+ {
+ GimpParasite *parasite = image_undo->parasite;
+
+ image_undo->parasite = gimp_parasite_copy
+ (gimp_image_parasite_find (image, image_undo->parasite_name));
+
+ if (parasite)
+ gimp_image_parasite_attach (image, parasite, FALSE);
+ else
+ gimp_image_parasite_detach (image, image_undo->parasite_name, FALSE);
+
+ if (parasite)
+ gimp_parasite_free (parasite);
+ }
+ break;
+
+ default:
+ g_return_if_reached ();
+ }
+}
+
+static void
+gimp_image_undo_free (GimpUndo *undo,
+ GimpUndoMode undo_mode)
+{
+ GimpImageUndo *image_undo = GIMP_IMAGE_UNDO (undo);
+
+ g_clear_object (&image_undo->grid);
+ g_clear_pointer (&image_undo->colormap, g_free);
+ g_clear_object (&image_undo->metadata);
+ g_clear_pointer (&image_undo->parasite_name, g_free);
+ g_clear_pointer (&image_undo->parasite, gimp_parasite_free);
+
+ GIMP_UNDO_CLASS (parent_class)->free (undo, undo_mode);
+}
diff --git a/app/core/gimpimageundo.h b/app/core/gimpimageundo.h
new file mode 100644
index 0000000..0290b40
--- /dev/null
+++ b/app/core/gimpimageundo.h
@@ -0,0 +1,69 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_IMAGE_UNDO_H__
+#define __GIMP_IMAGE_UNDO_H__
+
+
+#include "gimpundo.h"
+
+
+#define GIMP_TYPE_IMAGE_UNDO (gimp_image_undo_get_type ())
+#define GIMP_IMAGE_UNDO(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_IMAGE_UNDO, GimpImageUndo))
+#define GIMP_IMAGE_UNDO_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_IMAGE_UNDO, GimpImageUndoClass))
+#define GIMP_IS_IMAGE_UNDO(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_IMAGE_UNDO))
+#define GIMP_IS_IMAGE_UNDO_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_IMAGE_UNDO))
+#define GIMP_IMAGE_UNDO_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_IMAGE_UNDO, GimpImageUndoClass))
+
+
+typedef struct _GimpImageUndo GimpImageUndo;
+typedef struct _GimpImageUndoClass GimpImageUndoClass;
+
+struct _GimpImageUndo
+{
+ GimpUndo parent_instance;
+
+ GimpImageBaseType base_type;
+ GimpPrecision precision;
+ gint width;
+ gint height;
+ gint previous_origin_x;
+ gint previous_origin_y;
+ gint previous_width;
+ gint previous_height;
+ gdouble xresolution;
+ gdouble yresolution;
+ GimpUnit resolution_unit;
+ GimpGrid *grid;
+ gint num_colors;
+ guchar *colormap;
+ gboolean is_color_managed;
+ GimpMetadata *metadata;
+ gchar *parasite_name;
+ GimpParasite *parasite;
+};
+
+struct _GimpImageUndoClass
+{
+ GimpUndoClass parent_class;
+};
+
+
+GType gimp_image_undo_get_type (void) G_GNUC_CONST;
+
+
+#endif /* __GIMP_IMAGE_UNDO_H__ */
diff --git a/app/core/gimpitem-exclusive.c b/app/core/gimpitem-exclusive.c
new file mode 100644
index 0000000..5cdb45f
--- /dev/null
+++ b/app/core/gimpitem-exclusive.c
@@ -0,0 +1,276 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpitem-exclusive.c
+ * Copyright (C) 2011 Michael Natterer <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "core-types.h"
+
+#include "gimpcontext.h"
+#include "gimpimage-undo.h"
+#include "gimpimage-undo-push.h"
+#include "gimpitem.h"
+#include "gimpitem-exclusive.h"
+#include "gimpitemstack.h"
+#include "gimpitemtree.h"
+#include "gimpundostack.h"
+
+#include "gimp-intl.h"
+
+
+static GList * gimp_item_exclusive_get_ancestry (GimpItem *item);
+static void gimp_item_exclusive_get_lists (GimpItem *item,
+ const gchar *property,
+ GList **on,
+ GList **off);
+
+
+/* public functions */
+
+void
+gimp_item_toggle_exclusive_visible (GimpItem *item,
+ GimpContext *context)
+{
+ GList *ancestry;
+ GList *on;
+ GList *off;
+ GList *list;
+
+ g_return_if_fail (GIMP_IS_ITEM (item));
+ g_return_if_fail (gimp_item_is_attached (item));
+ g_return_if_fail (GIMP_IS_CONTEXT (context));
+
+ ancestry = gimp_item_exclusive_get_ancestry (item);
+ gimp_item_exclusive_get_lists (item, "visible", &on, &off);
+
+ if (on || off || ! gimp_item_is_visible (item))
+ {
+ GimpImage *image = gimp_item_get_image (item);
+ GimpUndo *undo;
+ gboolean push_undo = TRUE;
+
+ undo = gimp_image_undo_can_compress (image, GIMP_TYPE_UNDO_STACK,
+ GIMP_UNDO_GROUP_ITEM_VISIBILITY);
+
+ if (undo && (g_object_get_data (G_OBJECT (undo), "exclusive-visible-item") ==
+ (gpointer) item))
+ push_undo = FALSE;
+
+ if (push_undo)
+ {
+ if (gimp_image_undo_group_start (image,
+ GIMP_UNDO_GROUP_ITEM_VISIBILITY,
+ _("Set Item Exclusive Visible")))
+ {
+ undo = gimp_image_undo_can_compress (image, GIMP_TYPE_UNDO_STACK,
+ GIMP_UNDO_GROUP_ITEM_VISIBILITY);
+
+ if (undo)
+ g_object_set_data (G_OBJECT (undo), "exclusive-visible-item",
+ (gpointer) item);
+ }
+
+ for (list = ancestry; list; list = g_list_next (list))
+ gimp_image_undo_push_item_visibility (image, NULL, list->data);
+
+ for (list = on; list; list = g_list_next (list))
+ gimp_image_undo_push_item_visibility (image, NULL, list->data);
+
+ for (list = off; list; list = g_list_next (list))
+ gimp_image_undo_push_item_visibility (image, NULL, list->data);
+
+ gimp_image_undo_group_end (image);
+ }
+ else
+ {
+ gimp_undo_refresh_preview (undo, context);
+ }
+
+ for (list = ancestry; list; list = g_list_next (list))
+ gimp_item_set_visible (list->data, TRUE, FALSE);
+
+ if (on)
+ {
+ for (list = on; list; list = g_list_next (list))
+ gimp_item_set_visible (list->data, FALSE, FALSE);
+ }
+ else if (off)
+ {
+ for (list = off; list; list = g_list_next (list))
+ gimp_item_set_visible (list->data, TRUE, FALSE);
+ }
+
+ g_list_free (on);
+ g_list_free (off);
+ }
+
+ g_list_free (ancestry);
+}
+
+void
+gimp_item_toggle_exclusive_linked (GimpItem *item,
+ GimpContext *context)
+{
+ GList *on = NULL;
+ GList *off = NULL;
+ GList *list;
+
+ g_return_if_fail (GIMP_IS_ITEM (item));
+ g_return_if_fail (gimp_item_is_attached (item));
+ g_return_if_fail (GIMP_IS_CONTEXT (context));
+
+ for (list = gimp_item_get_container_iter (item);
+ list;
+ list = g_list_next (list))
+ {
+ GimpItem *other = list->data;
+
+ if (other != item)
+ {
+ if (gimp_item_get_linked (other))
+ on = g_list_prepend (on, other);
+ else
+ off = g_list_prepend (off, other);
+ }
+ }
+
+ if (on || off || ! gimp_item_get_linked (item))
+ {
+ GimpImage *image = gimp_item_get_image (item);
+ GimpUndo *undo;
+ gboolean push_undo = TRUE;
+
+ undo = gimp_image_undo_can_compress (image, GIMP_TYPE_UNDO_STACK,
+ GIMP_UNDO_GROUP_ITEM_LINKED);
+
+ if (undo && (g_object_get_data (G_OBJECT (undo), "exclusive-linked-item") ==
+ (gpointer) item))
+ push_undo = FALSE;
+
+ if (push_undo)
+ {
+ if (gimp_image_undo_group_start (image,
+ GIMP_UNDO_GROUP_ITEM_LINKED,
+ _("Set Item Exclusive Linked")))
+ {
+ undo = gimp_image_undo_can_compress (image, GIMP_TYPE_UNDO_STACK,
+ GIMP_UNDO_GROUP_ITEM_LINKED);
+
+ if (undo)
+ g_object_set_data (G_OBJECT (undo), "exclusive-linked-item",
+ (gpointer) item);
+ }
+
+ gimp_image_undo_push_item_linked (image, NULL, item);
+
+ for (list = on; list; list = g_list_next (list))
+ gimp_image_undo_push_item_linked (image, NULL, list->data);
+
+ for (list = off; list; list = g_list_next (list))
+ gimp_image_undo_push_item_linked (image, NULL, list->data);
+
+ gimp_image_undo_group_end (image);
+ }
+ else
+ {
+ gimp_undo_refresh_preview (undo, context);
+ }
+
+ if (off || ! gimp_item_get_linked (item))
+ {
+ gimp_item_set_linked (item, TRUE, FALSE);
+
+ for (list = off; list; list = g_list_next (list))
+ gimp_item_set_linked (list->data, TRUE, FALSE);
+ }
+ else
+ {
+ for (list = on; list; list = g_list_next (list))
+ gimp_item_set_linked (list->data, FALSE, FALSE);
+ }
+
+ g_list_free (on);
+ g_list_free (off);
+ }
+}
+
+
+/* private functions */
+
+static GList *
+gimp_item_exclusive_get_ancestry (GimpItem *item)
+{
+ GimpViewable *parent;
+ GList *ancestry = NULL;
+
+ for (parent = GIMP_VIEWABLE (item);
+ parent;
+ parent = gimp_viewable_get_parent (parent))
+ {
+ ancestry = g_list_prepend (ancestry, parent);
+ }
+
+ return ancestry;
+}
+
+static void
+gimp_item_exclusive_get_lists (GimpItem *item,
+ const gchar *property,
+ GList **on,
+ GList **off)
+{
+ GimpItemTree *tree;
+ GList *items;
+ GList *list;
+
+ *on = NULL;
+ *off = NULL;
+
+ tree = gimp_item_get_tree (item);
+
+ items = gimp_item_stack_get_item_list (GIMP_ITEM_STACK (tree->container));
+
+ for (list = items; list; list = g_list_next (list))
+ {
+ GimpItem *other = list->data;
+
+ if (other != item)
+ {
+ /* we are only interested in same level items.
+ */
+ if (gimp_viewable_get_parent (GIMP_VIEWABLE (other)) ==
+ gimp_viewable_get_parent (GIMP_VIEWABLE (item)))
+ {
+ gboolean value;
+
+ g_object_get (other, property, &value, NULL);
+
+ if (value)
+ *on = g_list_prepend (*on, other);
+ else
+ *off = g_list_prepend (*off, other);
+ }
+ }
+ }
+
+ g_list_free (items);
+}
diff --git a/app/core/gimpitem-exclusive.h b/app/core/gimpitem-exclusive.h
new file mode 100644
index 0000000..98838c5
--- /dev/null
+++ b/app/core/gimpitem-exclusive.h
@@ -0,0 +1,31 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpitem-exclusive.h
+ * Copyright (C) 2011 Michael Natterer <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_ITEM_EXCLUSIVE_H__
+#define __GIMP_ITEM_EXCLUSIVE_H__
+
+
+void gimp_item_toggle_exclusive_visible (GimpItem *item,
+ GimpContext *context);
+void gimp_item_toggle_exclusive_linked (GimpItem *item,
+ GimpContext *context);
+
+
+#endif /* __GIMP_ITEM_EXCLUSIVE_H__ */
diff --git a/app/core/gimpitem-linked.c b/app/core/gimpitem-linked.c
new file mode 100644
index 0000000..9c876e3
--- /dev/null
+++ b/app/core/gimpitem-linked.c
@@ -0,0 +1,187 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "core-types.h"
+
+#include "gimpcontext.h"
+#include "gimpimage.h"
+#include "gimpimage-item-list.h"
+#include "gimpimage-undo.h"
+#include "gimpitem.h"
+#include "gimpitem-linked.h"
+#include "gimplist.h"
+#include "gimpprogress.h"
+
+#include "gimp-intl.h"
+
+
+/* public functions */
+
+gboolean
+gimp_item_linked_is_locked (GimpItem *item)
+{
+ GList *list;
+ GList *l;
+ gboolean locked = FALSE;
+
+ g_return_val_if_fail (GIMP_IS_ITEM (item), FALSE);
+ g_return_val_if_fail (gimp_item_get_linked (item) == TRUE, FALSE);
+ g_return_val_if_fail (gimp_item_is_attached (item), FALSE);
+
+ list = gimp_image_item_list_get_list (gimp_item_get_image (item),
+ GIMP_ITEM_TYPE_ALL,
+ GIMP_ITEM_SET_LINKED);
+
+ list = gimp_image_item_list_filter (list);
+
+ for (l = list; l && ! locked; l = g_list_next (l))
+ {
+ /* We must not use gimp_item_is_position_locked(), especially
+ * since a child implementation may call the current function and
+ * end up in infinite loop.
+ * We are only interested in the value of `lock_position` flag.
+ */
+ if (gimp_item_get_lock_position (l->data))
+ locked = TRUE;
+ }
+
+ g_list_free (list);
+
+ return locked;
+}
+
+void
+gimp_item_linked_translate (GimpItem *item,
+ gint offset_x,
+ gint offset_y,
+ gboolean push_undo)
+{
+ GimpImage *image;
+ GList *items;
+
+ g_return_if_fail (GIMP_IS_ITEM (item));
+ g_return_if_fail (gimp_item_get_linked (item) == TRUE);
+ g_return_if_fail (gimp_item_is_attached (item));
+
+ image = gimp_item_get_image (item);
+
+ items = gimp_image_item_list_get_list (image,
+ GIMP_ITEM_TYPE_ALL,
+ GIMP_ITEM_SET_LINKED);
+
+ items = gimp_image_item_list_filter (items);
+
+ gimp_image_item_list_translate (gimp_item_get_image (item), items,
+ offset_x, offset_y, push_undo);
+
+ g_list_free (items);
+}
+
+void
+gimp_item_linked_flip (GimpItem *item,
+ GimpContext *context,
+ GimpOrientationType flip_type,
+ gdouble axis,
+ gboolean clip_result)
+{
+ GimpImage *image;
+ GList *items;
+
+ g_return_if_fail (GIMP_IS_ITEM (item));
+ g_return_if_fail (GIMP_IS_CONTEXT (context));
+ g_return_if_fail (gimp_item_get_linked (item) == TRUE);
+ g_return_if_fail (gimp_item_is_attached (item));
+
+ image = gimp_item_get_image (item);
+
+ items = gimp_image_item_list_get_list (image,
+ GIMP_ITEM_TYPE_ALL,
+ GIMP_ITEM_SET_LINKED);
+ items = gimp_image_item_list_filter (items);
+
+ gimp_image_item_list_flip (image, items, context,
+ flip_type, axis, clip_result);
+
+ g_list_free (items);
+}
+
+void
+gimp_item_linked_rotate (GimpItem *item,
+ GimpContext *context,
+ GimpRotationType rotate_type,
+ gdouble center_x,
+ gdouble center_y,
+ gboolean clip_result)
+{
+ GimpImage *image;
+ GList *items;
+
+ g_return_if_fail (GIMP_IS_ITEM (item));
+ g_return_if_fail (GIMP_IS_CONTEXT (context));
+ g_return_if_fail (gimp_item_get_linked (item) == TRUE);
+ g_return_if_fail (gimp_item_is_attached (item));
+
+ image = gimp_item_get_image (item);
+
+ items = gimp_image_item_list_get_list (image,
+ GIMP_ITEM_TYPE_ALL,
+ GIMP_ITEM_SET_LINKED);
+ items = gimp_image_item_list_filter (items);
+
+ gimp_image_item_list_rotate (image, items, context,
+ rotate_type, center_x, center_y, clip_result);
+
+ g_list_free (items);
+}
+
+void
+gimp_item_linked_transform (GimpItem *item,
+ GimpContext *context,
+ const GimpMatrix3 *matrix,
+ GimpTransformDirection direction,
+ GimpInterpolationType interpolation_type,
+ GimpTransformResize clip_result,
+ GimpProgress *progress)
+{
+ GimpImage *image;
+ GList *items;
+
+ g_return_if_fail (GIMP_IS_ITEM (item));
+ g_return_if_fail (GIMP_IS_CONTEXT (context));
+ g_return_if_fail (gimp_item_get_linked (item) == TRUE);
+ g_return_if_fail (gimp_item_is_attached (item));
+ g_return_if_fail (progress == NULL || GIMP_IS_PROGRESS (progress));
+
+ image = gimp_item_get_image (item);
+
+ items = gimp_image_item_list_get_list (image,
+ GIMP_ITEM_TYPE_ALL,
+ GIMP_ITEM_SET_LINKED);
+ items = gimp_image_item_list_filter (items);
+
+ gimp_image_item_list_transform (image, items, context,
+ matrix, direction,
+ interpolation_type,
+ clip_result, progress);
+
+ g_list_free (items);
+}
diff --git a/app/core/gimpitem-linked.h b/app/core/gimpitem-linked.h
new file mode 100644
index 0000000..c82d85a
--- /dev/null
+++ b/app/core/gimpitem-linked.h
@@ -0,0 +1,48 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_ITEM_LINKED_H__
+#define __GIMP_ITEM_LINKED_H__
+
+
+gboolean gimp_item_linked_is_locked (GimpItem *item);
+
+void gimp_item_linked_translate (GimpItem *item,
+ gint offset_x,
+ gint offset_y,
+ gboolean push_undo);
+void gimp_item_linked_flip (GimpItem *item,
+ GimpContext *context,
+ GimpOrientationType flip_type,
+ gdouble axis,
+ gboolean clip_result);
+void gimp_item_linked_rotate (GimpItem *item,
+ GimpContext *context,
+ GimpRotationType rotate_type,
+ gdouble center_x,
+ gdouble center_y,
+ gboolean clip_result);
+void gimp_item_linked_transform (GimpItem *item,
+ GimpContext *context,
+ const GimpMatrix3 *matrix,
+ GimpTransformDirection direction,
+ GimpInterpolationType interpolation_type,
+ GimpTransformResize clip_result,
+ GimpProgress *progress);
+
+
+#endif /* __GIMP_ITEM_LINKED_H__ */
diff --git a/app/core/gimpitem-preview.c b/app/core/gimpitem-preview.c
new file mode 100644
index 0000000..f24017b
--- /dev/null
+++ b/app/core/gimpitem-preview.c
@@ -0,0 +1,133 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpmath/gimpmath.h"
+
+#include "core-types.h"
+
+#include "config/gimpcoreconfig.h"
+
+#include "gimp.h"
+#include "gimpimage.h"
+#include "gimpitem.h"
+#include "gimpitem-preview.h"
+
+
+/* public functions */
+
+void
+gimp_item_get_preview_size (GimpViewable *viewable,
+ gint size,
+ gboolean is_popup,
+ gboolean dot_for_dot,
+ gint *width,
+ gint *height)
+{
+ GimpItem *item = GIMP_ITEM (viewable);
+ GimpImage *image = gimp_item_get_image (item);
+
+ if (image && ! image->gimp->config->layer_previews && ! is_popup)
+ {
+ *width = size;
+ *height = size;
+ return;
+ }
+
+ if (image && ! is_popup)
+ {
+ gdouble xres;
+ gdouble yres;
+
+ gimp_image_get_resolution (image, &xres, &yres);
+
+ gimp_viewable_calc_preview_size (gimp_image_get_width (image),
+ gimp_image_get_height (image),
+ size,
+ size,
+ dot_for_dot,
+ xres,
+ yres,
+ width,
+ height,
+ NULL);
+ }
+ else
+ {
+ gimp_viewable_calc_preview_size (gimp_item_get_width (item),
+ gimp_item_get_height (item),
+ size,
+ size,
+ dot_for_dot, 1.0, 1.0,
+ width,
+ height,
+ NULL);
+ }
+}
+
+gboolean
+gimp_item_get_popup_size (GimpViewable *viewable,
+ gint width,
+ gint height,
+ gboolean dot_for_dot,
+ gint *popup_width,
+ gint *popup_height)
+{
+ GimpItem *item = GIMP_ITEM (viewable);
+ GimpImage *image = gimp_item_get_image (item);
+
+ if (image && ! image->gimp->config->layer_previews)
+ return FALSE;
+
+ if (gimp_item_get_width (item) > width ||
+ gimp_item_get_height (item) > height)
+ {
+ gboolean scaling_up;
+ gdouble xres = 1.0;
+ gdouble yres = 1.0;
+
+ if (image)
+ gimp_image_get_resolution (image, &xres, &yres);
+
+ gimp_viewable_calc_preview_size (gimp_item_get_width (item),
+ gimp_item_get_height (item),
+ width * 2,
+ height * 2,
+ dot_for_dot,
+ xres,
+ yres,
+ popup_width,
+ popup_height,
+ &scaling_up);
+
+ if (scaling_up)
+ {
+ *popup_width = gimp_item_get_width (item);
+ *popup_height = gimp_item_get_height (item);
+ }
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
diff --git a/app/core/gimpitem-preview.h b/app/core/gimpitem-preview.h
new file mode 100644
index 0000000..6bfed24
--- /dev/null
+++ b/app/core/gimpitem-preview.h
@@ -0,0 +1,40 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_ITEM__PREVIEW_H__
+#define __GIMP_ITEM__PREVIEW_H__
+
+
+/*
+ * virtual functions of GimpItem -- don't call directly
+ */
+
+void gimp_item_get_preview_size (GimpViewable *viewable,
+ gint size,
+ gboolean is_popup,
+ gboolean dot_for_dot,
+ gint *width,
+ gint *height);
+gboolean gimp_item_get_popup_size (GimpViewable *viewable,
+ gint width,
+ gint height,
+ gboolean dot_for_dot,
+ gint *popup_width,
+ gint *popup_height);
+
+
+#endif /* __GIMP_ITEM__PREVIEW_H__ */
diff --git a/app/core/gimpitem.c b/app/core/gimpitem.c
new file mode 100644
index 0000000..c0f732c
--- /dev/null
+++ b/app/core/gimpitem.c
@@ -0,0 +1,2689 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpbase/gimpbase.h"
+#include "libgimpmath/gimpmath.h"
+
+#include "core-types.h"
+
+#include "gimp.h"
+#include "gimp-parasites.h"
+#include "gimpchannel.h"
+#include "gimpcontainer.h"
+#include "gimpidtable.h"
+#include "gimpimage.h"
+#include "gimpimage-undo.h"
+#include "gimpimage-undo-push.h"
+#include "gimpitem.h"
+#include "gimpitem-linked.h"
+#include "gimpitem-preview.h"
+#include "gimpitemtree.h"
+#include "gimplist.h"
+#include "gimpmarshal.h"
+#include "gimpparasitelist.h"
+#include "gimpprogress.h"
+#include "gimpstrokeoptions.h"
+
+#include "paint/gimppaintoptions.h"
+
+#include "gimp-intl.h"
+
+
+enum
+{
+ REMOVED,
+ VISIBILITY_CHANGED,
+ LINKED_CHANGED,
+ COLOR_TAG_CHANGED,
+ LOCK_CONTENT_CHANGED,
+ LOCK_POSITION_CHANGED,
+ LAST_SIGNAL
+};
+
+enum
+{
+ PROP_0,
+ PROP_IMAGE,
+ PROP_ID,
+ PROP_WIDTH,
+ PROP_HEIGHT,
+ PROP_OFFSET_X,
+ PROP_OFFSET_Y,
+ PROP_VISIBLE,
+ PROP_LINKED,
+ PROP_COLOR_TAG,
+ PROP_LOCK_CONTENT,
+ PROP_LOCK_POSITION
+};
+
+
+typedef struct _GimpItemPrivate GimpItemPrivate;
+
+struct _GimpItemPrivate
+{
+ gint ID; /* provides a unique ID */
+ guint32 tattoo; /* provides a permanent ID */
+
+ GimpImage *image; /* item owner */
+
+ GimpParasiteList *parasites; /* Plug-in parasite data */
+
+ gint width, height; /* size in pixels */
+ gint offset_x, offset_y; /* pixel offset in image */
+
+ guint visible : 1; /* item visibility */
+ guint bind_visible_to_active : 1; /* visibility bound to active */
+
+ guint linked : 1; /* control linkage */
+ guint lock_content : 1; /* content editability */
+ guint lock_position : 1; /* content movability */
+
+ guint removed : 1; /* removed from the image? */
+
+ GimpColorTag color_tag; /* color tag */
+
+ GList *offset_nodes; /* offset nodes to manage */
+};
+
+#define GET_PRIVATE(item) ((GimpItemPrivate *) gimp_item_get_instance_private ((GimpItem *) (item)))
+
+
+/* local function prototypes */
+
+static void gimp_item_constructed (GObject *object);
+static void gimp_item_finalize (GObject *object);
+static void gimp_item_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_item_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static gint64 gimp_item_get_memsize (GimpObject *object,
+ gint64 *gui_size);
+
+static gboolean gimp_item_real_is_content_locked (GimpItem *item);
+static gboolean gimp_item_real_is_position_locked (GimpItem *item);
+static gboolean gimp_item_real_bounds (GimpItem *item,
+ gdouble *x,
+ gdouble *y,
+ gdouble *width,
+ gdouble *height);
+static GimpItem * gimp_item_real_duplicate (GimpItem *item,
+ GType new_type);
+static void gimp_item_real_convert (GimpItem *item,
+ GimpImage *dest_image,
+ GType old_type);
+static gboolean gimp_item_real_rename (GimpItem *item,
+ const gchar *new_name,
+ const gchar *undo_desc,
+ GError **error);
+static void gimp_item_real_start_transform (GimpItem *item,
+ gboolean push_undo);
+static void gimp_item_real_end_transform (GimpItem *item,
+ gboolean push_undo);
+static void gimp_item_real_translate (GimpItem *item,
+ gdouble offset_x,
+ gdouble offset_y,
+ gboolean push_undo);
+static void gimp_item_real_scale (GimpItem *item,
+ gint new_width,
+ gint new_height,
+ gint new_offset_x,
+ gint new_offset_y,
+ GimpInterpolationType interpolation,
+ GimpProgress *progress);
+static void gimp_item_real_resize (GimpItem *item,
+ GimpContext *context,
+ GimpFillType fill_type,
+ gint new_width,
+ gint new_height,
+ gint offset_x,
+ gint offset_y);
+static GimpTransformResize
+ gimp_item_real_get_clip (GimpItem *item,
+ GimpTransformResize clip_result);
+
+
+
+G_DEFINE_TYPE_WITH_PRIVATE (GimpItem, gimp_item, GIMP_TYPE_FILTER)
+
+#define parent_class gimp_item_parent_class
+
+static guint gimp_item_signals[LAST_SIGNAL] = { 0 };
+
+
+static void
+gimp_item_class_init (GimpItemClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpObjectClass *gimp_object_class = GIMP_OBJECT_CLASS (klass);
+ GimpViewableClass *viewable_class = GIMP_VIEWABLE_CLASS (klass);
+
+ gimp_item_signals[REMOVED] =
+ g_signal_new ("removed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpItemClass, removed),
+ NULL, NULL,
+ gimp_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ gimp_item_signals[VISIBILITY_CHANGED] =
+ g_signal_new ("visibility-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpItemClass, visibility_changed),
+ NULL, NULL,
+ gimp_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ gimp_item_signals[LINKED_CHANGED] =
+ g_signal_new ("linked-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpItemClass, linked_changed),
+ NULL, NULL,
+ gimp_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ gimp_item_signals[COLOR_TAG_CHANGED] =
+ g_signal_new ("color-tag-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpItemClass, color_tag_changed),
+ NULL, NULL,
+ gimp_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ gimp_item_signals[LOCK_CONTENT_CHANGED] =
+ g_signal_new ("lock-content-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpItemClass, lock_content_changed),
+ NULL, NULL,
+ gimp_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ gimp_item_signals[LOCK_POSITION_CHANGED] =
+ g_signal_new ("lock-position-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpItemClass, lock_position_changed),
+ NULL, NULL,
+ gimp_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ object_class->constructed = gimp_item_constructed;
+ object_class->finalize = gimp_item_finalize;
+ object_class->set_property = gimp_item_set_property;
+ object_class->get_property = gimp_item_get_property;
+
+ gimp_object_class->get_memsize = gimp_item_get_memsize;
+
+ viewable_class->name_editable = TRUE;
+ viewable_class->get_preview_size = gimp_item_get_preview_size;
+ viewable_class->get_popup_size = gimp_item_get_popup_size;
+
+ klass->removed = NULL;
+ klass->visibility_changed = NULL;
+ klass->linked_changed = NULL;
+ klass->color_tag_changed = NULL;
+ klass->lock_content_changed = NULL;
+ klass->lock_position_changed = NULL;
+
+ klass->unset_removed = NULL;
+ klass->is_attached = NULL;
+ klass->is_content_locked = gimp_item_real_is_content_locked;
+ klass->is_position_locked = gimp_item_real_is_position_locked;
+ klass->get_tree = NULL;
+ klass->bounds = gimp_item_real_bounds;
+ klass->duplicate = gimp_item_real_duplicate;
+ klass->convert = gimp_item_real_convert;
+ klass->rename = gimp_item_real_rename;
+ klass->start_move = NULL;
+ klass->end_move = NULL;
+ klass->start_transform = gimp_item_real_start_transform;
+ klass->end_transform = gimp_item_real_end_transform;
+ klass->translate = gimp_item_real_translate;
+ klass->scale = gimp_item_real_scale;
+ klass->resize = gimp_item_real_resize;
+ klass->flip = NULL;
+ klass->rotate = NULL;
+ klass->transform = NULL;
+ klass->get_clip = gimp_item_real_get_clip;
+ klass->fill = NULL;
+ klass->stroke = NULL;
+ klass->to_selection = NULL;
+
+ klass->default_name = NULL;
+ klass->rename_desc = NULL;
+ klass->translate_desc = NULL;
+ klass->scale_desc = NULL;
+ klass->resize_desc = NULL;
+ klass->flip_desc = NULL;
+ klass->rotate_desc = NULL;
+ klass->transform_desc = NULL;
+ klass->fill_desc = NULL;
+ klass->stroke_desc = NULL;
+
+ g_object_class_install_property (object_class, PROP_IMAGE,
+ g_param_spec_object ("image", NULL, NULL,
+ GIMP_TYPE_IMAGE,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+ g_object_class_install_property (object_class, PROP_ID,
+ g_param_spec_int ("id", NULL, NULL,
+ 0, G_MAXINT, 0,
+ GIMP_PARAM_READABLE));
+
+ g_object_class_install_property (object_class, PROP_WIDTH,
+ g_param_spec_int ("width", NULL, NULL,
+ 1, GIMP_MAX_IMAGE_SIZE, 1,
+ GIMP_PARAM_READABLE));
+
+ g_object_class_install_property (object_class, PROP_HEIGHT,
+ g_param_spec_int ("height", NULL, NULL,
+ 1, GIMP_MAX_IMAGE_SIZE, 1,
+ GIMP_PARAM_READABLE));
+
+ g_object_class_install_property (object_class, PROP_OFFSET_X,
+ g_param_spec_int ("offset-x", NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE, 0,
+ GIMP_PARAM_READABLE));
+
+ g_object_class_install_property (object_class, PROP_OFFSET_Y,
+ g_param_spec_int ("offset-y", NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE, 0,
+ GIMP_PARAM_READABLE));
+
+ g_object_class_install_property (object_class, PROP_VISIBLE,
+ g_param_spec_boolean ("visible", NULL, NULL,
+ TRUE,
+ GIMP_PARAM_READABLE));
+
+ g_object_class_install_property (object_class, PROP_LINKED,
+ g_param_spec_boolean ("linked", NULL, NULL,
+ FALSE,
+ GIMP_PARAM_READABLE));
+
+ g_object_class_install_property (object_class, PROP_COLOR_TAG,
+ g_param_spec_enum ("color-tag", NULL, NULL,
+ GIMP_TYPE_COLOR_TAG,
+ GIMP_COLOR_TAG_NONE,
+ GIMP_PARAM_READABLE));
+
+ g_object_class_install_property (object_class, PROP_LOCK_CONTENT,
+ g_param_spec_boolean ("lock-content",
+ NULL, NULL,
+ FALSE,
+ GIMP_PARAM_READABLE));
+
+ g_object_class_install_property (object_class, PROP_LOCK_POSITION,
+ g_param_spec_boolean ("lock-position",
+ NULL, NULL,
+ FALSE,
+ GIMP_PARAM_READABLE));
+}
+
+static void
+gimp_item_init (GimpItem *item)
+{
+ GimpItemPrivate *private = GET_PRIVATE (item);
+
+ g_object_force_floating (G_OBJECT (item));
+
+ private->parasites = gimp_parasite_list_new ();
+ private->visible = TRUE;
+ private->bind_visible_to_active = TRUE;
+}
+
+static void
+gimp_item_constructed (GObject *object)
+{
+ GimpItemPrivate *private = GET_PRIVATE (object);
+
+ G_OBJECT_CLASS (parent_class)->constructed (object);
+
+ gimp_assert (GIMP_IS_IMAGE (private->image));
+ gimp_assert (private->ID != 0);
+}
+
+static void
+gimp_item_finalize (GObject *object)
+{
+ GimpItemPrivate *private = GET_PRIVATE (object);
+
+ if (private->offset_nodes)
+ {
+ g_list_free_full (private->offset_nodes,
+ (GDestroyNotify) g_object_unref);
+ private->offset_nodes = NULL;
+ }
+
+ if (private->image && private->image->gimp)
+ {
+ gimp_id_table_remove (private->image->gimp->item_table, private->ID);
+ private->image = NULL;
+ }
+
+ g_clear_object (&private->parasites);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gimp_item_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpItem *item = GIMP_ITEM (object);
+
+ switch (property_id)
+ {
+ case PROP_IMAGE:
+ gimp_item_set_image (item, g_value_get_object (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_item_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpItemPrivate *private = GET_PRIVATE (object);
+
+ switch (property_id)
+ {
+ case PROP_IMAGE:
+ g_value_set_object (value, private->image);
+ break;
+ case PROP_ID:
+ g_value_set_int (value, private->ID);
+ break;
+ case PROP_WIDTH:
+ g_value_set_int (value, private->width);
+ break;
+ case PROP_HEIGHT:
+ g_value_set_int (value, private->height);
+ break;
+ case PROP_OFFSET_X:
+ g_value_set_int (value, private->offset_x);
+ break;
+ case PROP_OFFSET_Y:
+ g_value_set_int (value, private->offset_y);
+ break;
+ case PROP_VISIBLE:
+ g_value_set_boolean (value, private->visible);
+ break;
+ case PROP_LINKED:
+ g_value_set_boolean (value, private->linked);
+ break;
+ case PROP_COLOR_TAG:
+ g_value_set_enum (value, private->color_tag);
+ break;
+ case PROP_LOCK_CONTENT:
+ g_value_set_boolean (value, private->lock_content);
+ break;
+ case PROP_LOCK_POSITION:
+ g_value_set_boolean (value, private->lock_position);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static gint64
+gimp_item_get_memsize (GimpObject *object,
+ gint64 *gui_size)
+{
+ GimpItemPrivate *private = GET_PRIVATE (object);
+ gint64 memsize = 0;
+
+ memsize += gimp_object_get_memsize (GIMP_OBJECT (private->parasites),
+ gui_size);
+
+ return memsize + GIMP_OBJECT_CLASS (parent_class)->get_memsize (object,
+ gui_size);
+}
+
+static gboolean
+gimp_item_real_is_content_locked (GimpItem *item)
+{
+ GimpItem *parent = gimp_item_get_parent (item);
+
+ if (parent && gimp_item_is_content_locked (parent))
+ return TRUE;
+
+ return GET_PRIVATE (item)->lock_content;
+}
+
+static gboolean
+gimp_item_real_is_position_locked (GimpItem *item)
+{
+ if (gimp_item_get_linked (item))
+ if (gimp_item_linked_is_locked (item))
+ return TRUE;
+
+ return GET_PRIVATE (item)->lock_position;
+}
+
+static gboolean
+gimp_item_real_bounds (GimpItem *item,
+ gdouble *x,
+ gdouble *y,
+ gdouble *width,
+ gdouble *height)
+{
+ GimpItemPrivate *private = GET_PRIVATE (item);
+
+ *x = 0;
+ *y = 0;
+ *width = private->width;
+ *height = private->height;
+
+ return TRUE;
+}
+
+static GimpItem *
+gimp_item_real_duplicate (GimpItem *item,
+ GType new_type)
+{
+ GimpItemPrivate *private;
+ GimpItem *new_item;
+ gchar *new_name;
+
+ g_return_val_if_fail (GIMP_IS_ITEM (item), NULL);
+
+ private = GET_PRIVATE (item);
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (private->image), NULL);
+ g_return_val_if_fail (g_type_is_a (new_type, GIMP_TYPE_ITEM), NULL);
+
+ /* formulate the new name */
+ {
+ const gchar *name;
+ gint len;
+
+ name = gimp_object_get_name (item);
+
+ g_return_val_if_fail (name != NULL, NULL);
+
+ len = strlen (_("copy"));
+
+ if ((strlen (name) >= len &&
+ strcmp (&name[strlen (name) - len], _("copy")) == 0) ||
+ g_regex_match_simple ("#([0-9]+)\\s*$", name, 0, 0))
+ {
+ /* don't have redundant "copy"s */
+ new_name = g_strdup (name);
+ }
+ else
+ {
+ new_name = g_strdup_printf (_("%s copy"), name);
+ }
+ }
+
+ new_item = gimp_item_new (new_type,
+ gimp_item_get_image (item), new_name,
+ private->offset_x, private->offset_y,
+ gimp_item_get_width (item),
+ gimp_item_get_height (item));
+
+ g_free (new_name);
+
+ gimp_viewable_set_expanded (GIMP_VIEWABLE (new_item),
+ gimp_viewable_get_expanded (GIMP_VIEWABLE (item)));
+
+ g_object_unref (GET_PRIVATE (new_item)->parasites);
+ GET_PRIVATE (new_item)->parasites = gimp_parasite_list_copy (private->parasites);
+
+ gimp_item_set_visible (new_item, gimp_item_get_visible (item), FALSE);
+ gimp_item_set_linked (new_item, gimp_item_get_linked (item), FALSE);
+ gimp_item_set_color_tag (new_item, gimp_item_get_color_tag (item), FALSE);
+
+ if (gimp_item_can_lock_content (new_item))
+ gimp_item_set_lock_content (new_item, gimp_item_get_lock_content (item),
+ FALSE);
+
+ if (gimp_item_can_lock_position (new_item))
+ gimp_item_set_lock_position (new_item, gimp_item_get_lock_position (item),
+ FALSE);
+
+ return new_item;
+}
+
+static void
+gimp_item_real_convert (GimpItem *item,
+ GimpImage *dest_image,
+ GType old_type)
+{
+ gimp_item_set_image (item, dest_image);
+}
+
+static gboolean
+gimp_item_real_rename (GimpItem *item,
+ const gchar *new_name,
+ const gchar *undo_desc,
+ GError **error)
+{
+ if (gimp_item_is_attached (item))
+ gimp_item_tree_rename_item (gimp_item_get_tree (item), item,
+ new_name, TRUE, undo_desc);
+ else
+ gimp_object_set_name (GIMP_OBJECT (item), new_name);
+
+ return TRUE;
+}
+
+static void
+gimp_item_real_translate (GimpItem *item,
+ gdouble offset_x,
+ gdouble offset_y,
+ gboolean push_undo)
+{
+ GimpItemPrivate *private = GET_PRIVATE (item);
+
+ gimp_item_set_offset (item,
+ private->offset_x + SIGNED_ROUND (offset_x),
+ private->offset_y + SIGNED_ROUND (offset_y));
+}
+
+static void
+gimp_item_real_start_transform (GimpItem *item,
+ gboolean push_undo)
+{
+ gimp_item_start_move (item, push_undo);
+}
+
+static void
+gimp_item_real_end_transform (GimpItem *item,
+ gboolean push_undo)
+{
+ gimp_item_end_move (item, push_undo);
+}
+
+static void
+gimp_item_real_scale (GimpItem *item,
+ gint new_width,
+ gint new_height,
+ gint new_offset_x,
+ gint new_offset_y,
+ GimpInterpolationType interpolation,
+ GimpProgress *progress)
+{
+ GimpItemPrivate *private = GET_PRIVATE (item);
+
+ if (private->width != new_width)
+ {
+ private->width = new_width;
+ g_object_notify (G_OBJECT (item), "width");
+ }
+
+ if (private->height != new_height)
+ {
+ private->height = new_height;
+ g_object_notify (G_OBJECT (item), "height");
+ }
+
+ gimp_item_set_offset (item, new_offset_x, new_offset_y);
+}
+
+static void
+gimp_item_real_resize (GimpItem *item,
+ GimpContext *context,
+ GimpFillType fill_type,
+ gint new_width,
+ gint new_height,
+ gint offset_x,
+ gint offset_y)
+{
+ GimpItemPrivate *private = GET_PRIVATE (item);
+
+ if (private->width != new_width)
+ {
+ private->width = new_width;
+ g_object_notify (G_OBJECT (item), "width");
+ }
+
+ if (private->height != new_height)
+ {
+ private->height = new_height;
+ g_object_notify (G_OBJECT (item), "height");
+ }
+
+ gimp_item_set_offset (item,
+ private->offset_x - offset_x,
+ private->offset_y - offset_y);
+}
+
+static GimpTransformResize
+gimp_item_real_get_clip (GimpItem *item,
+ GimpTransformResize clip_result)
+{
+ if (gimp_item_get_lock_position (item))
+ return GIMP_TRANSFORM_RESIZE_CLIP;
+ else
+ return clip_result;
+}
+
+
+/* public functions */
+
+/**
+ * gimp_item_new:
+ * @type: The new item's type.
+ * @image: The new item's #GimpImage.
+ * @name: The name to assign the item.
+ * @offset_x: The X offset to assign the item.
+ * @offset_y: The Y offset to assign the item.
+ * @width: The width to assign the item.
+ * @height: The height to assign the item.
+ *
+ * Return value: The newly created item.
+ */
+GimpItem *
+gimp_item_new (GType type,
+ GimpImage *image,
+ const gchar *name,
+ gint offset_x,
+ gint offset_y,
+ gint width,
+ gint height)
+{
+ GimpItem *item;
+ GimpItemPrivate *private;
+
+ g_return_val_if_fail (g_type_is_a (type, GIMP_TYPE_ITEM), NULL);
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+ g_return_val_if_fail (width > 0 && height > 0, NULL);
+
+ item = g_object_new (type,
+ "image", image,
+ NULL);
+
+ private = GET_PRIVATE (item);
+
+ private->width = width;
+ private->height = height;
+ gimp_item_set_offset (item, offset_x, offset_y);
+
+ if (name && strlen (name))
+ gimp_object_set_name (GIMP_OBJECT (item), name);
+ else
+ gimp_object_set_static_name (GIMP_OBJECT (item),
+ GIMP_ITEM_GET_CLASS (item)->default_name);
+
+ return item;
+}
+
+/**
+ * gimp_item_remove:
+ * @item: the #GimpItem to remove.
+ *
+ * This function sets the 'removed' flag on @item to #TRUE, and emits
+ * a 'removed' signal on the item.
+ */
+void
+gimp_item_removed (GimpItem *item)
+{
+ GimpContainer *children;
+
+ g_return_if_fail (GIMP_IS_ITEM (item));
+
+ GET_PRIVATE (item)->removed = TRUE;
+
+ children = gimp_viewable_get_children (GIMP_VIEWABLE (item));
+
+ if (children)
+ gimp_container_foreach (children, (GFunc) gimp_item_removed, NULL);
+
+ g_signal_emit (item, gimp_item_signals[REMOVED], 0);
+}
+
+/**
+ * gimp_item_is_removed:
+ * @item: the #GimpItem to check.
+ *
+ * Returns: %TRUE if the 'removed' flag is set for @item, %FALSE otherwise.
+ */
+gboolean
+gimp_item_is_removed (GimpItem *item)
+{
+ g_return_val_if_fail (GIMP_IS_ITEM (item), FALSE);
+
+ return GET_PRIVATE (item)->removed;
+}
+
+/**
+ * gimp_item_unset_removed:
+ * @item: a #GimpItem which was on the undo stack
+ *
+ * Unsets an item's "removed" state. This function is called when an
+ * item was on the undo stack and is added back to its parent
+ * container during and undo or redo. It must never be called from
+ * anywhere else.
+ **/
+void
+gimp_item_unset_removed (GimpItem *item)
+{
+ GimpContainer *children;
+
+ g_return_if_fail (GIMP_IS_ITEM (item));
+ g_return_if_fail (gimp_item_is_removed (item));
+
+ GET_PRIVATE (item)->removed = FALSE;
+
+ children = gimp_viewable_get_children (GIMP_VIEWABLE (item));
+
+ if (children)
+ gimp_container_foreach (children, (GFunc) gimp_item_unset_removed, NULL);
+
+ if (GIMP_ITEM_GET_CLASS (item)->unset_removed)
+ GIMP_ITEM_GET_CLASS (item)->unset_removed (item);
+}
+
+/**
+ * gimp_item_is_attached:
+ * @item: The #GimpItem to check.
+ *
+ * Returns: %TRUE if the item is attached to an image, %FALSE otherwise.
+ */
+gboolean
+gimp_item_is_attached (GimpItem *item)
+{
+ GimpItem *parent;
+
+ g_return_val_if_fail (GIMP_IS_ITEM (item), FALSE);
+
+ parent = gimp_item_get_parent (item);
+
+ if (parent)
+ return gimp_item_is_attached (parent);
+
+ return GIMP_ITEM_GET_CLASS (item)->is_attached (item);
+}
+
+GimpItem *
+gimp_item_get_parent (GimpItem *item)
+{
+ g_return_val_if_fail (GIMP_IS_ITEM (item), NULL);
+
+ return GIMP_ITEM (gimp_viewable_get_parent (GIMP_VIEWABLE (item)));
+}
+
+GimpItemTree *
+gimp_item_get_tree (GimpItem *item)
+{
+ g_return_val_if_fail (GIMP_IS_ITEM (item), NULL);
+
+ if (GIMP_ITEM_GET_CLASS (item)->get_tree)
+ return GIMP_ITEM_GET_CLASS (item)->get_tree (item);
+
+ return NULL;
+}
+
+GimpContainer *
+gimp_item_get_container (GimpItem *item)
+{
+ GimpItem *parent;
+ GimpItemTree *tree;
+
+ g_return_val_if_fail (GIMP_IS_ITEM (item), NULL);
+
+ parent = gimp_item_get_parent (item);
+
+ if (parent)
+ return gimp_viewable_get_children (GIMP_VIEWABLE (parent));
+
+ tree = gimp_item_get_tree (item);
+
+ if (tree)
+ return tree->container;
+
+ return NULL;
+}
+
+GList *
+gimp_item_get_container_iter (GimpItem *item)
+{
+ GimpContainer *container;
+
+ g_return_val_if_fail (GIMP_IS_ITEM (item), NULL);
+
+ container = gimp_item_get_container (item);
+
+ if (container)
+ return GIMP_LIST (container)->queue->head;
+
+ return NULL;
+}
+
+gint
+gimp_item_get_index (GimpItem *item)
+{
+ GimpContainer *container;
+
+ g_return_val_if_fail (GIMP_IS_ITEM (item), -1);
+
+ container = gimp_item_get_container (item);
+
+ if (container)
+ return gimp_container_get_child_index (container, GIMP_OBJECT (item));
+
+ return -1;
+}
+
+GList *
+gimp_item_get_path (GimpItem *item)
+{
+ GimpContainer *container;
+ GList *path = NULL;
+
+ g_return_val_if_fail (GIMP_IS_ITEM (item), NULL);
+ g_return_val_if_fail (gimp_item_is_attached (item), NULL);
+
+ container = gimp_item_get_container (item);
+
+ while (container)
+ {
+ guint32 index = gimp_container_get_child_index (container,
+ GIMP_OBJECT (item));
+
+ path = g_list_prepend (path, GUINT_TO_POINTER (index));
+
+ item = gimp_item_get_parent (item);
+
+ if (item)
+ container = gimp_item_get_container (item);
+ else
+ container = NULL;
+ }
+
+ return path;
+}
+
+gboolean
+gimp_item_bounds (GimpItem *item,
+ gint *x,
+ gint *y,
+ gint *width,
+ gint *height)
+{
+ gdouble tmp_x, tmp_y, tmp_width, tmp_height;
+ gboolean retval;
+
+ g_return_val_if_fail (GIMP_IS_ITEM (item), FALSE);
+
+ retval = GIMP_ITEM_GET_CLASS (item)->bounds (item,
+ &tmp_x, &tmp_y,
+ &tmp_width, &tmp_height);
+
+ if (x) *x = floor (tmp_x);
+ if (y) *y = floor (tmp_y);
+ if (width) *width = ceil (tmp_x + tmp_width) - floor (tmp_x);
+ if (height) *height = ceil (tmp_y + tmp_height) - floor (tmp_y);
+
+ return retval;
+}
+
+gboolean
+gimp_item_bounds_f (GimpItem *item,
+ gdouble *x,
+ gdouble *y,
+ gdouble *width,
+ gdouble *height)
+{
+ gdouble tmp_x, tmp_y, tmp_width, tmp_height;
+ gboolean retval;
+
+ g_return_val_if_fail (GIMP_IS_ITEM (item), FALSE);
+
+ retval = GIMP_ITEM_GET_CLASS (item)->bounds (item,
+ &tmp_x, &tmp_y,
+ &tmp_width, &tmp_height);
+
+ if (x) *x = tmp_x;
+ if (y) *y = tmp_y;
+ if (width) *width = tmp_width;
+ if (height) *height = tmp_height;
+
+ return retval;
+}
+
+/**
+ * gimp_item_duplicate:
+ * @item: The #GimpItem to duplicate.
+ * @new_type: The type to make the new item.
+ *
+ * Returns: the newly created item.
+ */
+GimpItem *
+gimp_item_duplicate (GimpItem *item,
+ GType new_type)
+{
+ GimpItemPrivate *private;
+
+ g_return_val_if_fail (GIMP_IS_ITEM (item), NULL);
+
+ private = GET_PRIVATE (item);
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (private->image), NULL);
+ g_return_val_if_fail (g_type_is_a (new_type, GIMP_TYPE_ITEM), NULL);
+
+ return GIMP_ITEM_GET_CLASS (item)->duplicate (item, new_type);
+}
+
+/**
+ * gimp_item_convert:
+ * @item: The #GimpItem to convert.
+ * @dest_image: The #GimpImage in which to place the converted item.
+ * @new_type: The type to convert the item to.
+ *
+ * Returns: the new item that results from the conversion.
+ */
+GimpItem *
+gimp_item_convert (GimpItem *item,
+ GimpImage *dest_image,
+ GType new_type)
+{
+ GimpItem *new_item;
+ GType old_type;
+
+ g_return_val_if_fail (GIMP_IS_ITEM (item), NULL);
+ g_return_val_if_fail (GIMP_IS_IMAGE (GET_PRIVATE (item)->image), NULL);
+ g_return_val_if_fail (GIMP_IS_IMAGE (dest_image), NULL);
+ g_return_val_if_fail (g_type_is_a (new_type, GIMP_TYPE_ITEM), NULL);
+
+ old_type = G_TYPE_FROM_INSTANCE (item);
+
+ new_item = gimp_item_duplicate (item, new_type);
+
+ if (new_item)
+ GIMP_ITEM_GET_CLASS (new_item)->convert (new_item, dest_image, old_type);
+
+ return new_item;
+}
+
+/**
+ * gimp_item_rename:
+ * @item: The #GimpItem to rename.
+ * @new_name: The new name to give the item.
+ * @error: Return location for error message.
+ *
+ * This function assigns a new name to the item, if the desired name is
+ * different from the name it already has, and pushes an entry onto the
+ * undo stack for the item's image. If @new_name is NULL or empty, the
+ * default name for the item's class is used. If the name is changed,
+ * the GimpObject::name-changed signal is emitted for the item.
+ *
+ * Returns: %TRUE if the @item could be renamed, %FALSE otherwise.
+ */
+gboolean
+gimp_item_rename (GimpItem *item,
+ const gchar *new_name,
+ GError **error)
+{
+ GimpItemClass *item_class;
+
+ g_return_val_if_fail (GIMP_IS_ITEM (item), FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ item_class = GIMP_ITEM_GET_CLASS (item);
+
+ if (! new_name || ! *new_name)
+ new_name = item_class->default_name;
+
+ if (strcmp (new_name, gimp_object_get_name (item)))
+ return item_class->rename (item, new_name, item_class->rename_desc, error);
+
+ return TRUE;
+}
+
+/**
+ * gimp_item_get_width:
+ * @item: The #GimpItem to check.
+ *
+ * Returns: The width of the item.
+ */
+gint
+gimp_item_get_width (GimpItem *item)
+{
+ g_return_val_if_fail (GIMP_IS_ITEM (item), -1);
+
+ return GET_PRIVATE (item)->width;
+}
+
+/**
+ * gimp_item_get_height:
+ * @item: The #GimpItem to check.
+ *
+ * Returns: The height of the item.
+ */
+gint
+gimp_item_get_height (GimpItem *item)
+{
+ g_return_val_if_fail (GIMP_IS_ITEM (item), -1);
+
+ return GET_PRIVATE (item)->height;
+}
+
+void
+gimp_item_set_size (GimpItem *item,
+ gint width,
+ gint height)
+{
+ GimpItemPrivate *private;
+
+ g_return_if_fail (GIMP_IS_ITEM (item));
+
+ private = GET_PRIVATE (item);
+
+ if (private->width != width ||
+ private->height != height)
+ {
+ g_object_freeze_notify (G_OBJECT (item));
+
+ if (private->width != width)
+ {
+ private->width = width;
+ g_object_notify (G_OBJECT (item), "width");
+ }
+
+ if (private->height != height)
+ {
+ private->height = height;
+ g_object_notify (G_OBJECT (item), "height");
+ }
+
+ g_object_thaw_notify (G_OBJECT (item));
+
+ gimp_viewable_size_changed (GIMP_VIEWABLE (item));
+ }
+}
+
+/**
+ * gimp_item_get_offset:
+ * @item: The #GimpItem to check.
+ * @offset_x: Return location for the item's X offset.
+ * @offset_y: Return location for the item's Y offset.
+ *
+ * Reveals the X and Y offsets of the item.
+ */
+void
+gimp_item_get_offset (GimpItem *item,
+ gint *offset_x,
+ gint *offset_y)
+{
+ GimpItemPrivate *private;
+
+ g_return_if_fail (GIMP_IS_ITEM (item));
+
+ private = GET_PRIVATE (item);
+
+ if (offset_x) *offset_x = private->offset_x;
+ if (offset_y) *offset_y = private->offset_y;
+}
+
+void
+gimp_item_set_offset (GimpItem *item,
+ gint offset_x,
+ gint offset_y)
+{
+ GimpItemPrivate *private;
+ GList *list;
+
+ g_return_if_fail (GIMP_IS_ITEM (item));
+
+ private = GET_PRIVATE (item);
+
+ g_object_freeze_notify (G_OBJECT (item));
+
+ if (private->offset_x != offset_x)
+ {
+ private->offset_x = offset_x;
+ g_object_notify (G_OBJECT (item), "offset-x");
+ }
+
+ if (private->offset_y != offset_y)
+ {
+ private->offset_y = offset_y;
+ g_object_notify (G_OBJECT (item), "offset-y");
+ }
+
+ for (list = private->offset_nodes; list; list = g_list_next (list))
+ {
+ GeglNode *node = list->data;
+
+ gegl_node_set (node,
+ "x", (gdouble) private->offset_x,
+ "y", (gdouble) private->offset_y,
+ NULL);
+ }
+
+ g_object_thaw_notify (G_OBJECT (item));
+}
+
+gint
+gimp_item_get_offset_x (GimpItem *item)
+{
+ g_return_val_if_fail (GIMP_IS_ITEM (item), 0);
+
+ return GET_PRIVATE (item)->offset_x;
+}
+
+gint
+gimp_item_get_offset_y (GimpItem *item)
+{
+ g_return_val_if_fail (GIMP_IS_ITEM (item), 0);
+
+ return GET_PRIVATE (item)->offset_y;
+}
+
+void
+gimp_item_start_move (GimpItem *item,
+ gboolean push_undo)
+{
+ g_return_if_fail (GIMP_IS_ITEM (item));
+
+ if (GIMP_ITEM_GET_CLASS (item)->start_move)
+ GIMP_ITEM_GET_CLASS (item)->start_move (item, push_undo);
+}
+
+void
+gimp_item_end_move (GimpItem *item,
+ gboolean push_undo)
+{
+ g_return_if_fail (GIMP_IS_ITEM (item));
+
+ if (GIMP_ITEM_GET_CLASS (item)->end_move)
+ GIMP_ITEM_GET_CLASS (item)->end_move (item, push_undo);
+}
+
+void
+gimp_item_start_transform (GimpItem *item,
+ gboolean push_undo)
+{
+ g_return_if_fail (GIMP_IS_ITEM (item));
+
+ if (GIMP_ITEM_GET_CLASS (item)->start_transform)
+ GIMP_ITEM_GET_CLASS (item)->start_transform (item, push_undo);
+}
+
+void
+gimp_item_end_transform (GimpItem *item,
+ gboolean push_undo)
+{
+ g_return_if_fail (GIMP_IS_ITEM (item));
+
+ if (GIMP_ITEM_GET_CLASS (item)->end_transform)
+ GIMP_ITEM_GET_CLASS (item)->end_transform (item, push_undo);
+}
+
+/**
+ * gimp_item_translate:
+ * @item: The #GimpItem to move.
+ * @offset_x: Increment to the X offset of the item.
+ * @offset_y: Increment to the Y offset of the item.
+ * @push_undo: If #TRUE, create an entry in the image's undo stack
+ * for this action.
+ *
+ * Adds the specified increments to the X and Y offsets for the item.
+ */
+void
+gimp_item_translate (GimpItem *item,
+ gdouble offset_x,
+ gdouble offset_y,
+ gboolean push_undo)
+{
+ GimpItemClass *item_class;
+ GimpImage *image;
+
+ g_return_if_fail (GIMP_IS_ITEM (item));
+
+ item_class = GIMP_ITEM_GET_CLASS (item);
+ image = gimp_item_get_image (item);
+
+ if (! gimp_item_is_attached (item))
+ push_undo = FALSE;
+
+ if (push_undo)
+ gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_ITEM_DISPLACE,
+ item_class->translate_desc);
+
+ gimp_item_start_transform (item, push_undo);
+
+ item_class->translate (item, offset_x, offset_y, push_undo);
+
+ gimp_item_end_transform (item, push_undo);
+
+ if (push_undo)
+ gimp_image_undo_group_end (image);
+}
+
+/**
+ * gimp_item_check_scaling:
+ * @item: Item to check
+ * @new_width: proposed width of item, in pixels
+ * @new_height: proposed height of item, in pixels
+ *
+ * Scales item dimensions, then snaps them to pixel centers
+ *
+ * Returns: #FALSE if any dimension reduces to zero as a result
+ * of this; otherwise, returns #TRUE.
+ **/
+gboolean
+gimp_item_check_scaling (GimpItem *item,
+ gint new_width,
+ gint new_height)
+{
+ GimpItemPrivate *private;
+ GimpImage *image;
+ gdouble img_scale_w;
+ gdouble img_scale_h;
+ gint new_item_offset_x;
+ gint new_item_offset_y;
+ gint new_item_width;
+ gint new_item_height;
+
+ g_return_val_if_fail (GIMP_IS_ITEM (item), FALSE);
+
+ private = GET_PRIVATE (item);
+ image = gimp_item_get_image (item);
+
+ img_scale_w = ((gdouble) new_width /
+ (gdouble) gimp_image_get_width (image));
+ img_scale_h = ((gdouble) new_height /
+ (gdouble) gimp_image_get_height (image));
+ new_item_offset_x = SIGNED_ROUND (img_scale_w * private->offset_x);
+ new_item_offset_y = SIGNED_ROUND (img_scale_h * private->offset_y);
+ new_item_width = SIGNED_ROUND (img_scale_w * (private->offset_x +
+ gimp_item_get_width (item))) -
+ new_item_offset_x;
+ new_item_height = SIGNED_ROUND (img_scale_h * (private->offset_y +
+ gimp_item_get_height (item))) -
+ new_item_offset_y;
+
+ return (new_item_width > 0 && new_item_height > 0);
+}
+
+void
+gimp_item_scale (GimpItem *item,
+ gint new_width,
+ gint new_height,
+ gint new_offset_x,
+ gint new_offset_y,
+ GimpInterpolationType interpolation,
+ GimpProgress *progress)
+{
+ GimpItemClass *item_class;
+ GimpImage *image;
+ gboolean push_undo;
+
+ g_return_if_fail (GIMP_IS_ITEM (item));
+ g_return_if_fail (progress == NULL || GIMP_IS_PROGRESS (progress));
+
+ if (new_width < 1 || new_height < 1)
+ return;
+
+ item_class = GIMP_ITEM_GET_CLASS (item);
+ image = gimp_item_get_image (item);
+
+ push_undo = gimp_item_is_attached (item);
+
+ if (push_undo)
+ gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_ITEM_SCALE,
+ item_class->scale_desc);
+
+ gimp_item_start_transform (item, push_undo);
+
+ g_object_freeze_notify (G_OBJECT (item));
+
+ item_class->scale (item, new_width, new_height, new_offset_x, new_offset_y,
+ interpolation, progress);
+
+ g_object_thaw_notify (G_OBJECT (item));
+
+ gimp_item_end_transform (item, push_undo);
+
+ if (push_undo)
+ gimp_image_undo_group_end (image);
+}
+
+/**
+ * gimp_item_scale_by_factors:
+ * @item: Item to be transformed by explicit width and height factors.
+ * @w_factor: scale factor to apply to width and horizontal offset
+ * @h_factor: scale factor to apply to height and vertical offset
+ * @interpolation:
+ * @progress:
+ *
+ * Scales item dimensions and offsets by uniform width and
+ * height factors.
+ *
+ * Use gimp_item_scale_by_factors() in circumstances when the same
+ * width and height scaling factors are to be uniformly applied to a
+ * set of items. In this context, the item's dimensions and offsets
+ * from the sides of the containing image all change by these
+ * predetermined factors. By fiat, the fixed point of the transform is
+ * the upper left hand corner of the image. Returns #FALSE if a
+ * requested scale factor is zero or if a scaling zero's out a item
+ * dimension; returns #TRUE otherwise.
+ *
+ * Use gimp_item_scale() in circumstances where new item width
+ * and height dimensions are predetermined instead.
+ *
+ * Side effects: Undo set created for item. Old item imagery
+ * scaled & painted to new item tiles.
+ *
+ * Returns: #TRUE, if the scaled item has positive dimensions
+ * #FALSE if the scaled item has at least one zero dimension
+ **/
+gboolean
+gimp_item_scale_by_factors (GimpItem *item,
+ gdouble w_factor,
+ gdouble h_factor,
+ GimpInterpolationType interpolation,
+ GimpProgress *progress)
+{
+ return gimp_item_scale_by_factors_with_origin (item,
+ w_factor, h_factor,
+ 0, 0, 0, 0,
+ interpolation, progress);
+}
+
+/**
+ * gimp_item_scale_by_factors:
+ * @item: Item to be transformed by explicit width and height factors.
+ * @w_factor: scale factor to apply to width and horizontal offset
+ * @h_factor: scale factor to apply to height and vertical offset
+ * @origin_x: x-coordinate of the transformation input origin
+ * @origin_y: y-coordinate of the transformation input origin
+ * @new_origin_x: x-coordinate of the transformation output origin
+ * @new_origin_y: y-coordinate of the transformation output origin
+ * @interpolation:
+ * @progress:
+ *
+ * Same as gimp_item_scale_by_factors(), but with the option to specify
+ * custom input and output points of origin for the transformation.
+ *
+ * Returns: #TRUE, if the scaled item has positive dimensions
+ * #FALSE if the scaled item has at least one zero dimension
+ **/
+gboolean
+gimp_item_scale_by_factors_with_origin (GimpItem *item,
+ gdouble w_factor,
+ gdouble h_factor,
+ gint origin_x,
+ gint origin_y,
+ gint new_origin_x,
+ gint new_origin_y,
+ GimpInterpolationType interpolation,
+ GimpProgress *progress)
+{
+ GimpItemPrivate *private;
+ GimpContainer *children;
+ gint new_width, new_height;
+ gint new_offset_x, new_offset_y;
+
+ g_return_val_if_fail (GIMP_IS_ITEM (item), FALSE);
+ g_return_val_if_fail (progress == NULL || GIMP_IS_PROGRESS (progress), FALSE);
+
+ private = GET_PRIVATE (item);
+
+ if (w_factor <= 0.0 || h_factor <= 0.0)
+ {
+ g_warning ("%s: requested width or height scale is non-positive",
+ G_STRFUNC);
+ return FALSE;
+ }
+
+ children = gimp_viewable_get_children (GIMP_VIEWABLE (item));
+
+ /* avoid discarding empty layer groups */
+ if (children && gimp_container_is_empty (children))
+ return TRUE;
+
+ new_offset_x = SIGNED_ROUND (w_factor * (private->offset_x - origin_x));
+ new_offset_y = SIGNED_ROUND (h_factor * (private->offset_y - origin_y));
+ new_width = SIGNED_ROUND (w_factor * (private->offset_x - origin_x +
+ gimp_item_get_width (item))) -
+ new_offset_x;
+ new_height = SIGNED_ROUND (h_factor * (private->offset_y - origin_y +
+ gimp_item_get_height (item))) -
+ new_offset_y;
+
+ new_offset_x += new_origin_x;
+ new_offset_y += new_origin_y;
+
+ if (new_width > 0 && new_height > 0)
+ {
+ gimp_item_scale (item,
+ new_width, new_height,
+ new_offset_x, new_offset_y,
+ interpolation, progress);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/**
+ * gimp_item_scale_by_origin:
+ * @item: The item to be transformed by width & height scale factors
+ * @new_width: The width that item will acquire
+ * @new_height: The height that the item will acquire
+ * @interpolation:
+ * @progress:
+ * @local_origin: sets fixed point of the scaling transform. See below.
+ *
+ * Sets item dimensions to new_width and
+ * new_height. Derives vertical and horizontal scaling
+ * transforms from new width and height. If local_origin is
+ * #TRUE, the fixed point of the scaling transform coincides
+ * with the item's center point. Otherwise, the fixed
+ * point is taken to be [-GimpItem::offset_x, -GimpItem::->offset_y].
+ *
+ * Since this function derives scale factors from new and
+ * current item dimensions, these factors will vary from
+ * item to item because of aliasing artifacts; factor
+ * variations among items can be quite large where item
+ * dimensions approach pixel dimensions. Use
+ * gimp_item_scale_by_factors() where constant scales are to
+ * be uniformly applied to a number of items.
+ *
+ * Side effects: undo set created for item.
+ * Old item imagery scaled
+ * & painted to new item tiles
+ **/
+void
+gimp_item_scale_by_origin (GimpItem *item,
+ gint new_width,
+ gint new_height,
+ GimpInterpolationType interpolation,
+ GimpProgress *progress,
+ gboolean local_origin)
+{
+ GimpItemPrivate *private;
+ gint new_offset_x, new_offset_y;
+
+ g_return_if_fail (GIMP_IS_ITEM (item));
+ g_return_if_fail (progress == NULL || GIMP_IS_PROGRESS (progress));
+
+ private = GET_PRIVATE (item);
+
+ if (new_width == 0 || new_height == 0)
+ {
+ g_warning ("%s: requested width or height equals zero", G_STRFUNC);
+ return;
+ }
+
+ if (local_origin)
+ {
+ new_offset_x = (private->offset_x +
+ ((gimp_item_get_width (item) - new_width) / 2.0));
+ new_offset_y = (private->offset_y +
+ ((gimp_item_get_height (item) - new_height) / 2.0));
+ }
+ else
+ {
+ new_offset_x = (gint) (((gdouble) new_width *
+ (gdouble) private->offset_x /
+ (gdouble) gimp_item_get_width (item)));
+
+ new_offset_y = (gint) (((gdouble) new_height *
+ (gdouble) private->offset_y /
+ (gdouble) gimp_item_get_height (item)));
+ }
+
+ gimp_item_scale (item,
+ new_width, new_height,
+ new_offset_x, new_offset_y,
+ interpolation, progress);
+}
+
+void
+gimp_item_resize (GimpItem *item,
+ GimpContext *context,
+ GimpFillType fill_type,
+ gint new_width,
+ gint new_height,
+ gint offset_x,
+ gint offset_y)
+{
+ GimpItemClass *item_class;
+ GimpImage *image;
+ gboolean push_undo;
+
+ g_return_if_fail (GIMP_IS_ITEM (item));
+ g_return_if_fail (GIMP_IS_CONTEXT (context));
+
+ if (new_width < 1 || new_height < 1)
+ return;
+
+ item_class = GIMP_ITEM_GET_CLASS (item);
+ image = gimp_item_get_image (item);
+
+ push_undo = gimp_item_is_attached (item);
+
+ if (push_undo)
+ gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_ITEM_RESIZE,
+ item_class->resize_desc);
+
+ /* note that we call gimp_item_start_move(), and not
+ * gimp_item_start_transform(). whether or not a resize operation should be
+ * considered a transform operation, or a move operation, depends on the
+ * intended use of these functions by subclasses. atm, we only use
+ * gimp_item_{start,end}_transform() to suspend mask resizing in group
+ * layers, which should not happen when reisizing a group, hence the call to
+ * gimp_item_start_move().
+ *
+ * see the comment in gimp_group_layer_resize() for more information.
+ */
+ gimp_item_start_move (item, push_undo);
+
+ g_object_freeze_notify (G_OBJECT (item));
+
+ item_class->resize (item, context, fill_type,
+ new_width, new_height, offset_x, offset_y);
+
+ g_object_thaw_notify (G_OBJECT (item));
+
+ gimp_item_end_move (item, push_undo);
+
+ if (push_undo)
+ gimp_image_undo_group_end (image);
+}
+
+void
+gimp_item_flip (GimpItem *item,
+ GimpContext *context,
+ GimpOrientationType flip_type,
+ gdouble axis,
+ gboolean clip_result)
+{
+ GimpItemClass *item_class;
+ GimpImage *image;
+ gboolean push_undo;
+
+ g_return_if_fail (GIMP_IS_ITEM (item));
+ g_return_if_fail (gimp_item_is_attached (item));
+ g_return_if_fail (GIMP_IS_CONTEXT (context));
+
+ item_class = GIMP_ITEM_GET_CLASS (item);
+ image = gimp_item_get_image (item);
+
+ push_undo = gimp_item_is_attached (item);
+
+ if (push_undo)
+ gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_TRANSFORM,
+ item_class->flip_desc);
+
+ gimp_item_start_transform (item, push_undo);
+
+ g_object_freeze_notify (G_OBJECT (item));
+
+ item_class->flip (item, context, flip_type, axis, clip_result);
+
+ g_object_thaw_notify (G_OBJECT (item));
+
+ gimp_item_end_transform (item, push_undo);
+
+ if (push_undo)
+ gimp_image_undo_group_end (image);
+}
+
+void
+gimp_item_rotate (GimpItem *item,
+ GimpContext *context,
+ GimpRotationType rotate_type,
+ gdouble center_x,
+ gdouble center_y,
+ gboolean clip_result)
+{
+ GimpItemClass *item_class;
+ GimpImage *image;
+ gboolean push_undo;
+
+ g_return_if_fail (GIMP_IS_ITEM (item));
+ g_return_if_fail (gimp_item_is_attached (item));
+ g_return_if_fail (GIMP_IS_CONTEXT (context));
+
+ item_class = GIMP_ITEM_GET_CLASS (item);
+ image = gimp_item_get_image (item);
+
+ push_undo = gimp_item_is_attached (item);
+
+ if (push_undo)
+ gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_TRANSFORM,
+ item_class->rotate_desc);
+
+ gimp_item_start_transform (item, push_undo);
+
+ g_object_freeze_notify (G_OBJECT (item));
+
+ item_class->rotate (item, context, rotate_type, center_x, center_y,
+ clip_result);
+
+ g_object_thaw_notify (G_OBJECT (item));
+
+ gimp_item_end_transform (item, push_undo);
+
+ if (push_undo)
+ gimp_image_undo_group_end (image);
+}
+
+void
+gimp_item_transform (GimpItem *item,
+ GimpContext *context,
+ const GimpMatrix3 *matrix,
+ GimpTransformDirection direction,
+ GimpInterpolationType interpolation,
+ GimpTransformResize clip_result,
+ GimpProgress *progress)
+{
+ GimpItemClass *item_class;
+ GimpImage *image;
+ gboolean push_undo;
+
+ g_return_if_fail (GIMP_IS_ITEM (item));
+ g_return_if_fail (gimp_item_is_attached (item));
+ g_return_if_fail (GIMP_IS_CONTEXT (context));
+ g_return_if_fail (matrix != NULL);
+ g_return_if_fail (progress == NULL || GIMP_IS_PROGRESS (progress));
+
+ item_class = GIMP_ITEM_GET_CLASS (item);
+ image = gimp_item_get_image (item);
+
+ push_undo = gimp_item_is_attached (item);
+
+ if (push_undo)
+ gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_TRANSFORM,
+ item_class->transform_desc);
+
+ gimp_item_start_transform (item, push_undo);
+
+ g_object_freeze_notify (G_OBJECT (item));
+
+ item_class->transform (item, context, matrix, direction, interpolation,
+ clip_result, progress);
+
+ g_object_thaw_notify (G_OBJECT (item));
+
+ gimp_item_end_transform (item, push_undo);
+
+ if (push_undo)
+ gimp_image_undo_group_end (image);
+}
+
+GimpTransformResize
+gimp_item_get_clip (GimpItem *item,
+ GimpTransformResize clip_result)
+{
+ g_return_val_if_fail (GIMP_IS_ITEM (item), GIMP_TRANSFORM_RESIZE_ADJUST);
+
+ return GIMP_ITEM_GET_CLASS (item)->get_clip (item, clip_result);
+}
+
+gboolean
+gimp_item_fill (GimpItem *item,
+ GimpDrawable *drawable,
+ GimpFillOptions *fill_options,
+ gboolean push_undo,
+ GimpProgress *progress,
+ GError **error)
+{
+ GimpItemClass *item_class;
+ gboolean retval = FALSE;
+
+ g_return_val_if_fail (GIMP_IS_ITEM (item), FALSE);
+ g_return_val_if_fail (gimp_item_is_attached (item), FALSE);
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), FALSE);
+ g_return_val_if_fail (gimp_item_is_attached (GIMP_ITEM (drawable)), FALSE);
+ g_return_val_if_fail (GIMP_IS_FILL_OPTIONS (fill_options), FALSE);
+ g_return_val_if_fail (progress == NULL || GIMP_IS_PROGRESS (progress), FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ item_class = GIMP_ITEM_GET_CLASS (item);
+
+ if (item_class->fill)
+ {
+ GimpImage *image = gimp_item_get_image (item);
+
+ if (push_undo)
+ gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_PAINT,
+ item_class->fill_desc);
+
+ retval = item_class->fill (item, drawable, fill_options, push_undo,
+ progress, error);
+
+ if (push_undo)
+ gimp_image_undo_group_end (image);
+ }
+
+ return retval;
+}
+
+gboolean
+gimp_item_stroke (GimpItem *item,
+ GimpDrawable *drawable,
+ GimpContext *context,
+ GimpStrokeOptions *stroke_options,
+ GimpPaintOptions *paint_options,
+ gboolean push_undo,
+ GimpProgress *progress,
+ GError **error)
+{
+ GimpItemClass *item_class;
+ gboolean retval = FALSE;
+
+ g_return_val_if_fail (GIMP_IS_ITEM (item), FALSE);
+ g_return_val_if_fail (gimp_item_is_attached (item), FALSE);
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), FALSE);
+ g_return_val_if_fail (gimp_item_is_attached (GIMP_ITEM (drawable)), FALSE);
+ g_return_val_if_fail (GIMP_IS_CONTEXT (context), FALSE);
+ g_return_val_if_fail (GIMP_IS_STROKE_OPTIONS (stroke_options), FALSE);
+ g_return_val_if_fail (paint_options == NULL ||
+ GIMP_IS_PAINT_OPTIONS (paint_options), FALSE);
+ g_return_val_if_fail (progress == NULL || GIMP_IS_PROGRESS (progress), FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ item_class = GIMP_ITEM_GET_CLASS (item);
+
+ if (item_class->stroke)
+ {
+ GimpImage *image = gimp_item_get_image (item);
+
+ gimp_stroke_options_prepare (stroke_options, context, paint_options);
+
+ if (push_undo)
+ gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_PAINT,
+ item_class->stroke_desc);
+
+ retval = item_class->stroke (item, drawable, stroke_options, push_undo,
+ progress, error);
+
+ if (push_undo)
+ gimp_image_undo_group_end (image);
+
+ gimp_stroke_options_finish (stroke_options);
+ }
+
+ return retval;
+}
+
+void
+gimp_item_to_selection (GimpItem *item,
+ GimpChannelOps op,
+ gboolean antialias,
+ gboolean feather,
+ gdouble feather_radius_x,
+ gdouble feather_radius_y)
+{
+ GimpItemClass *item_class;
+
+ g_return_if_fail (GIMP_IS_ITEM (item));
+ g_return_if_fail (gimp_item_is_attached (item));
+
+ item_class = GIMP_ITEM_GET_CLASS (item);
+
+ if (item_class->to_selection)
+ item_class->to_selection (item, op, antialias,
+ feather, feather_radius_x, feather_radius_y);
+}
+
+void
+gimp_item_add_offset_node (GimpItem *item,
+ GeglNode *node)
+{
+ GimpItemPrivate *private;
+
+ g_return_if_fail (GIMP_IS_ITEM (item));
+ g_return_if_fail (GEGL_IS_NODE (node));
+
+ private = GET_PRIVATE (item);
+
+ g_return_if_fail (g_list_find (private->offset_nodes, node) == NULL);
+
+ gegl_node_set (node,
+ "x", (gdouble) private->offset_x,
+ "y", (gdouble) private->offset_y,
+ NULL);
+
+ private->offset_nodes = g_list_append (private->offset_nodes,
+ g_object_ref (node));
+}
+
+void
+gimp_item_remove_offset_node (GimpItem *item,
+ GeglNode *node)
+{
+ GimpItemPrivate *private;
+
+ g_return_if_fail (GIMP_IS_ITEM (item));
+ g_return_if_fail (GEGL_IS_NODE (node));
+
+ private = GET_PRIVATE (item);
+
+ g_return_if_fail (g_list_find (private->offset_nodes, node) != NULL);
+
+ private->offset_nodes = g_list_remove (private->offset_nodes, node);
+ g_object_unref (node);
+}
+
+gint
+gimp_item_get_ID (GimpItem *item)
+{
+ g_return_val_if_fail (GIMP_IS_ITEM (item), -1);
+
+ return GET_PRIVATE (item)->ID;
+}
+
+GimpItem *
+gimp_item_get_by_ID (Gimp *gimp,
+ gint item_id)
+{
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
+
+ if (gimp->item_table == NULL)
+ return NULL;
+
+ return (GimpItem *) gimp_id_table_lookup (gimp->item_table, item_id);
+}
+
+GimpTattoo
+gimp_item_get_tattoo (GimpItem *item)
+{
+ g_return_val_if_fail (GIMP_IS_ITEM (item), 0);
+
+ return GET_PRIVATE (item)->tattoo;
+}
+
+void
+gimp_item_set_tattoo (GimpItem *item,
+ GimpTattoo tattoo)
+{
+ g_return_if_fail (GIMP_IS_ITEM (item));
+
+ GET_PRIVATE (item)->tattoo = tattoo;
+}
+
+GimpImage *
+gimp_item_get_image (GimpItem *item)
+{
+ g_return_val_if_fail (GIMP_IS_ITEM (item), NULL);
+
+ return GET_PRIVATE (item)->image;
+}
+
+void
+gimp_item_set_image (GimpItem *item,
+ GimpImage *image)
+{
+ GimpItemPrivate *private;
+
+ g_return_if_fail (GIMP_IS_ITEM (item));
+ g_return_if_fail (! gimp_item_is_attached (item));
+ g_return_if_fail (! gimp_item_is_removed (item));
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+
+ private = GET_PRIVATE (item);
+
+ if (image == private->image)
+ return;
+
+ g_object_freeze_notify (G_OBJECT (item));
+
+ if (private->ID == 0)
+ {
+ private->ID = gimp_id_table_insert (image->gimp->item_table, item);
+
+ g_object_notify (G_OBJECT (item), "id");
+ }
+
+ if (private->tattoo == 0 || private->image != image)
+ {
+ private->tattoo = gimp_image_get_new_tattoo (image);
+ }
+
+ private->image = image;
+ g_object_notify (G_OBJECT (item), "image");
+
+ g_object_thaw_notify (G_OBJECT (item));
+}
+
+/**
+ * gimp_item_replace_item:
+ * @item: a newly allocated #GimpItem
+ * @replace: the #GimpItem to be replaced by @item
+ *
+ * This function shouly only be called right after @item has been
+ * newly allocated.
+ *
+ * Replaces @replace by @item, as far as possible within the #GimpItem
+ * class. The new @item takes over @replace's ID, tattoo, offset, size
+ * etc. and all these properties are set to %NULL on @replace.
+ *
+ * This function *only* exists to allow subclasses to do evil hacks
+ * like in XCF text layer loading. Don't ever use this function if you
+ * are not sure.
+ *
+ * After this function returns, @replace has become completely
+ * unusable, should only be used to steal everything it has (like its
+ * drawable properties if it's a drawable), and then be destroyed.
+ **/
+void
+gimp_item_replace_item (GimpItem *item,
+ GimpItem *replace)
+{
+ GimpItemPrivate *private;
+ gint offset_x;
+ gint offset_y;
+
+ g_return_if_fail (GIMP_IS_ITEM (item));
+ g_return_if_fail (! gimp_item_is_attached (item));
+ g_return_if_fail (! gimp_item_is_removed (item));
+ g_return_if_fail (GIMP_IS_ITEM (replace));
+
+ private = GET_PRIVATE (item);
+
+ gimp_object_set_name (GIMP_OBJECT (item), gimp_object_get_name (replace));
+
+ if (private->ID)
+ gimp_id_table_remove (gimp_item_get_image (item)->gimp->item_table,
+ gimp_item_get_ID (item));
+
+ private->ID = gimp_item_get_ID (replace);
+ gimp_id_table_replace (gimp_item_get_image (item)->gimp->item_table,
+ gimp_item_get_ID (item),
+ item);
+
+ /* Set image before tattoo so that the explicitly set tattoo overrides
+ * the one implicitly set when setting the image
+ */
+ gimp_item_set_image (item, gimp_item_get_image (replace));
+ GET_PRIVATE (replace)->image = NULL;
+
+ gimp_item_set_tattoo (item, gimp_item_get_tattoo (replace));
+ gimp_item_set_tattoo (replace, 0);
+
+ g_object_unref (private->parasites);
+ private->parasites = GET_PRIVATE (replace)->parasites;
+ GET_PRIVATE (replace)->parasites = NULL;
+
+ gimp_item_get_offset (replace, &offset_x, &offset_y);
+ gimp_item_set_offset (item, offset_x, offset_y);
+
+ gimp_item_set_size (item,
+ gimp_item_get_width (replace),
+ gimp_item_get_height (replace));
+
+ gimp_item_set_visible (item, gimp_item_get_visible (replace), FALSE);
+ gimp_item_set_linked (item, gimp_item_get_linked (replace), FALSE);
+ gimp_item_set_color_tag (item, gimp_item_get_color_tag (replace), FALSE);
+ gimp_item_set_lock_content (item, gimp_item_get_lock_content (replace), FALSE);
+ gimp_item_set_lock_position (item, gimp_item_get_lock_position (replace), FALSE);
+}
+
+/**
+ * gimp_item_set_parasites:
+ * @item: a #GimpItem
+ * @parasites: a #GimpParasiteList
+ *
+ * Set an @item's #GimpParasiteList. It's usually never needed to
+ * fiddle with an item's parasite list directly. This function exists
+ * for special purposes only, like when creating items from unusual
+ * sources.
+ **/
+void
+gimp_item_set_parasites (GimpItem *item,
+ GimpParasiteList *parasites)
+{
+ GimpItemPrivate *private;
+
+ g_return_if_fail (GIMP_IS_ITEM (item));
+ g_return_if_fail (GIMP_IS_PARASITE_LIST (parasites));
+
+ private = GET_PRIVATE (item);
+
+ g_set_object (&private->parasites, parasites);
+}
+
+/**
+ * gimp_item_get_parasites:
+ * @item: a #GimpItem
+ *
+ * Get an @item's #GimpParasiteList. It's usually never needed to
+ * fiddle with an item's parasite list directly. This function exists
+ * for special purposes only, like when saving an item to XCF.
+ *
+ * Return value: The @item's #GimpParasiteList.
+ **/
+GimpParasiteList *
+gimp_item_get_parasites (GimpItem *item)
+{
+ g_return_val_if_fail (GIMP_IS_ITEM (item), NULL);
+
+ return GET_PRIVATE (item)->parasites;
+}
+
+gboolean
+gimp_item_parasite_validate (GimpItem *item,
+ const GimpParasite *parasite,
+ GError **error)
+{
+ g_return_val_if_fail (GIMP_IS_ITEM (item), FALSE);
+ g_return_val_if_fail (parasite != NULL, FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ return TRUE;
+}
+
+void
+gimp_item_parasite_attach (GimpItem *item,
+ const GimpParasite *parasite,
+ gboolean push_undo)
+{
+ GimpItemPrivate *private;
+ GimpParasite copy;
+
+ g_return_if_fail (GIMP_IS_ITEM (item));
+ g_return_if_fail (parasite != NULL);
+
+ private = GET_PRIVATE (item);
+
+ /* make a temporary copy of the GimpParasite struct because
+ * gimp_parasite_shift_parent() changes it
+ */
+ copy = *parasite;
+
+ if (! gimp_item_is_attached (item))
+ push_undo = FALSE;
+
+ if (push_undo)
+ {
+ /* only set the dirty bit manually if we can be saved and the new
+ * parasite differs from the current one and we aren't undoable
+ */
+ if (gimp_parasite_is_undoable (&copy))
+ {
+ /* do a group in case we have attach_parent set */
+ gimp_image_undo_group_start (private->image,
+ GIMP_UNDO_GROUP_PARASITE_ATTACH,
+ C_("undo-type", "Attach Parasite"));
+
+ gimp_image_undo_push_item_parasite (private->image, NULL, item, &copy);
+ }
+ else if (gimp_parasite_is_persistent (&copy) &&
+ ! gimp_parasite_compare (&copy,
+ gimp_item_parasite_find
+ (item, gimp_parasite_name (&copy))))
+ {
+ gimp_image_undo_push_cantundo (private->image,
+ C_("undo-type", "Attach Parasite to Item"));
+ }
+ }
+
+ gimp_parasite_list_add (private->parasites, &copy);
+
+ if (gimp_parasite_has_flag (&copy, GIMP_PARASITE_ATTACH_PARENT))
+ {
+ gimp_parasite_shift_parent (&copy);
+ gimp_image_parasite_attach (private->image, &copy, TRUE);
+ }
+ else if (gimp_parasite_has_flag (&copy, GIMP_PARASITE_ATTACH_GRANDPARENT))
+ {
+ gimp_parasite_shift_parent (&copy);
+ gimp_parasite_shift_parent (&copy);
+ gimp_parasite_attach (private->image->gimp, &copy);
+ }
+
+ if (gimp_item_is_attached (item) &&
+ gimp_parasite_is_undoable (&copy))
+ {
+ gimp_image_undo_group_end (private->image);
+ }
+}
+
+void
+gimp_item_parasite_detach (GimpItem *item,
+ const gchar *name,
+ gboolean push_undo)
+{
+ GimpItemPrivate *private;
+ const GimpParasite *parasite;
+
+ g_return_if_fail (GIMP_IS_ITEM (item));
+ g_return_if_fail (name != NULL);
+
+ private = GET_PRIVATE (item);
+
+ parasite = gimp_parasite_list_find (private->parasites, name);
+
+ if (! parasite)
+ return;
+
+ if (! gimp_item_is_attached (item))
+ push_undo = FALSE;
+
+ if (push_undo)
+ {
+ if (gimp_parasite_is_undoable (parasite))
+ {
+ gimp_image_undo_push_item_parasite_remove (private->image,
+ C_("undo-type", "Remove Parasite from Item"),
+ item,
+ gimp_parasite_name (parasite));
+ }
+ else if (gimp_parasite_is_persistent (parasite))
+ {
+ gimp_image_undo_push_cantundo (private->image,
+ C_("undo-type", "Remove Parasite from Item"));
+ }
+ }
+
+ gimp_parasite_list_remove (private->parasites, name);
+}
+
+const GimpParasite *
+gimp_item_parasite_find (GimpItem *item,
+ const gchar *name)
+{
+ g_return_val_if_fail (GIMP_IS_ITEM (item), NULL);
+
+ return gimp_parasite_list_find (GET_PRIVATE (item)->parasites, name);
+}
+
+static void
+gimp_item_parasite_list_foreach_func (gchar *name,
+ GimpParasite *parasite,
+ gchar ***cur)
+{
+ *(*cur)++ = (gchar *) g_strdup (name);
+}
+
+gchar **
+gimp_item_parasite_list (GimpItem *item,
+ gint *count)
+{
+ GimpItemPrivate *private;
+ gchar **list;
+ gchar **cur;
+
+ g_return_val_if_fail (GIMP_IS_ITEM (item), NULL);
+ g_return_val_if_fail (count != NULL, NULL);
+
+ private = GET_PRIVATE (item);
+
+ *count = gimp_parasite_list_length (private->parasites);
+
+ cur = list = g_new (gchar *, *count);
+
+ gimp_parasite_list_foreach (private->parasites,
+ (GHFunc) gimp_item_parasite_list_foreach_func,
+ &cur);
+
+ return list;
+}
+
+void
+gimp_item_set_visible (GimpItem *item,
+ gboolean visible,
+ gboolean push_undo)
+{
+ g_return_if_fail (GIMP_IS_ITEM (item));
+
+ visible = visible ? TRUE : FALSE;
+
+ if (gimp_item_get_visible (item) != visible)
+ {
+ if (push_undo && gimp_item_is_attached (item))
+ {
+ GimpImage *image = gimp_item_get_image (item);
+
+ if (image)
+ gimp_image_undo_push_item_visibility (image, NULL, item);
+ }
+
+ GET_PRIVATE (item)->visible = visible;
+
+ if (GET_PRIVATE (item)->bind_visible_to_active)
+ gimp_filter_set_active (GIMP_FILTER (item), visible);
+
+ g_signal_emit (item, gimp_item_signals[VISIBILITY_CHANGED], 0);
+
+ g_object_notify (G_OBJECT (item), "visible");
+ }
+}
+
+gboolean
+gimp_item_get_visible (GimpItem *item)
+{
+ g_return_val_if_fail (GIMP_IS_ITEM (item), FALSE);
+
+ return GET_PRIVATE (item)->visible;
+}
+
+gboolean
+gimp_item_is_visible (GimpItem *item)
+{
+ g_return_val_if_fail (GIMP_IS_ITEM (item), FALSE);
+
+ if (gimp_item_get_visible (item))
+ {
+ GimpItem *parent;
+
+ parent = GIMP_ITEM (gimp_viewable_get_parent (GIMP_VIEWABLE (item)));
+
+ if (parent)
+ return gimp_item_is_visible (parent);
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+void
+gimp_item_bind_visible_to_active (GimpItem *item,
+ gboolean bind)
+{
+ g_return_if_fail (GIMP_IS_ITEM (item));
+
+ GET_PRIVATE (item)->bind_visible_to_active = bind;
+
+ if (bind)
+ gimp_filter_set_active (GIMP_FILTER (item), gimp_item_get_visible (item));
+}
+
+void
+gimp_item_set_linked (GimpItem *item,
+ gboolean linked,
+ gboolean push_undo)
+{
+ g_return_if_fail (GIMP_IS_ITEM (item));
+
+ linked = linked ? TRUE : FALSE;
+
+ if (gimp_item_get_linked (item) != linked)
+ {
+ GimpImage *image = gimp_item_get_image (item);
+ gboolean is_attached = gimp_item_is_attached (item);
+
+ if (push_undo && is_attached && image)
+ gimp_image_undo_push_item_linked (image, NULL, item);
+
+ GET_PRIVATE (item)->linked = linked;
+
+ g_signal_emit (item, gimp_item_signals[LINKED_CHANGED], 0);
+
+ if (is_attached && image)
+ gimp_image_linked_items_changed (image);
+
+ g_object_notify (G_OBJECT (item), "linked");
+ }
+}
+
+gboolean
+gimp_item_get_linked (GimpItem *item)
+{
+ g_return_val_if_fail (GIMP_IS_ITEM (item), FALSE);
+
+ return GET_PRIVATE (item)->linked;
+}
+
+void
+gimp_item_set_color_tag (GimpItem *item,
+ GimpColorTag color_tag,
+ gboolean push_undo)
+{
+ g_return_if_fail (GIMP_IS_ITEM (item));
+
+ if (gimp_item_get_color_tag (item) != color_tag)
+ {
+ if (push_undo && gimp_item_is_attached (item))
+ {
+ GimpImage *image = gimp_item_get_image (item);
+
+ if (image)
+ gimp_image_undo_push_item_color_tag (image, NULL, item);
+ }
+
+ GET_PRIVATE (item)->color_tag = color_tag;
+
+ g_signal_emit (item, gimp_item_signals[COLOR_TAG_CHANGED], 0);
+
+ g_object_notify (G_OBJECT (item), "color-tag");
+ }
+}
+
+GimpColorTag
+gimp_item_get_color_tag (GimpItem *item)
+{
+ g_return_val_if_fail (GIMP_IS_ITEM (item), GIMP_COLOR_TAG_NONE);
+
+ return GET_PRIVATE (item)->color_tag;
+}
+
+GimpColorTag
+gimp_item_get_merged_color_tag (GimpItem *item)
+{
+ g_return_val_if_fail (GIMP_IS_ITEM (item), GIMP_COLOR_TAG_NONE);
+
+ if (gimp_item_get_color_tag (item) == GIMP_COLOR_TAG_NONE)
+ {
+ GimpItem *parent;
+
+ parent = GIMP_ITEM (gimp_viewable_get_parent (GIMP_VIEWABLE (item)));
+
+ if (parent)
+ return gimp_item_get_merged_color_tag (parent);
+ }
+
+ return gimp_item_get_color_tag (item);
+}
+
+void
+gimp_item_set_lock_content (GimpItem *item,
+ gboolean lock_content,
+ gboolean push_undo)
+{
+ g_return_if_fail (GIMP_IS_ITEM (item));
+ g_return_if_fail (gimp_item_can_lock_content (item));
+
+ lock_content = lock_content ? TRUE : FALSE;
+
+ if (gimp_item_get_lock_content (item) != lock_content)
+ {
+ if (push_undo && gimp_item_is_attached (item))
+ {
+ /* Right now I don't think this should be pushed. */
+#if 0
+ GimpImage *image = gimp_item_get_image (item);
+
+ gimp_image_undo_push_item_lock_content (image, NULL, item);
+#endif
+ }
+
+ GET_PRIVATE (item)->lock_content = lock_content;
+
+ g_signal_emit (item, gimp_item_signals[LOCK_CONTENT_CHANGED], 0);
+
+ g_object_notify (G_OBJECT (item), "lock-content");
+ }
+}
+
+gboolean
+gimp_item_get_lock_content (GimpItem *item)
+{
+ g_return_val_if_fail (GIMP_IS_ITEM (item), FALSE);
+
+ return GET_PRIVATE (item)->lock_content;
+}
+
+gboolean
+gimp_item_can_lock_content (GimpItem *item)
+{
+ g_return_val_if_fail (GIMP_IS_ITEM (item), FALSE);
+
+ return TRUE;
+}
+
+gboolean
+gimp_item_is_content_locked (GimpItem *item)
+{
+ g_return_val_if_fail (GIMP_IS_ITEM (item), FALSE);
+
+ return GIMP_ITEM_GET_CLASS (item)->is_content_locked (item);
+}
+
+void
+gimp_item_set_lock_position (GimpItem *item,
+ gboolean lock_position,
+ gboolean push_undo)
+{
+ g_return_if_fail (GIMP_IS_ITEM (item));
+ g_return_if_fail (gimp_item_can_lock_position (item));
+
+ lock_position = lock_position ? TRUE : FALSE;
+
+ if (gimp_item_get_lock_position (item) != lock_position)
+ {
+ if (push_undo && gimp_item_is_attached (item))
+ {
+ GimpImage *image = gimp_item_get_image (item);
+
+ gimp_image_undo_push_item_lock_position (image, NULL, item);
+ }
+
+ GET_PRIVATE (item)->lock_position = lock_position;
+
+ g_signal_emit (item, gimp_item_signals[LOCK_POSITION_CHANGED], 0);
+
+ g_object_notify (G_OBJECT (item), "lock-position");
+ }
+}
+
+gboolean
+gimp_item_get_lock_position (GimpItem *item)
+{
+ g_return_val_if_fail (GIMP_IS_ITEM (item), FALSE);
+
+ return GET_PRIVATE (item)->lock_position;
+}
+
+gboolean
+gimp_item_can_lock_position (GimpItem *item)
+{
+ g_return_val_if_fail (GIMP_IS_ITEM (item), FALSE);
+
+ if (gimp_viewable_get_children (GIMP_VIEWABLE (item)))
+ return FALSE;
+
+ return TRUE;
+}
+
+gboolean
+gimp_item_is_position_locked (GimpItem *item)
+{
+ g_return_val_if_fail (GIMP_IS_ITEM (item), FALSE);
+
+ return GIMP_ITEM_GET_CLASS (item)->is_position_locked (item);
+}
+
+gboolean
+gimp_item_mask_bounds (GimpItem *item,
+ gint *x1,
+ gint *y1,
+ gint *x2,
+ gint *y2)
+{
+ GimpImage *image;
+ GimpChannel *selection;
+ gint x, y, width, height;
+ gboolean retval;
+
+ g_return_val_if_fail (GIMP_IS_ITEM (item), FALSE);
+ g_return_val_if_fail (gimp_item_is_attached (item), FALSE);
+
+ image = gimp_item_get_image (item);
+ selection = gimp_image_get_mask (image);
+
+ /* check for is_empty() before intersecting so we ignore the
+ * selection if it is suspended (like when stroking)
+ */
+ if (GIMP_ITEM (selection) != item &&
+ ! gimp_channel_is_empty (selection) &&
+ gimp_item_bounds (GIMP_ITEM (selection), &x, &y, &width, &height))
+ {
+ gint off_x, off_y;
+ gint x2, y2;
+
+ gimp_item_get_offset (item, &off_x, &off_y);
+
+ x2 = x + width;
+ y2 = y + height;
+
+ x = CLAMP (x - off_x, 0, gimp_item_get_width (item));
+ y = CLAMP (y - off_y, 0, gimp_item_get_height (item));
+ x2 = CLAMP (x2 - off_x, 0, gimp_item_get_width (item));
+ y2 = CLAMP (y2 - off_y, 0, gimp_item_get_height (item));
+
+ width = x2 - x;
+ height = y2 - y;
+
+ retval = TRUE;
+ }
+ else
+ {
+ x = 0;
+ y = 0;
+ width = gimp_item_get_width (item);
+ height = gimp_item_get_height (item);
+
+ retval = FALSE;
+ }
+
+ if (x1) *x1 = x;
+ if (y1) *y1 = y;
+ if (x2) *x2 = x + width;
+ if (y2) *y2 = y + height;
+
+ return retval;
+}
+
+/**
+ * gimp_item_mask_intersect:
+ * @item: a #GimpItem
+ * @x: return location for x
+ * @y: return location for y
+ * @width: return location for the width
+ * @height: return location for the height
+ *
+ * Intersect the area of the @item and its image's selection mask.
+ * The computed area is the bounding box of he selection within the
+ * item.
+ **/
+gboolean
+gimp_item_mask_intersect (GimpItem *item,
+ gint *x,
+ gint *y,
+ gint *width,
+ gint *height)
+{
+ GimpImage *image;
+ GimpChannel *selection;
+ gint tmp_x, tmp_y;
+ gint tmp_width, tmp_height;
+ gboolean retval;
+
+ g_return_val_if_fail (GIMP_IS_ITEM (item), FALSE);
+ g_return_val_if_fail (gimp_item_is_attached (item), FALSE);
+
+ image = gimp_item_get_image (item);
+ selection = gimp_image_get_mask (image);
+
+ /* check for is_empty() before intersecting so we ignore the
+ * selection if it is suspended (like when stroking)
+ */
+ if (GIMP_ITEM (selection) != item &&
+ ! gimp_channel_is_empty (selection) &&
+ gimp_item_bounds (GIMP_ITEM (selection),
+ &tmp_x, &tmp_y, &tmp_width, &tmp_height))
+ {
+ gint off_x, off_y;
+
+ gimp_item_get_offset (item, &off_x, &off_y);
+
+ retval = gimp_rectangle_intersect (tmp_x - off_x, tmp_y - off_y,
+ tmp_width, tmp_height,
+ 0, 0,
+ gimp_item_get_width (item),
+ gimp_item_get_height (item),
+ &tmp_x, &tmp_y,
+ &tmp_width, &tmp_height);
+ }
+ else
+ {
+ tmp_x = 0;
+ tmp_y = 0;
+ tmp_width = gimp_item_get_width (item);
+ tmp_height = gimp_item_get_height (item);
+
+ retval = TRUE;
+ }
+
+ if (x) *x = tmp_x;
+ if (y) *y = tmp_y;
+ if (width) *width = tmp_width;
+ if (height) *height = tmp_height;
+
+ return retval;
+}
+
+gboolean
+gimp_item_is_in_set (GimpItem *item,
+ GimpItemSet set)
+{
+ GimpItemPrivate *private;
+
+ g_return_val_if_fail (GIMP_IS_ITEM (item), FALSE);
+
+ private = GET_PRIVATE (item);
+
+ switch (set)
+ {
+ case GIMP_ITEM_SET_NONE:
+ return FALSE;
+
+ case GIMP_ITEM_SET_ALL:
+ return TRUE;
+
+ case GIMP_ITEM_SET_IMAGE_SIZED:
+ return (gimp_item_get_width (item) == gimp_image_get_width (private->image) &&
+ gimp_item_get_height (item) == gimp_image_get_height (private->image));
+
+ case GIMP_ITEM_SET_VISIBLE:
+ return gimp_item_get_visible (item);
+
+ case GIMP_ITEM_SET_LINKED:
+ return gimp_item_get_linked (item);
+ }
+
+ return FALSE;
+}
diff --git a/app/core/gimpitem.h b/app/core/gimpitem.h
new file mode 100644
index 0000000..6bc56e3
--- /dev/null
+++ b/app/core/gimpitem.h
@@ -0,0 +1,405 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_ITEM_H__
+#define __GIMP_ITEM_H__
+
+
+#include "gimpfilter.h"
+
+
+#define GIMP_TYPE_ITEM (gimp_item_get_type ())
+#define GIMP_ITEM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_ITEM, GimpItem))
+#define GIMP_ITEM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_ITEM, GimpItemClass))
+#define GIMP_IS_ITEM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_ITEM))
+#define GIMP_IS_ITEM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_ITEM))
+#define GIMP_ITEM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_ITEM, GimpItemClass))
+
+
+typedef struct _GimpItemClass GimpItemClass;
+
+struct _GimpItem
+{
+ GimpFilter parent_instance;
+};
+
+struct _GimpItemClass
+{
+ GimpFilterClass parent_class;
+
+ /* signals */
+ void (* removed) (GimpItem *item);
+ void (* visibility_changed) (GimpItem *item);
+ void (* linked_changed) (GimpItem *item);
+ void (* color_tag_changed) (GimpItem *item);
+ void (* lock_content_changed) (GimpItem *item);
+ void (* lock_position_changed) (GimpItem *item);
+
+ /* virtual functions */
+ void (* unset_removed) (GimpItem *item);
+ gboolean (* is_attached) (GimpItem *item);
+ gboolean (* is_content_locked) (GimpItem *item);
+ gboolean (* is_position_locked) (GimpItem *item);
+ GimpItemTree * (* get_tree) (GimpItem *item);
+ gboolean (* bounds) (GimpItem *item,
+ gdouble *x,
+ gdouble *y,
+ gdouble *width,
+ gdouble *height);
+ GimpItem * (* duplicate) (GimpItem *item,
+ GType new_type);
+ void (* convert) (GimpItem *item,
+ GimpImage *dest_image,
+ GType old_type);
+ gboolean (* rename) (GimpItem *item,
+ const gchar *new_name,
+ const gchar *undo_desc,
+ GError **error);
+ void (* start_move) (GimpItem *item,
+ gboolean push_undo);
+ void (* end_move) (GimpItem *item,
+ gboolean push_undo);
+ void (* start_transform) (GimpItem *item,
+ gboolean push_undo);
+ void (* end_transform) (GimpItem *item,
+ gboolean push_undo);
+ void (* translate) (GimpItem *item,
+ gdouble offset_x,
+ gdouble offset_y,
+ gboolean push_undo);
+ void (* scale) (GimpItem *item,
+ gint new_width,
+ gint new_height,
+ gint new_offset_x,
+ gint new_offset_y,
+ GimpInterpolationType interpolation_type,
+ GimpProgress *progress);
+ void (* resize) (GimpItem *item,
+ GimpContext *context,
+ GimpFillType fill_type,
+ gint new_width,
+ gint new_height,
+ gint offset_x,
+ gint offset_y);
+ void (* flip) (GimpItem *item,
+ GimpContext *context,
+ GimpOrientationType flip_type,
+ gdouble axis,
+ gboolean clip_result);
+ void (* rotate) (GimpItem *item,
+ GimpContext *context,
+ GimpRotationType rotate_type,
+ gdouble center_x,
+ gdouble center_y,
+ gboolean clip_result);
+ void (* transform) (GimpItem *item,
+ GimpContext *context,
+ const GimpMatrix3 *matrix,
+ GimpTransformDirection direction,
+ GimpInterpolationType interpolation_type,
+ GimpTransformResize clip_result,
+ GimpProgress *progress);
+ GimpTransformResize (* get_clip) (GimpItem *item,
+ GimpTransformResize clip_result);
+ gboolean (* fill) (GimpItem *item,
+ GimpDrawable *drawable,
+ GimpFillOptions *fill_options,
+ gboolean push_undo,
+ GimpProgress *progress,
+ GError **error);
+ gboolean (* stroke) (GimpItem *item,
+ GimpDrawable *drawable,
+ GimpStrokeOptions *stroke_options,
+ gboolean push_undo,
+ GimpProgress *progress,
+ GError **error);
+ void (* to_selection) (GimpItem *item,
+ GimpChannelOps op,
+ gboolean antialias,
+ gboolean feather,
+ gdouble feather_radius_x,
+ gdouble feather_radius_y);
+
+ const gchar *default_name;
+ const gchar *rename_desc;
+ const gchar *translate_desc;
+ const gchar *scale_desc;
+ const gchar *resize_desc;
+ const gchar *flip_desc;
+ const gchar *rotate_desc;
+ const gchar *transform_desc;
+ const gchar *to_selection_desc;
+ const gchar *fill_desc;
+ const gchar *stroke_desc;
+
+ const gchar *reorder_desc;
+ const gchar *raise_desc;
+ const gchar *raise_to_top_desc;
+ const gchar *lower_desc;
+ const gchar *lower_to_bottom_desc;
+
+ const gchar *raise_failed;
+ const gchar *lower_failed;
+};
+
+
+GType gimp_item_get_type (void) G_GNUC_CONST;
+
+GimpItem * gimp_item_new (GType type,
+ GimpImage *image,
+ const gchar *name,
+ gint offset_x,
+ gint offset_y,
+ gint width,
+ gint height);
+
+void gimp_item_removed (GimpItem *item);
+gboolean gimp_item_is_removed (GimpItem *item);
+void gimp_item_unset_removed (GimpItem *item);
+
+gboolean gimp_item_is_attached (GimpItem *item);
+
+GimpItem * gimp_item_get_parent (GimpItem *item);
+
+GimpItemTree * gimp_item_get_tree (GimpItem *item);
+GimpContainer * gimp_item_get_container (GimpItem *item);
+GList * gimp_item_get_container_iter (GimpItem *item);
+gint gimp_item_get_index (GimpItem *item);
+GList * gimp_item_get_path (GimpItem *item);
+
+gboolean gimp_item_bounds (GimpItem *item,
+ gint *x,
+ gint *y,
+ gint *width,
+ gint *height);
+gboolean gimp_item_bounds_f (GimpItem *item,
+ gdouble *x,
+ gdouble *y,
+ gdouble *width,
+ gdouble *height);
+
+GimpItem * gimp_item_duplicate (GimpItem *item,
+ GType new_type);
+GimpItem * gimp_item_convert (GimpItem *item,
+ GimpImage *dest_image,
+ GType new_type);
+
+gboolean gimp_item_rename (GimpItem *item,
+ const gchar *new_name,
+ GError **error);
+
+gint gimp_item_get_width (GimpItem *item);
+gint gimp_item_get_height (GimpItem *item);
+void gimp_item_set_size (GimpItem *item,
+ gint width,
+ gint height);
+
+void gimp_item_get_offset (GimpItem *item,
+ gint *offset_x,
+ gint *offset_y);
+void gimp_item_set_offset (GimpItem *item,
+ gint offset_x,
+ gint offset_y);
+gint gimp_item_get_offset_x (GimpItem *item);
+gint gimp_item_get_offset_y (GimpItem *item);
+
+void gimp_item_start_move (GimpItem *item,
+ gboolean push_undo);
+void gimp_item_end_move (GimpItem *item,
+ gboolean push_undo);
+
+void gimp_item_start_transform (GimpItem *item,
+ gboolean push_undo);
+void gimp_item_end_transform (GimpItem *item,
+ gboolean push_undo);
+
+void gimp_item_translate (GimpItem *item,
+ gdouble offset_x,
+ gdouble offset_y,
+ gboolean push_undo);
+
+gboolean gimp_item_check_scaling (GimpItem *item,
+ gint new_width,
+ gint new_height);
+void gimp_item_scale (GimpItem *item,
+ gint new_width,
+ gint new_height,
+ gint new_offset_x,
+ gint new_offset_y,
+ GimpInterpolationType interpolation,
+ GimpProgress *progress);
+gboolean gimp_item_scale_by_factors (GimpItem *item,
+ gdouble w_factor,
+ gdouble h_factor,
+ GimpInterpolationType interpolation,
+ GimpProgress *progress);
+gboolean
+ gimp_item_scale_by_factors_with_origin (GimpItem *item,
+ gdouble w_factor,
+ gdouble h_factor,
+ gint origin_x,
+ gint origin_y,
+ gint new_origin_x,
+ gint new_origin_y,
+ GimpInterpolationType interpolation,
+ GimpProgress *progress);
+void gimp_item_scale_by_origin (GimpItem *item,
+ gint new_width,
+ gint new_height,
+ GimpInterpolationType interpolation,
+ GimpProgress *progress,
+ gboolean local_origin);
+void gimp_item_resize (GimpItem *item,
+ GimpContext *context,
+ GimpFillType fill_type,
+ gint new_width,
+ gint new_height,
+ gint offset_x,
+ gint offset_y);
+void gimp_item_resize_to_image (GimpItem *item);
+
+void gimp_item_flip (GimpItem *item,
+ GimpContext *context,
+ GimpOrientationType flip_type,
+ gdouble axis,
+ gboolean clip_result);
+void gimp_item_rotate (GimpItem *item,
+ GimpContext *context,
+ GimpRotationType rotate_type,
+ gdouble center_x,
+ gdouble center_y,
+ gboolean clip_result);
+void gimp_item_transform (GimpItem *item,
+ GimpContext *context,
+ const GimpMatrix3 *matrix,
+ GimpTransformDirection direction,
+ GimpInterpolationType interpolation_type,
+ GimpTransformResize clip_result,
+ GimpProgress *progress);
+GimpTransformResize gimp_item_get_clip (GimpItem *item,
+ GimpTransformResize clip_result);
+
+gboolean gimp_item_fill (GimpItem *item,
+ GimpDrawable *drawable,
+ GimpFillOptions *fill_options,
+ gboolean push_undo,
+ GimpProgress *progress,
+ GError **error);
+gboolean gimp_item_stroke (GimpItem *item,
+ GimpDrawable *drawable,
+ GimpContext *context,
+ GimpStrokeOptions *stroke_options,
+ GimpPaintOptions *paint_options,
+ gboolean push_undo,
+ GimpProgress *progress,
+ GError **error);
+
+void gimp_item_to_selection (GimpItem *item,
+ GimpChannelOps op,
+ gboolean antialias,
+ gboolean feather,
+ gdouble feather_radius_x,
+ gdouble feather_radius_y);
+
+void gimp_item_add_offset_node (GimpItem *item,
+ GeglNode *node);
+void gimp_item_remove_offset_node (GimpItem *item,
+ GeglNode *node);
+
+gint gimp_item_get_ID (GimpItem *item);
+GimpItem * gimp_item_get_by_ID (Gimp *gimp,
+ gint id);
+
+GimpTattoo gimp_item_get_tattoo (GimpItem *item);
+void gimp_item_set_tattoo (GimpItem *item,
+ GimpTattoo tattoo);
+
+GimpImage * gimp_item_get_image (GimpItem *item);
+void gimp_item_set_image (GimpItem *item,
+ GimpImage *image);
+
+void gimp_item_replace_item (GimpItem *item,
+ GimpItem *replace);
+
+void gimp_item_set_parasites (GimpItem *item,
+ GimpParasiteList *parasites);
+GimpParasiteList * gimp_item_get_parasites (GimpItem *item);
+
+gboolean gimp_item_parasite_validate (GimpItem *item,
+ const GimpParasite *parasite,
+ GError **error);
+void gimp_item_parasite_attach (GimpItem *item,
+ const GimpParasite *parasite,
+ gboolean push_undo);
+void gimp_item_parasite_detach (GimpItem *item,
+ const gchar *name,
+ gboolean push_undo);
+const GimpParasite * gimp_item_parasite_find (GimpItem *item,
+ const gchar *name);
+gchar ** gimp_item_parasite_list (GimpItem *item,
+ gint *count);
+
+void gimp_item_set_visible (GimpItem *item,
+ gboolean visible,
+ gboolean push_undo);
+gboolean gimp_item_get_visible (GimpItem *item);
+gboolean gimp_item_is_visible (GimpItem *item);
+
+void gimp_item_bind_visible_to_active (GimpItem *item,
+ gboolean bind);
+
+void gimp_item_set_linked (GimpItem *item,
+ gboolean linked,
+ gboolean push_undo);
+gboolean gimp_item_get_linked (GimpItem *item);
+
+void gimp_item_set_color_tag (GimpItem *item,
+ GimpColorTag color_tag,
+ gboolean push_undo);
+GimpColorTag gimp_item_get_color_tag (GimpItem *item);
+GimpColorTag gimp_item_get_merged_color_tag (GimpItem *item);
+
+void gimp_item_set_lock_content (GimpItem *item,
+ gboolean lock_content,
+ gboolean push_undo);
+gboolean gimp_item_get_lock_content (GimpItem *item);
+gboolean gimp_item_can_lock_content (GimpItem *item);
+gboolean gimp_item_is_content_locked (GimpItem *item);
+
+void gimp_item_set_lock_position (GimpItem *item,
+ gboolean lock_position,
+ gboolean push_undo);
+gboolean gimp_item_get_lock_position (GimpItem *item);
+gboolean gimp_item_can_lock_position (GimpItem *item);
+gboolean gimp_item_is_position_locked (GimpItem *item);
+
+gboolean gimp_item_mask_bounds (GimpItem *item,
+ gint *x1,
+ gint *y1,
+ gint *x2,
+ gint *y2);
+gboolean gimp_item_mask_intersect (GimpItem *item,
+ gint *x,
+ gint *y,
+ gint *width,
+ gint *height);
+
+gboolean gimp_item_is_in_set (GimpItem *item,
+ GimpItemSet set);
+
+
+#endif /* __GIMP_ITEM_H__ */
diff --git a/app/core/gimpitempropundo.c b/app/core/gimpitempropundo.c
new file mode 100644
index 0000000..241b8aa
--- /dev/null
+++ b/app/core/gimpitempropundo.c
@@ -0,0 +1,358 @@
+/* Gimp - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpbase/gimpbase.h"
+
+#include "core-types.h"
+
+#include "gimp-memsize.h"
+#include "gimpitem.h"
+#include "gimpitemtree.h"
+#include "gimpitempropundo.h"
+#include "gimpparasitelist.h"
+
+
+enum
+{
+ PROP_0,
+ PROP_PARASITE_NAME
+};
+
+
+static void gimp_item_prop_undo_constructed (GObject *object);
+static void gimp_item_prop_undo_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_item_prop_undo_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static gint64 gimp_item_prop_undo_get_memsize (GimpObject *object,
+ gint64 *gui_size);
+
+static void gimp_item_prop_undo_pop (GimpUndo *undo,
+ GimpUndoMode undo_mode,
+ GimpUndoAccumulator *accum);
+static void gimp_item_prop_undo_free (GimpUndo *undo,
+ GimpUndoMode undo_mode);
+
+
+G_DEFINE_TYPE (GimpItemPropUndo, gimp_item_prop_undo, GIMP_TYPE_ITEM_UNDO)
+
+#define parent_class gimp_item_prop_undo_parent_class
+
+
+static void
+gimp_item_prop_undo_class_init (GimpItemPropUndoClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpObjectClass *gimp_object_class = GIMP_OBJECT_CLASS (klass);
+ GimpUndoClass *undo_class = GIMP_UNDO_CLASS (klass);
+
+ object_class->constructed = gimp_item_prop_undo_constructed;
+ object_class->set_property = gimp_item_prop_undo_set_property;
+ object_class->get_property = gimp_item_prop_undo_get_property;
+
+ gimp_object_class->get_memsize = gimp_item_prop_undo_get_memsize;
+
+ undo_class->pop = gimp_item_prop_undo_pop;
+ undo_class->free = gimp_item_prop_undo_free;
+
+ g_object_class_install_property (object_class, PROP_PARASITE_NAME,
+ g_param_spec_string ("parasite-name",
+ NULL, NULL,
+ NULL,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+}
+
+static void
+gimp_item_prop_undo_init (GimpItemPropUndo *undo)
+{
+}
+
+static void
+gimp_item_prop_undo_constructed (GObject *object)
+{
+ GimpItemPropUndo *item_prop_undo = GIMP_ITEM_PROP_UNDO (object);
+ GimpItem *item;
+
+ G_OBJECT_CLASS (parent_class)->constructed (object);
+
+ item = GIMP_ITEM_UNDO (object)->item;
+
+ switch (GIMP_UNDO (object)->undo_type)
+ {
+ case GIMP_UNDO_ITEM_REORDER:
+ item_prop_undo->parent = gimp_item_get_parent (item);
+ item_prop_undo->position = gimp_item_get_index (item);
+ break;
+
+ case GIMP_UNDO_ITEM_RENAME:
+ item_prop_undo->name = g_strdup (gimp_object_get_name (item));
+ break;
+
+ case GIMP_UNDO_ITEM_DISPLACE:
+ gimp_item_get_offset (item,
+ &item_prop_undo->offset_x,
+ &item_prop_undo->offset_y);
+ break;
+
+ case GIMP_UNDO_ITEM_VISIBILITY:
+ item_prop_undo->visible = gimp_item_get_visible (item);
+ break;
+
+ case GIMP_UNDO_ITEM_LINKED:
+ item_prop_undo->linked = gimp_item_get_linked (item);
+ break;
+
+ case GIMP_UNDO_ITEM_COLOR_TAG:
+ item_prop_undo->color_tag = gimp_item_get_color_tag (item);
+ break;
+
+ case GIMP_UNDO_ITEM_LOCK_CONTENT:
+ item_prop_undo->lock_content = gimp_item_get_lock_content (item);
+ break;
+
+ case GIMP_UNDO_ITEM_LOCK_POSITION:
+ item_prop_undo->lock_position = gimp_item_get_lock_position (item);
+ break;
+
+ case GIMP_UNDO_PARASITE_ATTACH:
+ case GIMP_UNDO_PARASITE_REMOVE:
+ gimp_assert (item_prop_undo->parasite_name != NULL);
+
+ item_prop_undo->parasite = gimp_parasite_copy
+ (gimp_item_parasite_find (item, item_prop_undo->parasite_name));
+ break;
+
+ default:
+ g_return_if_reached ();
+ }
+}
+
+static void
+gimp_item_prop_undo_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpItemPropUndo *item_prop_undo = GIMP_ITEM_PROP_UNDO (object);
+
+ switch (property_id)
+ {
+ case PROP_PARASITE_NAME:
+ item_prop_undo->parasite_name = g_value_dup_string (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_item_prop_undo_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpItemPropUndo *item_prop_undo = GIMP_ITEM_PROP_UNDO (object);
+
+ switch (property_id)
+ {
+ case PROP_PARASITE_NAME:
+ g_value_set_string (value, item_prop_undo->parasite_name);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static gint64
+gimp_item_prop_undo_get_memsize (GimpObject *object,
+ gint64 *gui_size)
+{
+ GimpItemPropUndo *item_prop_undo = GIMP_ITEM_PROP_UNDO (object);
+ gint64 memsize = 0;
+
+ memsize += gimp_string_get_memsize (item_prop_undo->name);
+ memsize += gimp_string_get_memsize (item_prop_undo->parasite_name);
+ memsize += gimp_parasite_get_memsize (item_prop_undo->parasite, NULL);
+
+ return memsize + GIMP_OBJECT_CLASS (parent_class)->get_memsize (object,
+ gui_size);
+}
+
+static void
+gimp_item_prop_undo_pop (GimpUndo *undo,
+ GimpUndoMode undo_mode,
+ GimpUndoAccumulator *accum)
+{
+ GimpItemPropUndo *item_prop_undo = GIMP_ITEM_PROP_UNDO (undo);
+ GimpItem *item = GIMP_ITEM_UNDO (undo)->item;
+
+ GIMP_UNDO_CLASS (parent_class)->pop (undo, undo_mode, accum);
+
+ switch (undo->undo_type)
+ {
+ case GIMP_UNDO_ITEM_REORDER:
+ {
+ GimpItem *parent;
+ gint position;
+
+ parent = gimp_item_get_parent (item);
+ position = gimp_item_get_index (item);
+
+ gimp_item_tree_reorder_item (gimp_item_get_tree (item), item,
+ item_prop_undo->parent,
+ item_prop_undo->position,
+ FALSE, NULL);
+
+ item_prop_undo->parent = parent;
+ item_prop_undo->position = position;
+ }
+ break;
+
+ case GIMP_UNDO_ITEM_RENAME:
+ {
+ gchar *name;
+
+ name = g_strdup (gimp_object_get_name (item));
+
+ gimp_item_tree_rename_item (gimp_item_get_tree (item), item,
+ item_prop_undo->name,
+ FALSE, NULL);
+
+ g_free (item_prop_undo->name);
+ item_prop_undo->name = name;
+ }
+ break;
+
+ case GIMP_UNDO_ITEM_DISPLACE:
+ {
+ gint offset_x;
+ gint offset_y;
+
+ gimp_item_get_offset (item, &offset_x, &offset_y);
+
+ gimp_item_translate (item,
+ item_prop_undo->offset_x - offset_x,
+ item_prop_undo->offset_y - offset_y,
+ FALSE);
+
+ item_prop_undo->offset_x = offset_x;
+ item_prop_undo->offset_y = offset_y;
+ }
+ break;
+
+ case GIMP_UNDO_ITEM_VISIBILITY:
+ {
+ gboolean visible;
+
+ visible = gimp_item_get_visible (item);
+ gimp_item_set_visible (item, item_prop_undo->visible, FALSE);
+ item_prop_undo->visible = visible;
+ }
+ break;
+
+ case GIMP_UNDO_ITEM_LINKED:
+ {
+ gboolean linked;
+
+ linked = gimp_item_get_linked (item);
+ gimp_item_set_linked (item, item_prop_undo->linked, FALSE);
+ item_prop_undo->linked = linked;
+ }
+ break;
+
+ case GIMP_UNDO_ITEM_COLOR_TAG:
+ {
+ GimpColorTag color_tag;
+
+ color_tag = gimp_item_get_color_tag (item);
+ gimp_item_set_color_tag (item, item_prop_undo->color_tag, FALSE);
+ item_prop_undo->color_tag = color_tag;
+ }
+ break;
+
+ case GIMP_UNDO_ITEM_LOCK_CONTENT:
+ {
+ gboolean lock_content;
+
+ lock_content = gimp_item_get_lock_content (item);
+ gimp_item_set_lock_content (item, item_prop_undo->lock_content, FALSE);
+ item_prop_undo->lock_content = lock_content;
+ }
+ break;
+
+ case GIMP_UNDO_ITEM_LOCK_POSITION:
+ {
+ gboolean lock_position;
+
+ lock_position = gimp_item_get_lock_position (item);
+ gimp_item_set_lock_position (item, item_prop_undo->lock_position, FALSE);
+ item_prop_undo->lock_position = lock_position;
+ }
+ break;
+
+ case GIMP_UNDO_PARASITE_ATTACH:
+ case GIMP_UNDO_PARASITE_REMOVE:
+ {
+ GimpParasite *parasite;
+
+ parasite = item_prop_undo->parasite;
+
+ item_prop_undo->parasite = gimp_parasite_copy
+ (gimp_item_parasite_find (item, item_prop_undo->parasite_name));
+
+ if (parasite)
+ gimp_item_parasite_attach (item, parasite, FALSE);
+ else
+ gimp_item_parasite_detach (item, item_prop_undo->parasite_name, FALSE);
+
+ if (parasite)
+ gimp_parasite_free (parasite);
+ }
+ break;
+
+ default:
+ g_return_if_reached ();
+ }
+}
+
+static void
+gimp_item_prop_undo_free (GimpUndo *undo,
+ GimpUndoMode undo_mode)
+{
+ GimpItemPropUndo *item_prop_undo = GIMP_ITEM_PROP_UNDO (undo);
+
+ g_clear_pointer (&item_prop_undo->name, g_free);
+ g_clear_pointer (&item_prop_undo->parasite_name, g_free);
+ g_clear_pointer (&item_prop_undo->parasite, gimp_parasite_free);
+
+ GIMP_UNDO_CLASS (parent_class)->free (undo, undo_mode);
+}
diff --git a/app/core/gimpitempropundo.h b/app/core/gimpitempropundo.h
new file mode 100644
index 0000000..8f96e46
--- /dev/null
+++ b/app/core/gimpitempropundo.h
@@ -0,0 +1,63 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_ITEM_PROP_UNDO_H__
+#define __GIMP_ITEM_PROP_UNDO_H__
+
+
+#include "gimpitemundo.h"
+
+
+#define GIMP_TYPE_ITEM_PROP_UNDO (gimp_item_prop_undo_get_type ())
+#define GIMP_ITEM_PROP_UNDO(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_ITEM_PROP_UNDO, GimpItemPropUndo))
+#define GIMP_ITEM_PROP_UNDO_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_ITEM_PROP_UNDO, GimpItemPropUndoClass))
+#define GIMP_IS_ITEM_PROP_UNDO(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_ITEM_PROP_UNDO))
+#define GIMP_IS_ITEM_PROP_UNDO_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_ITEM_PROP_UNDO))
+#define GIMP_ITEM_PROP_UNDO_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_ITEM_PROP_UNDO, GimpItemPropUndoClass))
+
+
+typedef struct _GimpItemPropUndo GimpItemPropUndo;
+typedef struct _GimpItemPropUndoClass GimpItemPropUndoClass;
+
+struct _GimpItemPropUndo
+{
+ GimpItemUndo parent_instance;
+
+ GimpItem *parent;
+ gint position;
+ gchar *name;
+ gint offset_x;
+ gint offset_y;
+ guint visible : 1;
+ guint linked : 1;
+ guint lock_content : 1;
+ guint lock_position : 1;
+ GimpColorTag color_tag;
+ gchar *parasite_name;
+ GimpParasite *parasite;
+};
+
+struct _GimpItemPropUndoClass
+{
+ GimpItemUndoClass parent_class;
+};
+
+
+GType gimp_item_prop_undo_get_type (void) G_GNUC_CONST;
+
+
+#endif /* __GIMP_ITEM_PROP_UNDO_H__ */
diff --git a/app/core/gimpitemstack.c b/app/core/gimpitemstack.c
new file mode 100644
index 0000000..c7f2fe1
--- /dev/null
+++ b/app/core/gimpitemstack.c
@@ -0,0 +1,348 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-1997 Spencer Kimball and Peter Mattis
+ *
+ * gimpitemstack.c
+ * Copyright (C) 2008 Michael Natterer <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <cairo.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpcolor/gimpcolor.h"
+
+#include "core-types.h"
+
+#include "gimpitem.h"
+#include "gimpitemstack.h"
+
+
+/* local function prototypes */
+
+static void gimp_item_stack_constructed (GObject *object);
+
+static void gimp_item_stack_add (GimpContainer *container,
+ GimpObject *object);
+static void gimp_item_stack_remove (GimpContainer *container,
+ GimpObject *object);
+
+
+G_DEFINE_TYPE (GimpItemStack, gimp_item_stack, GIMP_TYPE_FILTER_STACK)
+
+#define parent_class gimp_item_stack_parent_class
+
+
+static void
+gimp_item_stack_class_init (GimpItemStackClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpContainerClass *container_class = GIMP_CONTAINER_CLASS (klass);
+
+ object_class->constructed = gimp_item_stack_constructed;
+
+ container_class->add = gimp_item_stack_add;
+ container_class->remove = gimp_item_stack_remove;
+}
+
+static void
+gimp_item_stack_init (GimpItemStack *stack)
+{
+}
+
+static void
+gimp_item_stack_constructed (GObject *object)
+{
+ GimpContainer *container = GIMP_CONTAINER (object);
+
+ G_OBJECT_CLASS (parent_class)->constructed (object);
+
+ gimp_assert (g_type_is_a (gimp_container_get_children_type (container),
+ GIMP_TYPE_ITEM));
+}
+
+static void
+gimp_item_stack_add (GimpContainer *container,
+ GimpObject *object)
+{
+ g_object_ref_sink (object);
+
+ GIMP_CONTAINER_CLASS (parent_class)->add (container, object);
+
+ g_object_unref (object);
+}
+
+static void
+gimp_item_stack_remove (GimpContainer *container,
+ GimpObject *object)
+{
+ GIMP_CONTAINER_CLASS (parent_class)->remove (container, object);
+}
+
+
+/* public functions */
+
+GimpContainer *
+gimp_item_stack_new (GType item_type)
+{
+ g_return_val_if_fail (g_type_is_a (item_type, GIMP_TYPE_ITEM), NULL);
+
+ return g_object_new (GIMP_TYPE_ITEM_STACK,
+ "name", g_type_name (item_type),
+ "children-type", item_type,
+ "policy", GIMP_CONTAINER_POLICY_STRONG,
+ NULL);
+}
+
+gint
+gimp_item_stack_get_n_items (GimpItemStack *stack)
+{
+ GList *list;
+ gint n_items = 0;
+
+ g_return_val_if_fail (GIMP_IS_ITEM_STACK (stack), 0);
+
+ for (list = GIMP_LIST (stack)->queue->head; list; list = g_list_next (list))
+ {
+ GimpItem *item = list->data;
+ GimpContainer *children;
+
+ n_items++;
+
+ children = gimp_viewable_get_children (GIMP_VIEWABLE (item));
+
+ if (children)
+ n_items += gimp_item_stack_get_n_items (GIMP_ITEM_STACK (children));
+ }
+
+ return n_items;
+}
+
+gboolean
+gimp_item_stack_is_flat (GimpItemStack *stack)
+{
+ GList *list;
+
+ g_return_val_if_fail (GIMP_IS_ITEM_STACK (stack), TRUE);
+
+ for (list = GIMP_LIST (stack)->queue->head; list; list = g_list_next (list))
+ {
+ GimpViewable *viewable = list->data;
+
+ if (gimp_viewable_get_children (viewable))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+GList *
+gimp_item_stack_get_item_iter (GimpItemStack *stack)
+{
+ g_return_val_if_fail (GIMP_IS_ITEM_STACK (stack), NULL);
+
+ return GIMP_LIST (stack)->queue->head;
+}
+
+GList *
+gimp_item_stack_get_item_list (GimpItemStack *stack)
+{
+ GList *list;
+ GList *result = NULL;
+
+ g_return_val_if_fail (GIMP_IS_ITEM_STACK (stack), NULL);
+
+ for (list = GIMP_LIST (stack)->queue->head;
+ list;
+ list = g_list_next (list))
+ {
+ GimpViewable *viewable = list->data;
+ GimpContainer *children;
+
+ result = g_list_prepend (result, viewable);
+
+ children = gimp_viewable_get_children (viewable);
+
+ if (children)
+ {
+ GList *child_list;
+
+ child_list = gimp_item_stack_get_item_list (GIMP_ITEM_STACK (children));
+
+ while (child_list)
+ {
+ result = g_list_prepend (result, child_list->data);
+
+ child_list = g_list_remove (child_list, child_list->data);
+ }
+ }
+ }
+
+ return g_list_reverse (result);
+}
+
+GimpItem *
+gimp_item_stack_get_item_by_tattoo (GimpItemStack *stack,
+ GimpTattoo tattoo)
+{
+ GList *list;
+
+ g_return_val_if_fail (GIMP_IS_ITEM_STACK (stack), NULL);
+
+ for (list = GIMP_LIST (stack)->queue->head; list; list = g_list_next (list))
+ {
+ GimpItem *item = list->data;
+ GimpContainer *children;
+
+ if (gimp_item_get_tattoo (item) == tattoo)
+ return item;
+
+ children = gimp_viewable_get_children (GIMP_VIEWABLE (item));
+
+ if (children)
+ {
+ item = gimp_item_stack_get_item_by_tattoo (GIMP_ITEM_STACK (children),
+ tattoo);
+
+ if (item)
+ return item;
+ }
+ }
+
+ return NULL;
+}
+
+GimpItem *
+gimp_item_stack_get_item_by_path (GimpItemStack *stack,
+ GList *path)
+{
+ GimpContainer *container;
+ GimpItem *item = NULL;
+
+ g_return_val_if_fail (GIMP_IS_ITEM_STACK (stack), NULL);
+ g_return_val_if_fail (path != NULL, NULL);
+
+ container = GIMP_CONTAINER (stack);
+
+ while (path)
+ {
+ guint32 i = GPOINTER_TO_UINT (path->data);
+
+ item = GIMP_ITEM (gimp_container_get_child_by_index (container, i));
+
+ g_return_val_if_fail (GIMP_IS_ITEM (item), item);
+
+ if (path->next)
+ {
+ container = gimp_viewable_get_children (GIMP_VIEWABLE (item));
+
+ g_return_val_if_fail (GIMP_IS_ITEM_STACK (container), item);
+ }
+
+ path = path->next;
+ }
+
+ return item;
+}
+
+GimpItem *
+gimp_item_stack_get_parent_by_path (GimpItemStack *stack,
+ GList *path,
+ gint *index)
+{
+ GimpItem *parent = NULL;
+ guint32 i;
+
+ g_return_val_if_fail (GIMP_IS_ITEM_STACK (stack), NULL);
+ g_return_val_if_fail (path != NULL, NULL);
+
+ i = GPOINTER_TO_UINT (path->data);
+
+ if (index)
+ *index = i;
+
+ while (path->next)
+ {
+ GimpObject *child;
+ GimpContainer *children;
+
+ child = gimp_container_get_child_by_index (GIMP_CONTAINER (stack), i);
+
+ g_return_val_if_fail (GIMP_IS_ITEM (child), parent);
+
+ children = gimp_viewable_get_children (GIMP_VIEWABLE (child));
+
+ g_return_val_if_fail (GIMP_IS_ITEM_STACK (children), parent);
+
+ parent = GIMP_ITEM (child);
+ stack = GIMP_ITEM_STACK (children);
+
+ path = path->next;
+
+ i = GPOINTER_TO_UINT (path->data);
+
+ if (index)
+ *index = i;
+ }
+
+ return parent;
+}
+
+static void
+gimp_item_stack_viewable_invalidate_previews (GimpViewable *viewable)
+{
+ GimpContainer *children = gimp_viewable_get_children (viewable);
+
+ if (children)
+ gimp_item_stack_invalidate_previews (GIMP_ITEM_STACK (children));
+
+ gimp_viewable_invalidate_preview (viewable);
+}
+
+void
+gimp_item_stack_invalidate_previews (GimpItemStack *stack)
+{
+ g_return_if_fail (GIMP_IS_ITEM_STACK (stack));
+
+ gimp_container_foreach (GIMP_CONTAINER (stack),
+ (GFunc) gimp_item_stack_viewable_invalidate_previews,
+ NULL);
+}
+
+static void
+gimp_item_stack_viewable_profile_changed (GimpViewable *viewable)
+{
+ GimpContainer *children = gimp_viewable_get_children (viewable);
+
+ if (children)
+ gimp_item_stack_profile_changed (GIMP_ITEM_STACK (children));
+
+ if (GIMP_IS_COLOR_MANAGED (viewable))
+ gimp_color_managed_profile_changed (GIMP_COLOR_MANAGED (viewable));
+}
+
+void
+gimp_item_stack_profile_changed (GimpItemStack *stack)
+{
+ g_return_if_fail (GIMP_IS_ITEM_STACK (stack));
+
+ gimp_container_foreach (GIMP_CONTAINER (stack),
+ (GFunc) gimp_item_stack_viewable_profile_changed,
+ NULL);
+}
diff --git a/app/core/gimpitemstack.h b/app/core/gimpitemstack.h
new file mode 100644
index 0000000..b476076
--- /dev/null
+++ b/app/core/gimpitemstack.h
@@ -0,0 +1,66 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-1997 Spencer Kimball and Peter Mattis
+ *
+ * gimpitemstack.h
+ * Copyright (C) 2008 Michael Natterer <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_ITEM_STACK_H__
+#define __GIMP_ITEM_STACK_H__
+
+#include "gimpfilterstack.h"
+
+
+#define GIMP_TYPE_ITEM_STACK (gimp_item_stack_get_type ())
+#define GIMP_ITEM_STACK(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_ITEM_STACK, GimpItemStack))
+#define GIMP_ITEM_STACK_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_ITEM_STACK, GimpItemStackClass))
+#define GIMP_IS_ITEM_STACK(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_ITEM_STACK))
+#define GIMP_IS_ITEM_STACK_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_ITEM_STACK))
+
+
+typedef struct _GimpItemStackClass GimpItemStackClass;
+
+struct _GimpItemStack
+{
+ GimpFilterStack parent_instance;
+};
+
+struct _GimpItemStackClass
+{
+ GimpFilterStackClass parent_class;
+};
+
+
+GType gimp_item_stack_get_type (void) G_GNUC_CONST;
+GimpContainer * gimp_item_stack_new (GType item_type);
+
+gint gimp_item_stack_get_n_items (GimpItemStack *stack);
+gboolean gimp_item_stack_is_flat (GimpItemStack *stack);
+GList * gimp_item_stack_get_item_iter (GimpItemStack *stack);
+GList * gimp_item_stack_get_item_list (GimpItemStack *stack);
+GimpItem * gimp_item_stack_get_item_by_tattoo (GimpItemStack *stack,
+ GimpTattoo tattoo);
+GimpItem * gimp_item_stack_get_item_by_path (GimpItemStack *stack,
+ GList *path);
+GimpItem * gimp_item_stack_get_parent_by_path (GimpItemStack *stack,
+ GList *path,
+ gint *index);
+
+void gimp_item_stack_invalidate_previews (GimpItemStack *stack);
+void gimp_item_stack_profile_changed (GimpItemStack *stack);
+
+
+#endif /* __GIMP_ITEM_STACK_H__ */
diff --git a/app/core/gimpitemtree.c b/app/core/gimpitemtree.c
new file mode 100644
index 0000000..7746afa
--- /dev/null
+++ b/app/core/gimpitemtree.c
@@ -0,0 +1,714 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-1997 Spencer Kimball and Peter Mattis
+ *
+ * gimpitemtree.c
+ * Copyright (C) 2010 Michael Natterer <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "core-types.h"
+
+#include "gimpimage.h"
+#include "gimpimage-undo-push.h"
+#include "gimpitem.h"
+#include "gimpitemstack.h"
+#include "gimpitemtree.h"
+
+
+enum
+{
+ PROP_0,
+ PROP_IMAGE,
+ PROP_CONTAINER_TYPE,
+ PROP_ITEM_TYPE,
+ PROP_ACTIVE_ITEM
+};
+
+
+typedef struct _GimpItemTreePrivate GimpItemTreePrivate;
+
+struct _GimpItemTreePrivate
+{
+ GimpImage *image;
+
+ GType container_type;
+ GType item_type;
+
+ GimpItem *active_item;
+
+ GHashTable *name_hash;
+};
+
+#define GIMP_ITEM_TREE_GET_PRIVATE(object) \
+ ((GimpItemTreePrivate *) gimp_item_tree_get_instance_private ((GimpItemTree *) (object)))
+
+
+/* local function prototypes */
+
+static void gimp_item_tree_constructed (GObject *object);
+static void gimp_item_tree_dispose (GObject *object);
+static void gimp_item_tree_finalize (GObject *object);
+static void gimp_item_tree_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_item_tree_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static gint64 gimp_item_tree_get_memsize (GimpObject *object,
+ gint64 *gui_size);
+
+static void gimp_item_tree_uniquefy_name (GimpItemTree *tree,
+ GimpItem *item,
+ const gchar *new_name);
+
+
+G_DEFINE_TYPE_WITH_PRIVATE (GimpItemTree, gimp_item_tree, GIMP_TYPE_OBJECT)
+
+#define parent_class gimp_item_tree_parent_class
+
+
+static void
+gimp_item_tree_class_init (GimpItemTreeClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpObjectClass *gimp_object_class = GIMP_OBJECT_CLASS (klass);
+
+ object_class->constructed = gimp_item_tree_constructed;
+ object_class->dispose = gimp_item_tree_dispose;
+ object_class->finalize = gimp_item_tree_finalize;
+ object_class->set_property = gimp_item_tree_set_property;
+ object_class->get_property = gimp_item_tree_get_property;
+
+ gimp_object_class->get_memsize = gimp_item_tree_get_memsize;
+
+ g_object_class_install_property (object_class, PROP_IMAGE,
+ g_param_spec_object ("image",
+ NULL, NULL,
+ GIMP_TYPE_IMAGE,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+
+ g_object_class_install_property (object_class, PROP_CONTAINER_TYPE,
+ g_param_spec_gtype ("container-type",
+ NULL, NULL,
+ GIMP_TYPE_ITEM_STACK,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+
+ g_object_class_install_property (object_class, PROP_ITEM_TYPE,
+ g_param_spec_gtype ("item-type",
+ NULL, NULL,
+ GIMP_TYPE_ITEM,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+
+ g_object_class_install_property (object_class, PROP_ACTIVE_ITEM,
+ g_param_spec_object ("active-item",
+ NULL, NULL,
+ GIMP_TYPE_ITEM,
+ GIMP_PARAM_READWRITE));
+}
+
+static void
+gimp_item_tree_init (GimpItemTree *tree)
+{
+ GimpItemTreePrivate *private = GIMP_ITEM_TREE_GET_PRIVATE (tree);
+
+ private->name_hash = g_hash_table_new (g_str_hash, g_str_equal);
+}
+
+static void
+gimp_item_tree_constructed (GObject *object)
+{
+ GimpItemTree *tree = GIMP_ITEM_TREE (object);
+ GimpItemTreePrivate *private = GIMP_ITEM_TREE_GET_PRIVATE (tree);
+
+ G_OBJECT_CLASS (parent_class)->constructed (object);
+
+ gimp_assert (GIMP_IS_IMAGE (private->image));
+ gimp_assert (g_type_is_a (private->container_type, GIMP_TYPE_ITEM_STACK));
+ gimp_assert (g_type_is_a (private->item_type, GIMP_TYPE_ITEM));
+ gimp_assert (private->item_type != GIMP_TYPE_ITEM);
+
+ tree->container = g_object_new (private->container_type,
+ "name", g_type_name (private->item_type),
+ "children-type", private->item_type,
+ "policy", GIMP_CONTAINER_POLICY_STRONG,
+ NULL);
+}
+
+static void
+gimp_item_tree_dispose (GObject *object)
+{
+ GimpItemTree *tree = GIMP_ITEM_TREE (object);
+ GimpItemTreePrivate *private = GIMP_ITEM_TREE_GET_PRIVATE (tree);
+
+ gimp_item_tree_set_active_item (tree, NULL);
+
+ gimp_container_foreach (tree->container,
+ (GFunc) gimp_item_removed, NULL);
+
+ gimp_container_clear (tree->container);
+ g_hash_table_remove_all (private->name_hash);
+
+ G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+gimp_item_tree_finalize (GObject *object)
+{
+ GimpItemTree *tree = GIMP_ITEM_TREE (object);
+ GimpItemTreePrivate *private = GIMP_ITEM_TREE_GET_PRIVATE (tree);
+
+ g_clear_pointer (&private->name_hash, g_hash_table_unref);
+ g_clear_object (&tree->container);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gimp_item_tree_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpItemTreePrivate *private = GIMP_ITEM_TREE_GET_PRIVATE (object);
+
+ switch (property_id)
+ {
+ case PROP_IMAGE:
+ private->image = g_value_get_object (value); /* don't ref */
+ break;
+ case PROP_CONTAINER_TYPE:
+ private->container_type = g_value_get_gtype (value);
+ break;
+ case PROP_ITEM_TYPE:
+ private->item_type = g_value_get_gtype (value);
+ break;
+ case PROP_ACTIVE_ITEM:
+ private->active_item = g_value_get_object (value); /* don't ref */
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_item_tree_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpItemTreePrivate *private = GIMP_ITEM_TREE_GET_PRIVATE (object);
+
+ switch (property_id)
+ {
+ case PROP_IMAGE:
+ g_value_set_object (value, private->image);
+ break;
+ case PROP_CONTAINER_TYPE:
+ g_value_set_gtype (value, private->container_type);
+ break;
+ case PROP_ITEM_TYPE:
+ g_value_set_gtype (value, private->item_type);
+ break;
+ case PROP_ACTIVE_ITEM:
+ g_value_set_object (value, private->active_item);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static gint64
+gimp_item_tree_get_memsize (GimpObject *object,
+ gint64 *gui_size)
+{
+ GimpItemTree *tree = GIMP_ITEM_TREE (object);
+ gint64 memsize = 0;
+
+ memsize += gimp_object_get_memsize (GIMP_OBJECT (tree->container), gui_size);
+
+ return memsize + GIMP_OBJECT_CLASS (parent_class)->get_memsize (object,
+ gui_size);
+}
+
+
+/* public functions */
+
+GimpItemTree *
+gimp_item_tree_new (GimpImage *image,
+ GType container_type,
+ GType item_type)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+ g_return_val_if_fail (g_type_is_a (container_type, GIMP_TYPE_ITEM_STACK), NULL);
+ g_return_val_if_fail (g_type_is_a (item_type, GIMP_TYPE_ITEM), NULL);
+
+ return g_object_new (GIMP_TYPE_ITEM_TREE,
+ "image", image,
+ "container-type", container_type,
+ "item-type", item_type,
+ NULL);
+}
+
+GimpItem *
+gimp_item_tree_get_active_item (GimpItemTree *tree)
+{
+ g_return_val_if_fail (GIMP_IS_ITEM_TREE (tree), NULL);
+
+ return GIMP_ITEM_TREE_GET_PRIVATE (tree)->active_item;
+}
+
+void
+gimp_item_tree_set_active_item (GimpItemTree *tree,
+ GimpItem *item)
+{
+ GimpItemTreePrivate *private;
+
+ g_return_if_fail (GIMP_IS_ITEM_TREE (tree));
+
+ private = GIMP_ITEM_TREE_GET_PRIVATE (tree);
+
+ g_return_if_fail (item == NULL ||
+ G_TYPE_CHECK_INSTANCE_TYPE (item, private->item_type));
+ g_return_if_fail (item == NULL || gimp_item_get_tree (item) == tree);
+
+ if (item != private->active_item)
+ {
+ private->active_item = item;
+
+ g_object_notify (G_OBJECT (tree), "active-item");
+ }
+}
+
+GimpItem *
+gimp_item_tree_get_item_by_name (GimpItemTree *tree,
+ const gchar *name)
+{
+ g_return_val_if_fail (GIMP_IS_ITEM_TREE (tree), NULL);
+ g_return_val_if_fail (name != NULL, NULL);
+
+ return g_hash_table_lookup (GIMP_ITEM_TREE_GET_PRIVATE (tree)->name_hash,
+ name);
+}
+
+gboolean
+gimp_item_tree_get_insert_pos (GimpItemTree *tree,
+ GimpItem *item,
+ GimpItem **parent,
+ gint *position)
+{
+ GimpItemTreePrivate *private;
+ GimpContainer *container;
+
+ g_return_val_if_fail (GIMP_IS_ITEM_TREE (tree), FALSE);
+ g_return_val_if_fail (parent != NULL, FALSE);
+
+ private = GIMP_ITEM_TREE_GET_PRIVATE (tree);
+
+ g_return_val_if_fail (G_TYPE_CHECK_INSTANCE_TYPE (item, private->item_type),
+ FALSE);
+ g_return_val_if_fail (! gimp_item_is_attached (item), FALSE);
+ g_return_val_if_fail (gimp_item_get_image (item) == private->image, FALSE);
+ g_return_val_if_fail (*parent == NULL ||
+ *parent == GIMP_IMAGE_ACTIVE_PARENT ||
+ G_TYPE_CHECK_INSTANCE_TYPE (*parent, private->item_type),
+ FALSE);
+ g_return_val_if_fail (*parent == NULL ||
+ *parent == GIMP_IMAGE_ACTIVE_PARENT ||
+ gimp_item_get_tree (*parent) == tree, FALSE);
+ g_return_val_if_fail (*parent == NULL ||
+ *parent == GIMP_IMAGE_ACTIVE_PARENT ||
+ gimp_viewable_get_children (GIMP_VIEWABLE (*parent)),
+ FALSE);
+ g_return_val_if_fail (position != NULL, FALSE);
+
+ /* if we want to insert in the active item's parent container */
+ if (*parent == GIMP_IMAGE_ACTIVE_PARENT)
+ {
+ if (private->active_item)
+ {
+ /* if the active item is a branch, add to the top of that
+ * branch; add to the active item's parent container
+ * otherwise
+ */
+ if (gimp_viewable_get_children (GIMP_VIEWABLE (private->active_item)))
+ {
+ *parent = private->active_item;
+ *position = 0;
+ }
+ else
+ {
+ *parent = gimp_item_get_parent (private->active_item);
+ }
+ }
+ else
+ {
+ /* use the toplevel container if there is no active item */
+ *parent = NULL;
+ }
+ }
+
+ if (*parent)
+ container = gimp_viewable_get_children (GIMP_VIEWABLE (*parent));
+ else
+ container = tree->container;
+
+ /* if we want to add on top of the active item */
+ if (*position == -1)
+ {
+ if (private->active_item)
+ *position =
+ gimp_container_get_child_index (container,
+ GIMP_OBJECT (private->active_item));
+
+ /* if the active item is not in the specified parent container,
+ * fall back to index 0
+ */
+ if (*position == -1)
+ *position = 0;
+ }
+
+ /* don't add at a non-existing index */
+ *position = CLAMP (*position, 0, gimp_container_get_n_children (container));
+
+ return TRUE;
+}
+
+void
+gimp_item_tree_add_item (GimpItemTree *tree,
+ GimpItem *item,
+ GimpItem *parent,
+ gint position)
+{
+ GimpItemTreePrivate *private;
+ GimpContainer *container;
+ GimpContainer *children;
+
+ g_return_if_fail (GIMP_IS_ITEM_TREE (tree));
+
+ private = GIMP_ITEM_TREE_GET_PRIVATE (tree);
+
+ g_return_if_fail (G_TYPE_CHECK_INSTANCE_TYPE (item, private->item_type));
+ g_return_if_fail (! gimp_item_is_attached (item));
+ g_return_if_fail (gimp_item_get_image (item) == private->image);
+ g_return_if_fail (parent == NULL ||
+ G_TYPE_CHECK_INSTANCE_TYPE (parent, private->item_type));
+ g_return_if_fail (parent == NULL || gimp_item_get_tree (parent) == tree);
+ g_return_if_fail (parent == NULL ||
+ gimp_viewable_get_children (GIMP_VIEWABLE (parent)));
+
+ gimp_item_tree_uniquefy_name (tree, item, NULL);
+
+ children = gimp_viewable_get_children (GIMP_VIEWABLE (item));
+
+ if (children)
+ {
+ GList *list = gimp_item_stack_get_item_list (GIMP_ITEM_STACK (children));
+
+ while (list)
+ {
+ gimp_item_tree_uniquefy_name (tree, list->data, NULL);
+
+ list = g_list_remove (list, list->data);
+ }
+ }
+
+ if (parent)
+ container = gimp_viewable_get_children (GIMP_VIEWABLE (parent));
+ else
+ container = tree->container;
+
+ if (parent)
+ gimp_viewable_set_parent (GIMP_VIEWABLE (item),
+ GIMP_VIEWABLE (parent));
+
+ gimp_container_insert (container, GIMP_OBJECT (item), position);
+
+ /* if the item came from the undo stack, reset its "removed" state */
+ if (gimp_item_is_removed (item))
+ gimp_item_unset_removed (item);
+}
+
+GimpItem *
+gimp_item_tree_remove_item (GimpItemTree *tree,
+ GimpItem *item,
+ GimpItem *new_active)
+{
+ GimpItemTreePrivate *private;
+ GimpItem *parent;
+ GimpContainer *container;
+ GimpContainer *children;
+ gint index;
+
+ g_return_val_if_fail (GIMP_IS_ITEM_TREE (tree), NULL);
+
+ private = GIMP_ITEM_TREE_GET_PRIVATE (tree);
+
+ g_return_val_if_fail (G_TYPE_CHECK_INSTANCE_TYPE (item, private->item_type),
+ NULL);
+ g_return_val_if_fail (gimp_item_get_tree (item) == tree, NULL);
+
+ parent = gimp_item_get_parent (item);
+ container = gimp_item_get_container (item);
+ index = gimp_item_get_index (item);
+
+ g_object_ref (item);
+
+ g_hash_table_remove (private->name_hash,
+ gimp_object_get_name (item));
+
+ children = gimp_viewable_get_children (GIMP_VIEWABLE (item));
+
+ if (children)
+ {
+ GList *list = gimp_item_stack_get_item_list (GIMP_ITEM_STACK (children));
+
+ while (list)
+ {
+ g_hash_table_remove (private->name_hash,
+ gimp_object_get_name (list->data));
+
+ list = g_list_remove (list, list->data);
+ }
+ }
+
+ gimp_container_remove (container, GIMP_OBJECT (item));
+
+ if (parent)
+ gimp_viewable_set_parent (GIMP_VIEWABLE (item), NULL);
+
+ gimp_item_removed (item);
+
+ if (! new_active)
+ {
+ gint n_children = gimp_container_get_n_children (container);
+
+ if (n_children > 0)
+ {
+ index = CLAMP (index, 0, n_children - 1);
+
+ new_active =
+ GIMP_ITEM (gimp_container_get_child_by_index (container, index));
+ }
+ else if (parent)
+ {
+ new_active = parent;
+ }
+ }
+
+ g_object_unref (item);
+
+ return new_active;
+}
+
+gboolean
+gimp_item_tree_reorder_item (GimpItemTree *tree,
+ GimpItem *item,
+ GimpItem *new_parent,
+ gint new_index,
+ gboolean push_undo,
+ const gchar *undo_desc)
+{
+ GimpItemTreePrivate *private;
+ GimpContainer *container;
+ GimpContainer *new_container;
+ gint n_items;
+
+ g_return_val_if_fail (GIMP_IS_ITEM_TREE (tree), FALSE);
+
+ private = GIMP_ITEM_TREE_GET_PRIVATE (tree);
+
+ g_return_val_if_fail (G_TYPE_CHECK_INSTANCE_TYPE (item, private->item_type),
+ FALSE);
+ g_return_val_if_fail (gimp_item_get_tree (item) == tree, FALSE);
+ g_return_val_if_fail (new_parent == NULL ||
+ G_TYPE_CHECK_INSTANCE_TYPE (new_parent,
+ private->item_type),
+ FALSE);
+ g_return_val_if_fail (new_parent == NULL ||
+ gimp_item_get_tree (new_parent) == tree, FALSE);
+ g_return_val_if_fail (new_parent == NULL ||
+ gimp_viewable_get_children (GIMP_VIEWABLE (new_parent)),
+ FALSE);
+ g_return_val_if_fail (item != new_parent, FALSE);
+ g_return_val_if_fail (new_parent == NULL ||
+ ! gimp_viewable_is_ancestor (GIMP_VIEWABLE (item),
+ GIMP_VIEWABLE (new_parent)),
+ FALSE);
+
+ container = gimp_item_get_container (item);
+
+ if (new_parent)
+ new_container = gimp_viewable_get_children (GIMP_VIEWABLE (new_parent));
+ else
+ new_container = tree->container;
+
+ n_items = gimp_container_get_n_children (new_container);
+
+ if (new_container == container)
+ n_items--;
+
+ new_index = CLAMP (new_index, 0, n_items);
+
+ if (new_container != container ||
+ new_index != gimp_item_get_index (item))
+ {
+ if (push_undo)
+ gimp_image_undo_push_item_reorder (private->image, undo_desc, item);
+
+ if (new_container != container)
+ {
+ g_object_ref (item);
+
+ gimp_container_remove (container, GIMP_OBJECT (item));
+
+ gimp_viewable_set_parent (GIMP_VIEWABLE (item),
+ GIMP_VIEWABLE (new_parent));
+
+ gimp_container_insert (new_container, GIMP_OBJECT (item), new_index);
+
+ g_object_unref (item);
+ }
+ else
+ {
+ gimp_container_reorder (container, GIMP_OBJECT (item), new_index);
+ }
+ }
+
+ return TRUE;
+}
+
+void
+gimp_item_tree_rename_item (GimpItemTree *tree,
+ GimpItem *item,
+ const gchar *new_name,
+ gboolean push_undo,
+ const gchar *undo_desc)
+{
+ GimpItemTreePrivate *private;
+
+ g_return_if_fail (GIMP_IS_ITEM_TREE (tree));
+
+ private = GIMP_ITEM_TREE_GET_PRIVATE (tree);
+
+ g_return_if_fail (G_TYPE_CHECK_INSTANCE_TYPE (item, private->item_type));
+ g_return_if_fail (gimp_item_get_tree (item) == tree);
+ g_return_if_fail (new_name != NULL);
+
+ if (strcmp (new_name, gimp_object_get_name (item)))
+ {
+ if (push_undo)
+ gimp_image_undo_push_item_rename (gimp_item_get_image (item),
+ undo_desc, item);
+
+ gimp_item_tree_uniquefy_name (tree, item, new_name);
+ }
+}
+
+
+/* private functions */
+
+static void
+gimp_item_tree_uniquefy_name (GimpItemTree *tree,
+ GimpItem *item,
+ const gchar *new_name)
+{
+ GimpItemTreePrivate *private = GIMP_ITEM_TREE_GET_PRIVATE (tree);
+
+ if (new_name)
+ {
+ g_hash_table_remove (private->name_hash,
+ gimp_object_get_name (item));
+
+ gimp_object_set_name (GIMP_OBJECT (item), new_name);
+ }
+
+ /* Remove any trailing whitespace. */
+ if (gimp_object_get_name (item))
+ {
+ gchar *name = g_strchomp (g_strdup (gimp_object_get_name (item)));
+
+ gimp_object_take_name (GIMP_OBJECT (item), name);
+ }
+
+ if (g_hash_table_lookup (private->name_hash,
+ gimp_object_get_name (item)))
+ {
+ gchar *name = g_strdup (gimp_object_get_name (item));
+ gchar *new_name = NULL;
+ gint number = 0;
+ gint precision = 1;
+ GRegex *end_numbers = g_regex_new (" ?#([0-9]+)\\s*$", 0, 0, NULL);
+ GMatchInfo *match_info = NULL;
+
+ if (g_regex_match (end_numbers, name, 0, &match_info))
+ {
+ gchar *match;
+ gint start_pos;
+
+ match = g_match_info_fetch (match_info, 1);
+ if (match && match[0] == '0')
+ {
+ precision = strlen (match);
+ }
+ number = atoi (match);
+ g_free (match);
+
+ g_match_info_fetch_pos (match_info, 0,
+ &start_pos, NULL);
+ name[start_pos] = '\0';
+ }
+ g_match_info_free (match_info);
+ g_regex_unref (end_numbers);
+
+ do
+ {
+ number++;
+
+ g_free (new_name);
+
+ new_name = g_strdup_printf ("%s #%.*d",
+ name,
+ precision,
+ number);
+ }
+ while (g_hash_table_lookup (private->name_hash, new_name));
+
+ g_free (name);
+
+ gimp_object_take_name (GIMP_OBJECT (item), new_name);
+ }
+
+ g_hash_table_insert (private->name_hash,
+ (gpointer) gimp_object_get_name (item),
+ item);
+}
diff --git a/app/core/gimpitemtree.h b/app/core/gimpitemtree.h
new file mode 100644
index 0000000..4d3c6ea
--- /dev/null
+++ b/app/core/gimpitemtree.h
@@ -0,0 +1,89 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-1997 Spencer Kimball and Peter Mattis
+ *
+ * gimpitemtree.h
+ * Copyright (C) 2010 Michael Natterer <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_ITEM_TREE_H__
+#define __GIMP_ITEM_TREE_H__
+
+
+#include "gimpobject.h"
+
+
+#define GIMP_TYPE_ITEM_TREE (gimp_item_tree_get_type ())
+#define GIMP_ITEM_TREE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_ITEM_TREE, GimpItemTree))
+#define GIMP_ITEM_TREE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_ITEM_TREE, GimpItemTreeClass))
+#define GIMP_IS_ITEM_TREE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_ITEM_TREE))
+#define GIMP_IS_ITEM_TREE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_ITEM_TREE))
+
+
+typedef struct _GimpItemTreeClass GimpItemTreeClass;
+
+struct _GimpItemTree
+{
+ GimpObject parent_instance;
+
+ GimpContainer *container;
+};
+
+struct _GimpItemTreeClass
+{
+ GimpObjectClass parent_class;
+};
+
+
+GType gimp_item_tree_get_type (void) G_GNUC_CONST;
+GimpItemTree * gimp_item_tree_new (GimpImage *image,
+ GType container_type,
+ GType item_type);
+
+GimpItem * gimp_item_tree_get_active_item (GimpItemTree *tree);
+void gimp_item_tree_set_active_item (GimpItemTree *tree,
+ GimpItem *item);
+
+GimpItem * gimp_item_tree_get_item_by_name (GimpItemTree *tree,
+ const gchar *name);
+
+gboolean gimp_item_tree_get_insert_pos (GimpItemTree *tree,
+ GimpItem *item,
+ GimpItem **parent,
+ gint *position);
+
+void gimp_item_tree_add_item (GimpItemTree *tree,
+ GimpItem *item,
+ GimpItem *parent,
+ gint position);
+GimpItem * gimp_item_tree_remove_item (GimpItemTree *tree,
+ GimpItem *item,
+ GimpItem *new_active);
+
+gboolean gimp_item_tree_reorder_item (GimpItemTree *tree,
+ GimpItem *item,
+ GimpItem *new_parent,
+ gint new_index,
+ gboolean push_undo,
+ const gchar *undo_desc);
+
+void gimp_item_tree_rename_item (GimpItemTree *tree,
+ GimpItem *item,
+ const gchar *new_name,
+ gboolean push_undo,
+ const gchar *undo_desc);
+
+
+#endif /* __GIMP_ITEM_TREE_H__ */
diff --git a/app/core/gimpitemundo.c b/app/core/gimpitemundo.c
new file mode 100644
index 0000000..0a50d43
--- /dev/null
+++ b/app/core/gimpitemundo.c
@@ -0,0 +1,139 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "core-types.h"
+
+#include "gimpimage.h"
+#include "gimpitem.h"
+#include "gimpitemundo.h"
+
+
+enum
+{
+ PROP_0,
+ PROP_ITEM
+};
+
+
+static void gimp_item_undo_constructed (GObject *object);
+static void gimp_item_undo_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_item_undo_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static void gimp_item_undo_free (GimpUndo *undo,
+ GimpUndoMode undo_mode);
+
+
+G_DEFINE_TYPE (GimpItemUndo, gimp_item_undo, GIMP_TYPE_UNDO)
+
+#define parent_class gimp_item_undo_parent_class
+
+
+static void
+gimp_item_undo_class_init (GimpItemUndoClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpUndoClass *undo_class = GIMP_UNDO_CLASS (klass);
+
+ object_class->constructed = gimp_item_undo_constructed;
+ object_class->set_property = gimp_item_undo_set_property;
+ object_class->get_property = gimp_item_undo_get_property;
+
+ undo_class->free = gimp_item_undo_free;
+
+ g_object_class_install_property (object_class, PROP_ITEM,
+ g_param_spec_object ("item", NULL, NULL,
+ GIMP_TYPE_ITEM,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+}
+
+static void
+gimp_item_undo_init (GimpItemUndo *undo)
+{
+}
+
+static void
+gimp_item_undo_constructed (GObject *object)
+{
+ GimpItemUndo *item_undo = GIMP_ITEM_UNDO (object);
+
+ G_OBJECT_CLASS (parent_class)->constructed (object);
+
+ gimp_assert (GIMP_IS_ITEM (item_undo->item));
+}
+
+static void
+gimp_item_undo_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpItemUndo *item_undo = GIMP_ITEM_UNDO (object);
+
+ switch (property_id)
+ {
+ case PROP_ITEM:
+ item_undo->item = g_value_dup_object (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_item_undo_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpItemUndo *item_undo = GIMP_ITEM_UNDO (object);
+
+ switch (property_id)
+ {
+ case PROP_ITEM:
+ g_value_set_object (value, item_undo->item);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_item_undo_free (GimpUndo *undo,
+ GimpUndoMode undo_mode)
+{
+ GimpItemUndo *item_undo = GIMP_ITEM_UNDO (undo);
+
+ g_clear_object (&item_undo->item);
+
+ GIMP_UNDO_CLASS (parent_class)->free (undo, undo_mode);
+}
diff --git a/app/core/gimpitemundo.h b/app/core/gimpitemundo.h
new file mode 100644
index 0000000..91fe8d6
--- /dev/null
+++ b/app/core/gimpitemundo.h
@@ -0,0 +1,52 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_ITEM_UNDO_H__
+#define __GIMP_ITEM_UNDO_H__
+
+
+#include "gimpundo.h"
+
+
+#define GIMP_TYPE_ITEM_UNDO (gimp_item_undo_get_type ())
+#define GIMP_ITEM_UNDO(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_ITEM_UNDO, GimpItemUndo))
+#define GIMP_ITEM_UNDO_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_ITEM_UNDO, GimpItemUndoClass))
+#define GIMP_IS_ITEM_UNDO(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_ITEM_UNDO))
+#define GIMP_IS_ITEM_UNDO_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_ITEM_UNDO))
+#define GIMP_ITEM_UNDO_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_ITEM_UNDO, GimpItemUndoClass))
+
+
+typedef struct _GimpItemUndo GimpItemUndo;
+typedef struct _GimpItemUndoClass GimpItemUndoClass;
+
+struct _GimpItemUndo
+{
+ GimpUndo parent_instance;
+
+ GimpItem *item; /* the item this undo is for */
+};
+
+struct _GimpItemUndoClass
+{
+ GimpUndoClass parent_class;
+};
+
+
+GType gimp_item_undo_get_type (void) G_GNUC_CONST;
+
+
+#endif /* __GIMP_ITEM_UNDO_H__ */
diff --git a/app/core/gimplayer-floating-selection.c b/app/core/gimplayer-floating-selection.c
new file mode 100644
index 0000000..80222a2
--- /dev/null
+++ b/app/core/gimplayer-floating-selection.c
@@ -0,0 +1,332 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpbase/gimpbase.h"
+
+#include "core-types.h"
+
+#include "gimpboundary.h"
+#include "gimpdrawable-filters.h"
+#include "gimpdrawable-floating-selection.h"
+#include "gimperror.h"
+#include "gimpimage.h"
+#include "gimpimage-undo.h"
+#include "gimpimage-undo-push.h"
+#include "gimplayer.h"
+#include "gimplayer-floating-selection.h"
+#include "gimplayermask.h"
+
+#include "gimp-intl.h"
+
+
+/* public functions */
+
+void
+floating_sel_attach (GimpLayer *layer,
+ GimpDrawable *drawable)
+{
+ GimpImage *image;
+ GimpLayer *floating_sel;
+ GimpLayer *parent = NULL;
+ gint position = 0;
+
+ g_return_if_fail (GIMP_IS_LAYER (layer));
+ g_return_if_fail (GIMP_IS_DRAWABLE (drawable));
+ g_return_if_fail (gimp_item_is_attached (GIMP_ITEM (drawable)));
+ g_return_if_fail (drawable != GIMP_DRAWABLE (layer));
+ g_return_if_fail (gimp_item_get_image (GIMP_ITEM (layer)) ==
+ gimp_item_get_image (GIMP_ITEM (drawable)));
+
+ image = gimp_item_get_image (GIMP_ITEM (drawable));
+
+ floating_sel = gimp_image_get_floating_selection (image);
+
+ /* If there is already a floating selection, anchor it */
+ if (floating_sel)
+ {
+ floating_sel_anchor (floating_sel);
+
+ /* if we were pasting to the old floating selection, paste now
+ * to the drawable
+ */
+ if (drawable == (GimpDrawable *) floating_sel)
+ drawable = gimp_image_get_active_drawable (image);
+ }
+
+ gimp_layer_set_lock_alpha (layer, TRUE, FALSE);
+
+ gimp_layer_set_floating_sel_drawable (layer, drawable);
+
+ /* Floating selection layer placement, default to the top of the
+ * layers stack; parent and position are adapted according to the
+ * drawable associated with the floating selection.
+ */
+
+ if (GIMP_IS_LAYER_MASK (drawable))
+ {
+ GimpLayer *tmp = gimp_layer_mask_get_layer (GIMP_LAYER_MASK (drawable));
+
+ parent = GIMP_LAYER (gimp_item_get_parent (GIMP_ITEM (tmp)));
+ position = gimp_item_get_index (GIMP_ITEM (tmp));
+ }
+ else if (GIMP_IS_LAYER (drawable))
+ {
+ parent = GIMP_LAYER (gimp_item_get_parent (GIMP_ITEM (drawable)));
+ position = gimp_item_get_index (GIMP_ITEM (drawable));
+ }
+
+ gimp_image_add_layer (image, layer, parent, position, TRUE);
+}
+
+void
+floating_sel_anchor (GimpLayer *layer)
+{
+ GimpImage *image;
+ GimpDrawable *drawable;
+ GimpFilter *filter = NULL;
+ GeglRectangle bounding_box;
+ GeglRectangle dr_bounding_box;
+ gint off_x, off_y;
+ gint dr_off_x, dr_off_y;
+
+ g_return_if_fail (GIMP_IS_LAYER (layer));
+ g_return_if_fail (gimp_layer_is_floating_sel (layer));
+
+ /* Don't let gimp_image_remove_layer free the layer while we still need it */
+ g_object_ref (layer);
+
+ image = gimp_item_get_image (GIMP_ITEM (layer));
+
+ gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_FS_ANCHOR,
+ C_("undo-type", "Anchor Floating Selection"));
+
+ drawable = gimp_layer_get_floating_sel_drawable (layer);
+
+ gimp_item_get_offset (GIMP_ITEM (layer), &off_x, &off_y);
+ gimp_item_get_offset (GIMP_ITEM (drawable), &dr_off_x, &dr_off_y);
+
+ bounding_box = gimp_drawable_get_bounding_box (GIMP_DRAWABLE (layer));
+ dr_bounding_box = gimp_drawable_get_bounding_box (drawable);
+
+ bounding_box.x += off_x;
+ bounding_box.y += off_y;
+
+ dr_bounding_box.x += dr_off_x;
+ dr_bounding_box.y += dr_off_y;
+
+ if (gimp_item_get_visible (GIMP_ITEM (layer)) &&
+ gegl_rectangle_intersect (NULL, &bounding_box, &dr_bounding_box))
+ {
+ filter = gimp_drawable_get_floating_sel_filter (drawable);
+ }
+
+ if (filter)
+ {
+ gimp_drawable_merge_filter (drawable, filter, NULL, NULL,
+ NULL, FALSE, FALSE, FALSE);
+ }
+
+ gimp_image_remove_layer (image, layer, TRUE, NULL);
+
+ gimp_image_undo_group_end (image);
+
+ /* invalidate the boundaries */
+ gimp_drawable_invalidate_boundary (GIMP_DRAWABLE (gimp_image_get_mask (image)));
+
+ g_object_unref (layer);
+}
+
+gboolean
+floating_sel_to_layer (GimpLayer *layer,
+ GError **error)
+{
+ GimpItem *item;
+ GimpImage *image;
+
+ g_return_val_if_fail (GIMP_IS_LAYER (layer), FALSE);
+ g_return_val_if_fail (gimp_layer_is_floating_sel (layer), FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ item = GIMP_ITEM (layer);
+ image = gimp_item_get_image (item);
+
+ /* Check if the floating layer belongs to a channel */
+ if (GIMP_IS_CHANNEL (gimp_layer_get_floating_sel_drawable (layer)))
+ {
+ g_set_error_literal (error, GIMP_ERROR, GIMP_FAILED,
+ _("Cannot create a new layer from the floating "
+ "selection because it belongs to a layer mask "
+ "or channel."));
+ return FALSE;
+ }
+
+ gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_FS_TO_LAYER,
+ C_("undo-type", "Floating Selection to Layer"));
+
+ gimp_image_undo_push_fs_to_layer (image, NULL, layer);
+
+ gimp_drawable_detach_floating_sel (gimp_layer_get_floating_sel_drawable (layer));
+ gimp_layer_set_floating_sel_drawable (layer, NULL);
+
+ gimp_item_set_visible (item, TRUE, TRUE);
+ gimp_layer_set_lock_alpha (layer, FALSE, TRUE);
+
+ gimp_image_undo_group_end (image);
+
+ /* When the floating selection is converted to/from a normal layer
+ * it does something resembling a name change, so emit the
+ * "name-changed" signal
+ */
+ gimp_object_name_changed (GIMP_OBJECT (layer));
+
+ gimp_drawable_update (GIMP_DRAWABLE (layer),
+ 0, 0,
+ gimp_item_get_width (item),
+ gimp_item_get_height (item));
+
+ return TRUE;
+}
+
+void
+floating_sel_activate_drawable (GimpLayer *layer)
+{
+ GimpImage *image;
+ GimpDrawable *drawable;
+
+ g_return_if_fail (GIMP_IS_LAYER (layer));
+ g_return_if_fail (gimp_layer_is_floating_sel (layer));
+
+ image = gimp_item_get_image (GIMP_ITEM (layer));
+
+ drawable = gimp_layer_get_floating_sel_drawable (layer);
+
+ /* set the underlying drawable to active */
+ if (GIMP_IS_LAYER_MASK (drawable))
+ {
+ GimpLayerMask *mask = GIMP_LAYER_MASK (drawable);
+
+ gimp_image_set_active_layer (image, gimp_layer_mask_get_layer (mask));
+ }
+ else if (GIMP_IS_CHANNEL (drawable))
+ {
+ gimp_image_set_active_channel (image, GIMP_CHANNEL (drawable));
+ }
+ else
+ {
+ gimp_image_set_active_layer (image, GIMP_LAYER (drawable));
+ }
+}
+
+const GimpBoundSeg *
+floating_sel_boundary (GimpLayer *layer,
+ gint *n_segs)
+{
+ g_return_val_if_fail (GIMP_IS_LAYER (layer), NULL);
+ g_return_val_if_fail (gimp_layer_is_floating_sel (layer), NULL);
+ g_return_val_if_fail (n_segs != NULL, NULL);
+
+ if (layer->fs.boundary_known == FALSE)
+ {
+ gint width, height;
+ gint off_x, off_y;
+
+ width = gimp_item_get_width (GIMP_ITEM (layer));
+ height = gimp_item_get_height (GIMP_ITEM (layer));
+ gimp_item_get_offset (GIMP_ITEM (layer), &off_x, &off_y);
+
+ if (layer->fs.segs)
+ g_free (layer->fs.segs);
+
+ if (gimp_drawable_has_alpha (GIMP_DRAWABLE (layer)))
+ {
+ GeglBuffer *buffer;
+ gint i;
+
+ /* find the segments */
+ buffer = gimp_drawable_get_buffer (GIMP_DRAWABLE (layer));
+
+ layer->fs.segs = gimp_boundary_find (buffer, NULL,
+ babl_format ("A float"),
+ GIMP_BOUNDARY_WITHIN_BOUNDS,
+ 0, 0, width, height,
+ GIMP_BOUNDARY_HALF_WAY,
+ &layer->fs.num_segs);
+
+ /* offset the segments */
+ for (i = 0; i < layer->fs.num_segs; i++)
+ {
+ layer->fs.segs[i].x1 += off_x;
+ layer->fs.segs[i].y1 += off_y;
+ layer->fs.segs[i].x2 += off_x;
+ layer->fs.segs[i].y2 += off_y;
+ }
+ }
+ else
+ {
+ layer->fs.num_segs = 4;
+ layer->fs.segs = g_new0 (GimpBoundSeg, 4);
+
+ /* top */
+ layer->fs.segs[0].x1 = off_x;
+ layer->fs.segs[0].y1 = off_y;
+ layer->fs.segs[0].x2 = off_x + width;
+ layer->fs.segs[0].y2 = off_y;
+
+ /* left */
+ layer->fs.segs[1].x1 = off_x;
+ layer->fs.segs[1].y1 = off_y;
+ layer->fs.segs[1].x2 = off_x;
+ layer->fs.segs[1].y2 = off_y + height;
+
+ /* right */
+ layer->fs.segs[2].x1 = off_x + width;
+ layer->fs.segs[2].y1 = off_y;
+ layer->fs.segs[2].x2 = off_x + width;
+ layer->fs.segs[2].y2 = off_y + height;
+
+ /* bottom */
+ layer->fs.segs[3].x1 = off_x;
+ layer->fs.segs[3].y1 = off_y + height;
+ layer->fs.segs[3].x2 = off_x + width;
+ layer->fs.segs[3].y2 = off_y + height;
+ }
+
+ layer->fs.boundary_known = TRUE;
+ }
+
+ *n_segs = layer->fs.num_segs;
+
+ return layer->fs.segs;
+}
+
+void
+floating_sel_invalidate (GimpLayer *layer)
+{
+ g_return_if_fail (GIMP_IS_LAYER (layer));
+ g_return_if_fail (gimp_layer_is_floating_sel (layer));
+
+ /* Invalidate the attached-to drawable's preview */
+ gimp_viewable_invalidate_preview (GIMP_VIEWABLE (gimp_layer_get_floating_sel_drawable (layer)));
+
+ /* Invalidate the boundary */
+ layer->fs.boundary_known = FALSE;
+}
diff --git a/app/core/gimplayer-floating-selection.h b/app/core/gimplayer-floating-selection.h
new file mode 100644
index 0000000..e111c1a
--- /dev/null
+++ b/app/core/gimplayer-floating-selection.h
@@ -0,0 +1,33 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_LAYER_FLOATING_SELECTION_H__
+#define __GIMP_LAYER_FLOATING_SELECTION_H__
+
+
+void floating_sel_attach (GimpLayer *layer,
+ GimpDrawable *drawable);
+void floating_sel_anchor (GimpLayer *layer);
+gboolean floating_sel_to_layer (GimpLayer *layer,
+ GError **error);
+void floating_sel_activate_drawable (GimpLayer *layer);
+const GimpBoundSeg * floating_sel_boundary (GimpLayer *layer,
+ gint *n_segs);
+void floating_sel_invalidate (GimpLayer *layer);
+
+
+#endif /* __GIMP_LAYER_FLOATING_SELECTION_H__ */
diff --git a/app/core/gimplayer-new.c b/app/core/gimplayer-new.c
new file mode 100644
index 0000000..7ea2450
--- /dev/null
+++ b/app/core/gimplayer-new.c
@@ -0,0 +1,253 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <cairo.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpcolor/gimpcolor.h"
+#include "libgimpconfig/gimpconfig.h"
+
+#include "core-types.h"
+
+#include "gegl/gimp-babl.h"
+#include "gegl/gimp-gegl-loops.h"
+
+#include "gimpbuffer.h"
+#include "gimpimage.h"
+#include "gimpimage-color-profile.h"
+#include "gimplayer.h"
+#include "gimplayer-new.h"
+
+
+/* local function prototypes */
+
+static void gimp_layer_new_convert_buffer (GimpLayer *layer,
+ GeglBuffer *src_buffer,
+ GimpColorProfile *src_profile,
+ GError **error);
+
+
+/* public functions */
+
+GimpLayer *
+gimp_layer_new (GimpImage *image,
+ gint width,
+ gint height,
+ const Babl *format,
+ const gchar *name,
+ gdouble opacity,
+ GimpLayerMode mode)
+{
+ GimpLayer *layer;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+ g_return_val_if_fail (width > 0, NULL);
+ g_return_val_if_fail (height > 0, NULL);
+ g_return_val_if_fail (format != NULL, NULL);
+
+ layer = GIMP_LAYER (gimp_drawable_new (GIMP_TYPE_LAYER,
+ image, name,
+ 0, 0, width, height,
+ format));
+
+ gimp_layer_set_opacity (layer, opacity, FALSE);
+ gimp_layer_set_mode (layer, mode, FALSE);
+
+ return layer;
+}
+
+/**
+ * gimp_layer_new_from_buffer:
+ * @buffer: The buffer to make the new layer from.
+ * @dest_image: The image the new layer will be added to.
+ * @format: The #Babl format of the new layer.
+ * @name: The new layer's name.
+ * @opacity: The new layer's opacity.
+ * @mode: The new layer's mode.
+ *
+ * Copies %buffer to a layer taking into consideration the
+ * possibility of transforming the contents to meet the requirements
+ * of the target image type
+ *
+ * Return value: The new layer.
+ **/
+GimpLayer *
+gimp_layer_new_from_buffer (GimpBuffer *buffer,
+ GimpImage *dest_image,
+ const Babl *format,
+ const gchar *name,
+ gdouble opacity,
+ GimpLayerMode mode)
+{
+ g_return_val_if_fail (GIMP_IS_BUFFER (buffer), NULL);
+ g_return_val_if_fail (GIMP_IS_IMAGE (dest_image), NULL);
+ g_return_val_if_fail (format != NULL, NULL);
+
+ return gimp_layer_new_from_gegl_buffer (gimp_buffer_get_buffer (buffer),
+ dest_image, format,
+ name, opacity, mode,
+ gimp_buffer_get_color_profile (buffer));
+}
+
+/**
+ * gimp_layer_new_from_gegl_buffer:
+ * @buffer: The buffer to make the new layer from.
+ * @dest_image: The image the new layer will be added to.
+ * @format: The #Babl format of the new layer.
+ * @name: The new layer's name.
+ * @opacity: The new layer's opacity.
+ * @mode: The new layer's mode.
+ *
+ * Copies %buffer to a layer taking into consideration the
+ * possibility of transforming the contents to meet the requirements
+ * of the target image type
+ *
+ * Return value: The new layer.
+ **/
+GimpLayer *
+gimp_layer_new_from_gegl_buffer (GeglBuffer *buffer,
+ GimpImage *dest_image,
+ const Babl *format,
+ const gchar *name,
+ gdouble opacity,
+ GimpLayerMode mode,
+ GimpColorProfile *buffer_profile)
+{
+ GimpLayer *layer;
+ const GeglRectangle *extent;
+
+ g_return_val_if_fail (GEGL_IS_BUFFER (buffer), NULL);
+ g_return_val_if_fail (GIMP_IS_IMAGE (dest_image), NULL);
+ g_return_val_if_fail (format != NULL, NULL);
+ g_return_val_if_fail (buffer_profile == NULL ||
+ GIMP_IS_COLOR_PROFILE (buffer_profile), NULL);
+
+ extent = gegl_buffer_get_extent (buffer);
+
+ /* do *not* use the buffer's format because this function gets
+ * buffers of any format passed, and converts them
+ */
+ layer = gimp_layer_new (dest_image,
+ extent->width, extent->height,
+ format,
+ name, opacity, mode);
+
+ if (extent->x != 0 || extent->y != 0)
+ gimp_item_translate (GIMP_ITEM (layer), extent->x, extent->y, FALSE);
+
+ gimp_layer_new_convert_buffer (layer, buffer, buffer_profile, NULL);
+
+ return layer;
+}
+
+/**
+ * gimp_layer_new_from_pixbuf:
+ * @pixbuf: The pixbuf to make the new layer from.
+ * @dest_image: The image the new layer will be added to.
+ * @format: The #Babl format of the new layer.
+ * @name: The new layer's name.
+ * @opacity: The new layer's opacity.
+ * @mode: The new layer's mode.
+ *
+ * Copies %pixbuf to a layer taking into consideration the
+ * possibility of transforming the contents to meet the requirements
+ * of the target image type
+ *
+ * Return value: The new layer.
+ **/
+GimpLayer *
+gimp_layer_new_from_pixbuf (GdkPixbuf *pixbuf,
+ GimpImage *dest_image,
+ const Babl *format,
+ const gchar *name,
+ gdouble opacity,
+ GimpLayerMode mode)
+{
+ GimpLayer *layer;
+ GeglBuffer *buffer;
+ guint8 *icc_data;
+ gsize icc_len;
+ GimpColorProfile *profile = NULL;
+
+ g_return_val_if_fail (GDK_IS_PIXBUF (pixbuf), NULL);
+ g_return_val_if_fail (GIMP_IS_IMAGE (dest_image), NULL);
+ g_return_val_if_fail (format != NULL, NULL);
+
+ layer = gimp_layer_new (dest_image,
+ gdk_pixbuf_get_width (pixbuf),
+ gdk_pixbuf_get_height (pixbuf),
+ format, name, opacity, mode);
+
+ buffer = gimp_pixbuf_create_buffer (pixbuf);
+
+ icc_data = gimp_pixbuf_get_icc_profile (pixbuf, &icc_len);
+ if (icc_data)
+ {
+ profile = gimp_color_profile_new_from_icc_profile (icc_data, icc_len,
+ NULL);
+ g_free (icc_data);
+ }
+
+ gimp_layer_new_convert_buffer (layer, buffer, profile, NULL);
+
+ if (profile)
+ g_object_unref (profile);
+
+ g_object_unref (buffer);
+
+ return layer;
+}
+
+
+/* private functions */
+
+static void
+gimp_layer_new_convert_buffer (GimpLayer *layer,
+ GeglBuffer *src_buffer,
+ GimpColorProfile *src_profile,
+ GError **error)
+{
+ GimpDrawable *drawable = GIMP_DRAWABLE (layer);
+ GimpImage *image = gimp_item_get_image (GIMP_ITEM (layer));
+ GeglBuffer *dest_buffer = gimp_drawable_get_buffer (drawable);
+ GimpColorProfile *dest_profile;
+
+ if (! gimp_image_get_is_color_managed (image))
+ {
+ gimp_gegl_buffer_copy (src_buffer, NULL, GEGL_ABYSS_NONE,
+ dest_buffer, NULL);
+ return;
+ }
+
+ if (! src_profile)
+ {
+ const Babl *src_format = gegl_buffer_get_format (src_buffer);
+
+ src_profile = gimp_babl_format_get_color_profile (src_format);
+ }
+
+ dest_profile =
+ gimp_color_managed_get_color_profile (GIMP_COLOR_MANAGED (layer));
+
+ gimp_gegl_convert_color_profile (src_buffer, NULL, src_profile,
+ dest_buffer, NULL, dest_profile,
+ GIMP_COLOR_RENDERING_INTENT_PERCEPTUAL,
+ TRUE, NULL);
+}
diff --git a/app/core/gimplayer-new.h b/app/core/gimplayer-new.h
new file mode 100644
index 0000000..58b3aa9
--- /dev/null
+++ b/app/core/gimplayer-new.h
@@ -0,0 +1,51 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_LAYER_NEW_H__
+#define __GIMP_LAYER_NEW_H__
+
+
+GimpLayer * gimp_layer_new (GimpImage *image,
+ gint width,
+ gint height,
+ const Babl *format,
+ const gchar *name,
+ gdouble opacity,
+ GimpLayerMode mode);
+
+GimpLayer * gimp_layer_new_from_buffer (GimpBuffer *buffer,
+ GimpImage *dest_image,
+ const Babl *format,
+ const gchar *name,
+ gdouble opacity,
+ GimpLayerMode mode);
+GimpLayer * gimp_layer_new_from_gegl_buffer (GeglBuffer *buffer,
+ GimpImage *dest_image,
+ const Babl *format,
+ const gchar *name,
+ gdouble opacity,
+ GimpLayerMode mode,
+ GimpColorProfile *buffer_profile);
+GimpLayer * gimp_layer_new_from_pixbuf (GdkPixbuf *pixbuf,
+ GimpImage *dest_image,
+ const Babl *format,
+ const gchar *name,
+ gdouble opacity,
+ GimpLayerMode mode);
+
+
+#endif /* __GIMP_LAYER_NEW_H__ */
diff --git a/app/core/gimplayer.c b/app/core/gimplayer.c
new file mode 100644
index 0000000..3887007
--- /dev/null
+++ b/app/core/gimplayer.c
@@ -0,0 +1,2943 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <cairo.h>
+#include <gegl.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+#include "libgimpbase/gimpbase.h"
+#include "libgimpcolor/gimpcolor.h"
+#include "libgimpconfig/gimpconfig.h"
+#include "libgimpmath/gimpmath.h"
+
+#include "core-types.h"
+
+#include "operations/layer-modes/gimp-layer-modes.h"
+
+#include "gegl/gimp-babl.h"
+#include "gegl/gimp-gegl-apply-operation.h"
+#include "gegl/gimp-gegl-loops.h"
+#include "gegl/gimp-gegl-nodes.h"
+
+#include "gimpboundary.h"
+#include "gimpchannel-select.h"
+#include "gimpcontext.h"
+#include "gimpcontainer.h"
+#include "gimpdrawable-floating-selection.h"
+#include "gimperror.h"
+#include "gimpgrouplayer.h"
+#include "gimpimage-undo-push.h"
+#include "gimpimage-undo.h"
+#include "gimpimage.h"
+#include "gimpimage-color-profile.h"
+#include "gimplayer-floating-selection.h"
+#include "gimplayer.h"
+#include "gimplayermask.h"
+#include "gimpmarshal.h"
+#include "gimpobjectqueue.h"
+#include "gimppickable.h"
+#include "gimpprogress.h"
+
+#include "gimp-intl.h"
+
+
+enum
+{
+ OPACITY_CHANGED,
+ MODE_CHANGED,
+ BLEND_SPACE_CHANGED,
+ COMPOSITE_SPACE_CHANGED,
+ COMPOSITE_MODE_CHANGED,
+ EFFECTIVE_MODE_CHANGED,
+ EXCLUDES_BACKDROP_CHANGED,
+ LOCK_ALPHA_CHANGED,
+ MASK_CHANGED,
+ APPLY_MASK_CHANGED,
+ EDIT_MASK_CHANGED,
+ SHOW_MASK_CHANGED,
+ LAST_SIGNAL
+};
+
+enum
+{
+ PROP_0,
+ PROP_OPACITY,
+ PROP_MODE,
+ PROP_BLEND_SPACE,
+ PROP_COMPOSITE_SPACE,
+ PROP_COMPOSITE_MODE,
+ PROP_EXCLUDES_BACKDROP,
+ PROP_LOCK_ALPHA,
+ PROP_MASK,
+ PROP_FLOATING_SELECTION
+};
+
+
+static void gimp_color_managed_iface_init (GimpColorManagedInterface *iface);
+static void gimp_pickable_iface_init (GimpPickableInterface *iface);
+
+static void gimp_layer_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_layer_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+static void gimp_layer_dispose (GObject *object);
+static void gimp_layer_finalize (GObject *object);
+static void gimp_layer_notify (GObject *object,
+ GParamSpec *pspec);
+
+static void gimp_layer_name_changed (GimpObject *object);
+static gint64 gimp_layer_get_memsize (GimpObject *object,
+ gint64 *gui_size);
+
+static void gimp_layer_invalidate_preview (GimpViewable *viewable);
+static gchar * gimp_layer_get_description (GimpViewable *viewable,
+ gchar **tooltip);
+
+static GeglNode * gimp_layer_get_node (GimpFilter *filter);
+
+static void gimp_layer_removed (GimpItem *item);
+static void gimp_layer_unset_removed (GimpItem *item);
+static gboolean gimp_layer_is_attached (GimpItem *item);
+static GimpItemTree * gimp_layer_get_tree (GimpItem *item);
+static GimpItem * gimp_layer_duplicate (GimpItem *item,
+ GType new_type);
+static void gimp_layer_convert (GimpItem *item,
+ GimpImage *dest_image,
+ GType old_type);
+static gboolean gimp_layer_rename (GimpItem *item,
+ const gchar *new_name,
+ const gchar *undo_desc,
+ GError **error);
+static void gimp_layer_start_move (GimpItem *item,
+ gboolean push_undo);
+static void gimp_layer_end_move (GimpItem *item,
+ gboolean push_undo);
+static void gimp_layer_translate (GimpItem *item,
+ gdouble offset_x,
+ gdouble offset_y,
+ gboolean push_undo);
+static void gimp_layer_scale (GimpItem *item,
+ gint new_width,
+ gint new_height,
+ gint new_offset_x,
+ gint new_offset_y,
+ GimpInterpolationType interp_type,
+ GimpProgress *progress);
+static void gimp_layer_resize (GimpItem *item,
+ GimpContext *context,
+ GimpFillType fill_type,
+ gint new_width,
+ gint new_height,
+ gint offset_x,
+ gint offset_y);
+static void gimp_layer_flip (GimpItem *item,
+ GimpContext *context,
+ GimpOrientationType flip_type,
+ gdouble axis,
+ gboolean clip_result);
+static void gimp_layer_rotate (GimpItem *item,
+ GimpContext *context,
+ GimpRotationType rotate_type,
+ gdouble center_x,
+ gdouble center_y,
+ gboolean clip_result);
+static void gimp_layer_transform (GimpItem *item,
+ GimpContext *context,
+ const GimpMatrix3 *matrix,
+ GimpTransformDirection direction,
+ GimpInterpolationType interpolation_type,
+ GimpTransformResize clip_result,
+ GimpProgress *progress);
+static void gimp_layer_to_selection (GimpItem *item,
+ GimpChannelOps op,
+ gboolean antialias,
+ gboolean feather,
+ gdouble feather_radius_x,
+ gdouble feather_radius_y);
+
+static void gimp_layer_alpha_changed (GimpDrawable *drawable);
+static gint64 gimp_layer_estimate_memsize (GimpDrawable *drawable,
+ GimpComponentType component_type,
+ gint width,
+ gint height);
+static gboolean gimp_layer_supports_alpha (GimpDrawable *drawable);
+static void gimp_layer_convert_type (GimpDrawable *drawable,
+ GimpImage *dest_image,
+ const Babl *new_format,
+ GimpColorProfile *dest_profile,
+ GeglDitherMethod layer_dither_type,
+ GeglDitherMethod mask_dither_type,
+ gboolean push_undo,
+ GimpProgress *progress);
+static void gimp_layer_invalidate_boundary (GimpDrawable *drawable);
+static void gimp_layer_get_active_components (GimpDrawable *drawable,
+ gboolean *active);
+static GimpComponentMask
+ gimp_layer_get_active_mask (GimpDrawable *drawable);
+static void gimp_layer_set_buffer (GimpDrawable *drawable,
+ gboolean push_undo,
+ const gchar *undo_desc,
+ GeglBuffer *buffer,
+ const GeglRectangle *bounds);
+static GeglRectangle
+ gimp_layer_get_bounding_box (GimpDrawable *drawable);
+
+static GimpColorProfile *
+ gimp_layer_get_color_profile (GimpColorManaged *managed);
+
+static gdouble gimp_layer_get_opacity_at (GimpPickable *pickable,
+ gint x,
+ gint y);
+static void gimp_layer_pixel_to_srgb (GimpPickable *pickable,
+ const Babl *format,
+ gpointer pixel,
+ GimpRGB *color);
+static void gimp_layer_srgb_to_pixel (GimpPickable *pickable,
+ const GimpRGB *color,
+ const Babl *format,
+ gpointer pixel);
+
+static void gimp_layer_real_translate (GimpLayer *layer,
+ gint offset_x,
+ gint offset_y);
+static void gimp_layer_real_scale (GimpLayer *layer,
+ gint new_width,
+ gint new_height,
+ gint new_offset_x,
+ gint new_offset_y,
+ GimpInterpolationType interp_type,
+ GimpProgress *progress);
+static void gimp_layer_real_resize (GimpLayer *layer,
+ GimpContext *context,
+ GimpFillType fill_type,
+ gint new_width,
+ gint new_height,
+ gint offset_x,
+ gint offset_y);
+static void gimp_layer_real_flip (GimpLayer *layer,
+ GimpContext *context,
+ GimpOrientationType flip_type,
+ gdouble axis,
+ gboolean clip_result);
+static void gimp_layer_real_rotate (GimpLayer *layer,
+ GimpContext *context,
+ GimpRotationType rotate_type,
+ gdouble center_x,
+ gdouble center_y,
+ gboolean clip_result);
+static void gimp_layer_real_transform (GimpLayer *layer,
+ GimpContext *context,
+ const GimpMatrix3 *matrix,
+ GimpTransformDirection direction,
+ GimpInterpolationType interpolation_type,
+ GimpTransformResize clip_result,
+ GimpProgress *progress);
+static void gimp_layer_real_convert_type (GimpLayer *layer,
+ GimpImage *dest_image,
+ const Babl *new_format,
+ GimpColorProfile *dest_profile,
+ GeglDitherMethod layer_dither_type,
+ GeglDitherMethod mask_dither_type,
+ gboolean push_undo,
+ GimpProgress *progress);
+static GeglRectangle
+ gimp_layer_real_get_bounding_box (GimpLayer *layer);
+static void gimp_layer_real_get_effective_mode (GimpLayer *layer,
+ GimpLayerMode *mode,
+ GimpLayerColorSpace *blend_space,
+ GimpLayerColorSpace *composite_space,
+ GimpLayerCompositeMode *composite_mode);
+static gboolean
+ gimp_layer_real_get_excludes_backdrop (GimpLayer *layer);
+
+static void gimp_layer_layer_mask_update (GimpDrawable *layer_mask,
+ gint x,
+ gint y,
+ gint width,
+ gint height,
+ GimpLayer *layer);
+
+
+G_DEFINE_TYPE_WITH_CODE (GimpLayer, gimp_layer, GIMP_TYPE_DRAWABLE,
+ G_IMPLEMENT_INTERFACE (GIMP_TYPE_COLOR_MANAGED,
+ gimp_color_managed_iface_init)
+ G_IMPLEMENT_INTERFACE (GIMP_TYPE_PICKABLE,
+ gimp_pickable_iface_init))
+
+#define parent_class gimp_layer_parent_class
+
+static guint layer_signals[LAST_SIGNAL] = { 0 };
+
+
+static void
+gimp_layer_class_init (GimpLayerClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpObjectClass *gimp_object_class = GIMP_OBJECT_CLASS (klass);
+ GimpViewableClass *viewable_class = GIMP_VIEWABLE_CLASS (klass);
+ GimpFilterClass *filter_class = GIMP_FILTER_CLASS (klass);
+ GimpItemClass *item_class = GIMP_ITEM_CLASS (klass);
+ GimpDrawableClass *drawable_class = GIMP_DRAWABLE_CLASS (klass);
+
+ layer_signals[OPACITY_CHANGED] =
+ g_signal_new ("opacity-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpLayerClass, opacity_changed),
+ NULL, NULL,
+ gimp_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ layer_signals[MODE_CHANGED] =
+ g_signal_new ("mode-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpLayerClass, mode_changed),
+ NULL, NULL,
+ gimp_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ layer_signals[BLEND_SPACE_CHANGED] =
+ g_signal_new ("blend-space-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpLayerClass, blend_space_changed),
+ NULL, NULL,
+ gimp_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ layer_signals[COMPOSITE_SPACE_CHANGED] =
+ g_signal_new ("composite-space-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpLayerClass, composite_space_changed),
+ NULL, NULL,
+ gimp_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ layer_signals[COMPOSITE_MODE_CHANGED] =
+ g_signal_new ("composite-mode-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpLayerClass, composite_mode_changed),
+ NULL, NULL,
+ gimp_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ layer_signals[EFFECTIVE_MODE_CHANGED] =
+ g_signal_new ("effective-mode-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpLayerClass, effective_mode_changed),
+ NULL, NULL,
+ gimp_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ layer_signals[EXCLUDES_BACKDROP_CHANGED] =
+ g_signal_new ("excludes-backdrop-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpLayerClass, excludes_backdrop_changed),
+ NULL, NULL,
+ gimp_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ layer_signals[LOCK_ALPHA_CHANGED] =
+ g_signal_new ("lock-alpha-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpLayerClass, lock_alpha_changed),
+ NULL, NULL,
+ gimp_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ layer_signals[MASK_CHANGED] =
+ g_signal_new ("mask-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpLayerClass, mask_changed),
+ NULL, NULL,
+ gimp_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ layer_signals[APPLY_MASK_CHANGED] =
+ g_signal_new ("apply-mask-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpLayerClass, apply_mask_changed),
+ NULL, NULL,
+ gimp_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ layer_signals[EDIT_MASK_CHANGED] =
+ g_signal_new ("edit-mask-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpLayerClass, edit_mask_changed),
+ NULL, NULL,
+ gimp_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ layer_signals[SHOW_MASK_CHANGED] =
+ g_signal_new ("show-mask-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpLayerClass, show_mask_changed),
+ NULL, NULL,
+ gimp_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ object_class->set_property = gimp_layer_set_property;
+ object_class->get_property = gimp_layer_get_property;
+ object_class->dispose = gimp_layer_dispose;
+ object_class->finalize = gimp_layer_finalize;
+ object_class->notify = gimp_layer_notify;
+
+ gimp_object_class->name_changed = gimp_layer_name_changed;
+ gimp_object_class->get_memsize = gimp_layer_get_memsize;
+
+ viewable_class->default_icon_name = "gimp-layer";
+ viewable_class->invalidate_preview = gimp_layer_invalidate_preview;
+ viewable_class->get_description = gimp_layer_get_description;
+
+ filter_class->get_node = gimp_layer_get_node;
+
+ item_class->removed = gimp_layer_removed;
+ item_class->unset_removed = gimp_layer_unset_removed;
+ item_class->is_attached = gimp_layer_is_attached;
+ item_class->get_tree = gimp_layer_get_tree;
+ item_class->duplicate = gimp_layer_duplicate;
+ item_class->convert = gimp_layer_convert;
+ item_class->rename = gimp_layer_rename;
+ item_class->start_move = gimp_layer_start_move;
+ item_class->end_move = gimp_layer_end_move;
+ item_class->translate = gimp_layer_translate;
+ item_class->scale = gimp_layer_scale;
+ item_class->resize = gimp_layer_resize;
+ item_class->flip = gimp_layer_flip;
+ item_class->rotate = gimp_layer_rotate;
+ item_class->transform = gimp_layer_transform;
+ item_class->to_selection = gimp_layer_to_selection;
+ item_class->default_name = _("Layer");
+ item_class->rename_desc = C_("undo-type", "Rename Layer");
+ item_class->translate_desc = C_("undo-type", "Move Layer");
+ item_class->scale_desc = C_("undo-type", "Scale Layer");
+ item_class->resize_desc = C_("undo-type", "Resize Layer");
+ item_class->flip_desc = C_("undo-type", "Flip Layer");
+ item_class->rotate_desc = C_("undo-type", "Rotate Layer");
+ item_class->transform_desc = C_("undo-type", "Transform Layer");
+ item_class->to_selection_desc = C_("undo-type", "Alpha to Selection");
+ item_class->reorder_desc = C_("undo-type", "Reorder Layer");
+ item_class->raise_desc = C_("undo-type", "Raise Layer");
+ item_class->raise_to_top_desc = C_("undo-type", "Raise Layer to Top");
+ item_class->lower_desc = C_("undo-type", "Lower Layer");
+ item_class->lower_to_bottom_desc = C_("undo-type", "Lower Layer to Bottom");
+ item_class->raise_failed = _("Layer cannot be raised higher.");
+ item_class->lower_failed = _("Layer cannot be lowered more.");
+
+ drawable_class->alpha_changed = gimp_layer_alpha_changed;
+ drawable_class->estimate_memsize = gimp_layer_estimate_memsize;
+ drawable_class->supports_alpha = gimp_layer_supports_alpha;
+ drawable_class->convert_type = gimp_layer_convert_type;
+ drawable_class->invalidate_boundary = gimp_layer_invalidate_boundary;
+ drawable_class->get_active_components = gimp_layer_get_active_components;
+ drawable_class->get_active_mask = gimp_layer_get_active_mask;
+ drawable_class->set_buffer = gimp_layer_set_buffer;
+ drawable_class->get_bounding_box = gimp_layer_get_bounding_box;
+
+ klass->opacity_changed = NULL;
+ klass->mode_changed = NULL;
+ klass->blend_space_changed = NULL;
+ klass->composite_space_changed = NULL;
+ klass->composite_mode_changed = NULL;
+ klass->excludes_backdrop_changed = NULL;
+ klass->lock_alpha_changed = NULL;
+ klass->mask_changed = NULL;
+ klass->apply_mask_changed = NULL;
+ klass->edit_mask_changed = NULL;
+ klass->show_mask_changed = NULL;
+ klass->translate = gimp_layer_real_translate;
+ klass->scale = gimp_layer_real_scale;
+ klass->resize = gimp_layer_real_resize;
+ klass->flip = gimp_layer_real_flip;
+ klass->rotate = gimp_layer_real_rotate;
+ klass->transform = gimp_layer_real_transform;
+ klass->convert_type = gimp_layer_real_convert_type;
+ klass->get_bounding_box = gimp_layer_real_get_bounding_box;
+ klass->get_effective_mode = gimp_layer_real_get_effective_mode;
+ klass->get_excludes_backdrop = gimp_layer_real_get_excludes_backdrop;
+
+ g_object_class_install_property (object_class, PROP_OPACITY,
+ g_param_spec_double ("opacity", NULL, NULL,
+ GIMP_OPACITY_TRANSPARENT,
+ GIMP_OPACITY_OPAQUE,
+ GIMP_OPACITY_OPAQUE,
+ GIMP_PARAM_READABLE));
+
+ g_object_class_install_property (object_class, PROP_MODE,
+ g_param_spec_enum ("mode", NULL, NULL,
+ GIMP_TYPE_LAYER_MODE,
+ GIMP_LAYER_MODE_NORMAL,
+ GIMP_PARAM_READABLE));
+
+ g_object_class_install_property (object_class, PROP_BLEND_SPACE,
+ g_param_spec_enum ("blend-space",
+ NULL, NULL,
+ GIMP_TYPE_LAYER_COLOR_SPACE,
+ GIMP_LAYER_COLOR_SPACE_AUTO,
+ GIMP_PARAM_READABLE));
+
+ g_object_class_install_property (object_class, PROP_COMPOSITE_SPACE,
+ g_param_spec_enum ("composite-space",
+ NULL, NULL,
+ GIMP_TYPE_LAYER_COLOR_SPACE,
+ GIMP_LAYER_COLOR_SPACE_AUTO,
+ GIMP_PARAM_READABLE));
+
+ g_object_class_install_property (object_class, PROP_COMPOSITE_MODE,
+ g_param_spec_enum ("composite-mode",
+ NULL, NULL,
+ GIMP_TYPE_LAYER_COMPOSITE_MODE,
+ GIMP_LAYER_COMPOSITE_AUTO,
+ GIMP_PARAM_READABLE));
+
+ g_object_class_install_property (object_class, PROP_EXCLUDES_BACKDROP,
+ g_param_spec_boolean ("excludes-backdrop",
+ NULL, NULL,
+ FALSE,
+ GIMP_PARAM_READABLE));
+
+ g_object_class_install_property (object_class, PROP_LOCK_ALPHA,
+ g_param_spec_boolean ("lock-alpha",
+ NULL, NULL,
+ FALSE,
+ GIMP_PARAM_READABLE));
+
+ g_object_class_install_property (object_class, PROP_MASK,
+ g_param_spec_object ("mask",
+ NULL, NULL,
+ GIMP_TYPE_LAYER_MASK,
+ GIMP_PARAM_READABLE));
+
+ g_object_class_install_property (object_class, PROP_FLOATING_SELECTION,
+ g_param_spec_boolean ("floating-selection",
+ NULL, NULL,
+ FALSE,
+ GIMP_PARAM_READABLE));
+}
+
+static void
+gimp_layer_init (GimpLayer *layer)
+{
+ layer->opacity = GIMP_OPACITY_OPAQUE;
+ layer->mode = GIMP_LAYER_MODE_NORMAL;
+ layer->blend_space = GIMP_LAYER_COLOR_SPACE_AUTO;
+ layer->composite_space = GIMP_LAYER_COLOR_SPACE_AUTO;
+ layer->composite_mode = GIMP_LAYER_COMPOSITE_AUTO;
+ layer->effective_mode = layer->mode;
+ layer->effective_blend_space = gimp_layer_get_real_blend_space (layer);
+ layer->effective_composite_space = gimp_layer_get_real_composite_space (layer);
+ layer->effective_composite_mode = gimp_layer_get_real_composite_mode (layer);
+ layer->excludes_backdrop = FALSE;
+ layer->lock_alpha = FALSE;
+
+ layer->mask = NULL;
+ layer->apply_mask = TRUE;
+ layer->edit_mask = TRUE;
+ layer->show_mask = FALSE;
+
+ /* floating selection */
+ layer->fs.drawable = NULL;
+ layer->fs.boundary_known = FALSE;
+ layer->fs.segs = NULL;
+ layer->fs.num_segs = 0;
+}
+
+static void
+gimp_color_managed_iface_init (GimpColorManagedInterface *iface)
+{
+ iface->get_color_profile = gimp_layer_get_color_profile;
+}
+
+static void
+gimp_pickable_iface_init (GimpPickableInterface *iface)
+{
+ iface->get_opacity_at = gimp_layer_get_opacity_at;
+ iface->pixel_to_srgb = gimp_layer_pixel_to_srgb;
+ iface->srgb_to_pixel = gimp_layer_srgb_to_pixel;
+}
+
+static void
+gimp_layer_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id)
+ {
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_layer_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpLayer *layer = GIMP_LAYER (object);
+
+ switch (property_id)
+ {
+ case PROP_OPACITY:
+ g_value_set_double (value, gimp_layer_get_opacity (layer));
+ break;
+ case PROP_MODE:
+ g_value_set_enum (value, gimp_layer_get_mode (layer));
+ break;
+ case PROP_BLEND_SPACE:
+ g_value_set_enum (value, gimp_layer_get_blend_space (layer));
+ break;
+ case PROP_COMPOSITE_SPACE:
+ g_value_set_enum (value, gimp_layer_get_composite_space (layer));
+ break;
+ case PROP_COMPOSITE_MODE:
+ g_value_set_enum (value, gimp_layer_get_composite_mode (layer));
+ break;
+ case PROP_EXCLUDES_BACKDROP:
+ g_value_set_boolean (value, gimp_layer_get_excludes_backdrop (layer));
+ break;
+ case PROP_LOCK_ALPHA:
+ g_value_set_boolean (value, gimp_layer_get_lock_alpha (layer));
+ break;
+ case PROP_MASK:
+ g_value_set_object (value, gimp_layer_get_mask (layer));
+ break;
+ case PROP_FLOATING_SELECTION:
+ g_value_set_boolean (value, gimp_layer_is_floating_sel (layer));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_layer_dispose (GObject *object)
+{
+ GimpLayer *layer = GIMP_LAYER (object);
+
+ if (layer->mask)
+ g_signal_handlers_disconnect_by_func (layer->mask,
+ gimp_layer_layer_mask_update,
+ layer);
+
+ if (gimp_layer_is_floating_sel (layer))
+ {
+ GimpDrawable *fs_drawable = gimp_layer_get_floating_sel_drawable (layer);
+
+ /* only detach if this is actually the drawable's fs because the
+ * layer might be on the undo stack and not attached to anything
+ */
+ if (gimp_drawable_get_floating_sel (fs_drawable) == layer)
+ gimp_drawable_detach_floating_sel (fs_drawable);
+
+ gimp_layer_set_floating_sel_drawable (layer, NULL);
+ }
+
+ G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+gimp_layer_finalize (GObject *object)
+{
+ GimpLayer *layer = GIMP_LAYER (object);
+
+ g_clear_object (&layer->mask);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gimp_layer_update_mode_node (GimpLayer *layer)
+{
+ GeglNode *mode_node;
+ GimpLayerMode visible_mode;
+ GimpLayerColorSpace visible_blend_space;
+ GimpLayerColorSpace visible_composite_space;
+ GimpLayerCompositeMode visible_composite_mode;
+
+ mode_node = gimp_drawable_get_mode_node (GIMP_DRAWABLE (layer));
+
+ if (layer->mask && layer->show_mask)
+ {
+ visible_mode = GIMP_LAYER_MODE_NORMAL;
+ visible_blend_space = GIMP_LAYER_COLOR_SPACE_AUTO;
+ visible_composite_space = GIMP_LAYER_COLOR_SPACE_AUTO;
+ visible_composite_mode = GIMP_LAYER_COMPOSITE_AUTO;
+
+ /* This makes sure that masks of LEGACY-mode layers are
+ * composited in PERCEPTUAL space, and non-LEGACY layers in
+ * LINEAR space, or whatever composite space was chosen in the
+ * layer attributes dialog
+ */
+ visible_composite_space = gimp_layer_get_real_composite_space (layer);
+ }
+ else
+ {
+ visible_mode = layer->effective_mode;
+ visible_blend_space = layer->effective_blend_space;
+ visible_composite_space = layer->effective_composite_space;
+ visible_composite_mode = layer->effective_composite_mode;
+ }
+
+ gimp_gegl_mode_node_set_mode (mode_node,
+ visible_mode,
+ visible_blend_space,
+ visible_composite_space,
+ visible_composite_mode);
+ gimp_gegl_mode_node_set_opacity (mode_node, layer->opacity);
+}
+
+static void
+gimp_layer_notify (GObject *object,
+ GParamSpec *pspec)
+{
+ if (! strcmp (pspec->name, "is-last-node") &&
+ gimp_filter_peek_node (GIMP_FILTER (object)))
+ {
+ gimp_layer_update_mode_node (GIMP_LAYER (object));
+
+ gimp_drawable_update (GIMP_DRAWABLE (object), 0, 0, -1, -1);
+ }
+}
+
+static void
+gimp_layer_name_changed (GimpObject *object)
+{
+ GimpLayer *layer = GIMP_LAYER (object);
+
+ if (GIMP_OBJECT_CLASS (parent_class)->name_changed)
+ GIMP_OBJECT_CLASS (parent_class)->name_changed (object);
+
+ if (layer->mask)
+ {
+ gchar *mask_name = g_strdup_printf (_("%s mask"),
+ gimp_object_get_name (object));
+
+ gimp_object_take_name (GIMP_OBJECT (layer->mask), mask_name);
+ }
+}
+
+static gint64
+gimp_layer_get_memsize (GimpObject *object,
+ gint64 *gui_size)
+{
+ GimpLayer *layer = GIMP_LAYER (object);
+ gint64 memsize = 0;
+
+ memsize += gimp_object_get_memsize (GIMP_OBJECT (layer->mask), gui_size);
+
+ *gui_size += layer->fs.num_segs * sizeof (GimpBoundSeg);
+
+ return memsize + GIMP_OBJECT_CLASS (parent_class)->get_memsize (object,
+ gui_size);
+}
+
+static void
+gimp_layer_invalidate_preview (GimpViewable *viewable)
+{
+ GimpLayer *layer = GIMP_LAYER (viewable);
+
+ GIMP_VIEWABLE_CLASS (parent_class)->invalidate_preview (viewable);
+
+ if (gimp_layer_is_floating_sel (layer))
+ floating_sel_invalidate (layer);
+}
+
+static gchar *
+gimp_layer_get_description (GimpViewable *viewable,
+ gchar **tooltip)
+{
+ if (gimp_layer_is_floating_sel (GIMP_LAYER (viewable)))
+ {
+ return g_strdup_printf (_("Floating Selection\n(%s)"),
+ gimp_object_get_name (viewable));
+ }
+
+ return GIMP_VIEWABLE_CLASS (parent_class)->get_description (viewable,
+ tooltip);
+}
+
+static GeglNode *
+gimp_layer_get_node (GimpFilter *filter)
+{
+ GimpDrawable *drawable = GIMP_DRAWABLE (filter);
+ GimpLayer *layer = GIMP_LAYER (filter);
+ GeglNode *node;
+ GeglNode *input;
+ GeglNode *source;
+ GeglNode *mode_node;
+ gboolean source_node_hijacked = FALSE;
+
+ node = GIMP_FILTER_CLASS (parent_class)->get_node (filter);
+
+ input = gegl_node_get_input_proxy (node, "input");
+
+ source = gimp_drawable_get_source_node (drawable);
+
+ /* if the source node already has a parent, we are a floating
+ * selection and the source node has been hijacked by the fs'
+ * drawable
+ */
+ if (gegl_node_get_parent (source))
+ source_node_hijacked = TRUE;
+
+ if (! source_node_hijacked)
+ gegl_node_add_child (node, source);
+
+ gegl_node_connect_to (input, "output",
+ source, "input");
+
+ g_warn_if_fail (layer->layer_offset_node == NULL);
+ g_warn_if_fail (layer->mask_offset_node == NULL);
+
+ /* the mode node connects it all, and has aux and aux2 inputs for
+ * the layer and its mask
+ */
+ mode_node = gimp_drawable_get_mode_node (drawable);
+ gimp_layer_update_mode_node (layer);
+
+ /* the layer's offset node */
+ layer->layer_offset_node = gegl_node_new_child (node,
+ "operation", "gegl:translate",
+ NULL);
+ gimp_item_add_offset_node (GIMP_ITEM (layer), layer->layer_offset_node);
+
+ /* the layer mask's offset node */
+ layer->mask_offset_node = gegl_node_new_child (node,
+ "operation", "gegl:translate",
+ NULL);
+ gimp_item_add_offset_node (GIMP_ITEM (layer), layer->mask_offset_node);
+
+ if (! source_node_hijacked)
+ {
+ gegl_node_connect_to (source, "output",
+ layer->layer_offset_node, "input");
+ }
+
+ if (! (layer->mask && gimp_layer_get_show_mask (layer)))
+ {
+ gegl_node_connect_to (layer->layer_offset_node, "output",
+ mode_node, "aux");
+ }
+
+ if (layer->mask)
+ {
+ GeglNode *mask;
+
+ mask = gimp_drawable_get_source_node (GIMP_DRAWABLE (layer->mask));
+
+ gegl_node_connect_to (mask, "output",
+ layer->mask_offset_node, "input");
+
+ if (gimp_layer_get_show_mask (layer))
+ {
+ gegl_node_connect_to (layer->mask_offset_node, "output",
+ mode_node, "aux");
+ }
+ else if (gimp_layer_get_apply_mask (layer))
+ {
+ gegl_node_connect_to (layer->mask_offset_node, "output",
+ mode_node, "aux2");
+ }
+ }
+
+ return node;
+}
+
+static void
+gimp_layer_removed (GimpItem *item)
+{
+ GimpLayer *layer = GIMP_LAYER (item);
+
+ if (layer->mask)
+ gimp_item_removed (GIMP_ITEM (layer->mask));
+
+ if (GIMP_ITEM_CLASS (parent_class)->removed)
+ GIMP_ITEM_CLASS (parent_class)->removed (item);
+}
+
+static void
+gimp_layer_unset_removed (GimpItem *item)
+{
+ GimpLayer *layer = GIMP_LAYER (item);
+
+ if (layer->mask)
+ gimp_item_unset_removed (GIMP_ITEM (layer->mask));
+
+ if (GIMP_ITEM_CLASS (parent_class)->unset_removed)
+ GIMP_ITEM_CLASS (parent_class)->unset_removed (item);
+}
+
+static gboolean
+gimp_layer_is_attached (GimpItem *item)
+{
+ GimpImage *image = gimp_item_get_image (item);
+
+ return (GIMP_IS_IMAGE (image) &&
+ gimp_container_have (gimp_image_get_layers (image),
+ GIMP_OBJECT (item)));
+}
+
+static GimpItemTree *
+gimp_layer_get_tree (GimpItem *item)
+{
+ if (gimp_item_is_attached (item))
+ {
+ GimpImage *image = gimp_item_get_image (item);
+
+ return gimp_image_get_layer_tree (image);
+ }
+
+ return NULL;
+}
+
+static GimpItem *
+gimp_layer_duplicate (GimpItem *item,
+ GType new_type)
+{
+ GimpItem *new_item;
+
+ g_return_val_if_fail (g_type_is_a (new_type, GIMP_TYPE_DRAWABLE), NULL);
+
+ new_item = GIMP_ITEM_CLASS (parent_class)->duplicate (item, new_type);
+
+ if (GIMP_IS_LAYER (new_item))
+ {
+ GimpLayer *layer = GIMP_LAYER (item);
+ GimpLayer *new_layer = GIMP_LAYER (new_item);
+
+ gimp_layer_set_mode (new_layer,
+ gimp_layer_get_mode (layer), FALSE);
+ gimp_layer_set_blend_space (new_layer,
+ gimp_layer_get_blend_space (layer), FALSE);
+ gimp_layer_set_composite_space (new_layer,
+ gimp_layer_get_composite_space (layer), FALSE);
+ gimp_layer_set_composite_mode (new_layer,
+ gimp_layer_get_composite_mode (layer), FALSE);
+ gimp_layer_set_opacity (new_layer,
+ gimp_layer_get_opacity (layer), FALSE);
+
+ if (gimp_layer_can_lock_alpha (new_layer))
+ gimp_layer_set_lock_alpha (new_layer,
+ gimp_layer_get_lock_alpha (layer), FALSE);
+
+ /* duplicate the layer mask if necessary */
+ if (layer->mask)
+ {
+ GimpItem *mask;
+
+ mask = gimp_item_duplicate (GIMP_ITEM (layer->mask),
+ G_TYPE_FROM_INSTANCE (layer->mask));
+ gimp_layer_add_mask (new_layer, GIMP_LAYER_MASK (mask), FALSE, NULL);
+
+ new_layer->apply_mask = layer->apply_mask;
+ new_layer->edit_mask = layer->edit_mask;
+ new_layer->show_mask = layer->show_mask;
+ }
+ }
+
+ return new_item;
+}
+
+static void
+gimp_layer_convert (GimpItem *item,
+ GimpImage *dest_image,
+ GType old_type)
+{
+ GimpLayer *layer = GIMP_LAYER (item);
+ GimpDrawable *drawable = GIMP_DRAWABLE (item);
+ GimpImageBaseType old_base_type;
+ GimpImageBaseType new_base_type;
+ GimpPrecision old_precision;
+ GimpPrecision new_precision;
+ GimpColorProfile *dest_profile = NULL;
+
+ old_base_type = gimp_drawable_get_base_type (drawable);
+ new_base_type = gimp_image_get_base_type (dest_image);
+
+ old_precision = gimp_drawable_get_precision (drawable);
+ new_precision = gimp_image_get_precision (dest_image);
+
+ if (g_type_is_a (old_type, GIMP_TYPE_LAYER) &&
+ gimp_image_get_is_color_managed (dest_image))
+ {
+ GimpColorProfile *src_profile =
+ gimp_color_managed_get_color_profile (GIMP_COLOR_MANAGED (item));
+
+ dest_profile =
+ gimp_color_managed_get_color_profile (GIMP_COLOR_MANAGED (dest_image));
+
+ if (gimp_color_profile_is_equal (dest_profile, src_profile))
+ dest_profile = NULL;
+ }
+
+ if (old_base_type != new_base_type ||
+ old_precision != new_precision ||
+ dest_profile)
+ {
+ gimp_drawable_convert_type (drawable, dest_image,
+ new_base_type,
+ new_precision,
+ gimp_drawable_has_alpha (drawable),
+ dest_profile,
+ GEGL_DITHER_NONE, GEGL_DITHER_NONE,
+ FALSE, NULL);
+ }
+
+ if (layer->mask)
+ gimp_item_set_image (GIMP_ITEM (layer->mask), dest_image);
+
+ GIMP_ITEM_CLASS (parent_class)->convert (item, dest_image, old_type);
+}
+
+static gboolean
+gimp_layer_rename (GimpItem *item,
+ const gchar *new_name,
+ const gchar *undo_desc,
+ GError **error)
+{
+ GimpLayer *layer = GIMP_LAYER (item);
+ GimpImage *image = gimp_item_get_image (item);
+ gboolean attached;
+ gboolean floating_sel;
+
+ attached = gimp_item_is_attached (item);
+ floating_sel = gimp_layer_is_floating_sel (layer);
+
+ if (floating_sel)
+ {
+ if (GIMP_IS_CHANNEL (gimp_layer_get_floating_sel_drawable (layer)))
+ {
+ g_set_error_literal (error, GIMP_ERROR, GIMP_FAILED,
+ _("Cannot create a new layer from the floating "
+ "selection because it belongs to a layer mask "
+ "or channel."));
+ return FALSE;
+ }
+
+ if (attached)
+ {
+ gimp_image_undo_group_start (image,
+ GIMP_UNDO_GROUP_ITEM_PROPERTIES,
+ undo_desc);
+
+ floating_sel_to_layer (layer, NULL);
+ }
+ }
+
+ GIMP_ITEM_CLASS (parent_class)->rename (item, new_name, undo_desc, error);
+
+ if (attached && floating_sel)
+ gimp_image_undo_group_end (image);
+
+ return TRUE;
+}
+
+static void
+gimp_layer_start_move (GimpItem *item,
+ gboolean push_undo)
+{
+ GimpLayer *layer = GIMP_LAYER (item);
+ GimpLayer *ancestor = layer;
+ GSList *ancestors = NULL;
+
+ /* suspend mask cropping for all of the layer's ancestors */
+ while ((ancestor = gimp_layer_get_parent (ancestor)))
+ {
+ gimp_group_layer_suspend_mask (GIMP_GROUP_LAYER (ancestor), push_undo);
+
+ ancestors = g_slist_prepend (ancestors, g_object_ref (ancestor));
+ }
+
+ /* we keep the ancestor list around, so that we can resume mask cropping for
+ * the same set of groups in gimp_layer_end_move(). note that
+ * gimp_image_remove_layer() calls start_move() before removing the layer,
+ * while it's still part of the layer tree, and end_move() afterwards, when
+ * it's no longer part of the layer tree, and hence we can't use get_parent()
+ * in end_move() to get the same set of ancestors.
+ */
+ layer->move_stack = g_slist_prepend (layer->move_stack, ancestors);
+
+ if (GIMP_ITEM_CLASS (parent_class)->start_move)
+ GIMP_ITEM_CLASS (parent_class)->start_move (item, push_undo);
+}
+
+static void
+gimp_layer_end_move (GimpItem *item,
+ gboolean push_undo)
+{
+ GimpLayer *layer = GIMP_LAYER (item);
+ GSList *ancestors;
+ GSList *iter;
+
+ g_return_if_fail (layer->move_stack != NULL);
+
+ if (GIMP_ITEM_CLASS (parent_class)->end_move)
+ GIMP_ITEM_CLASS (parent_class)->end_move (item, push_undo);
+
+ ancestors = layer->move_stack->data;
+
+ layer->move_stack = g_slist_remove (layer->move_stack, ancestors);
+
+ /* resume mask cropping for all of the layer's ancestors */
+ for (iter = ancestors; iter; iter = g_slist_next (iter))
+ {
+ GimpGroupLayer *ancestor = iter->data;
+
+ gimp_group_layer_resume_mask (ancestor, push_undo);
+
+ g_object_unref (ancestor);
+ }
+
+ g_slist_free (ancestors);
+}
+
+static void
+gimp_layer_translate (GimpItem *item,
+ gdouble offset_x,
+ gdouble offset_y,
+ gboolean push_undo)
+{
+ GimpLayer *layer = GIMP_LAYER (item);
+
+ if (push_undo)
+ gimp_image_undo_push_item_displace (gimp_item_get_image (item), NULL, item);
+
+ GIMP_LAYER_GET_CLASS (layer)->translate (layer,
+ SIGNED_ROUND (offset_x),
+ SIGNED_ROUND (offset_y));
+
+ if (layer->mask)
+ {
+ gint off_x, off_y;
+
+ gimp_item_get_offset (item, &off_x, &off_y);
+ gimp_item_set_offset (GIMP_ITEM (layer->mask), off_x, off_y);
+
+ gimp_viewable_invalidate_preview (GIMP_VIEWABLE (layer->mask));
+ }
+}
+
+static void
+gimp_layer_scale (GimpItem *item,
+ gint new_width,
+ gint new_height,
+ gint new_offset_x,
+ gint new_offset_y,
+ GimpInterpolationType interpolation_type,
+ GimpProgress *progress)
+{
+ GimpLayer *layer = GIMP_LAYER (item);
+ GimpObjectQueue *queue = NULL;
+
+ if (progress && layer->mask)
+ {
+ GimpLayerMask *mask;
+
+ queue = gimp_object_queue_new (progress);
+ progress = GIMP_PROGRESS (queue);
+
+ /* temporarily set layer->mask to NULL, so that its size won't be counted
+ * when pushing the layer to the queue.
+ */
+ mask = layer->mask;
+ layer->mask = NULL;
+
+ gimp_object_queue_push (queue, layer);
+ gimp_object_queue_push (queue, mask);
+
+ layer->mask = mask;
+ }
+
+ if (queue)
+ gimp_object_queue_pop (queue);
+
+ GIMP_LAYER_GET_CLASS (layer)->scale (layer, new_width, new_height,
+ new_offset_x, new_offset_y,
+ interpolation_type, progress);
+
+ if (layer->mask)
+ {
+ if (queue)
+ gimp_object_queue_pop (queue);
+
+ gimp_item_scale (GIMP_ITEM (layer->mask),
+ new_width, new_height,
+ new_offset_x, new_offset_y,
+ interpolation_type, progress);
+ }
+
+ g_clear_object (&queue);
+}
+
+static void
+gimp_layer_resize (GimpItem *item,
+ GimpContext *context,
+ GimpFillType fill_type,
+ gint new_width,
+ gint new_height,
+ gint offset_x,
+ gint offset_y)
+{
+ GimpLayer *layer = GIMP_LAYER (item);
+
+ GIMP_LAYER_GET_CLASS (layer)->resize (layer, context, fill_type,
+ new_width, new_height,
+ offset_x, offset_y);
+
+ if (layer->mask)
+ gimp_item_resize (GIMP_ITEM (layer->mask), context, GIMP_FILL_TRANSPARENT,
+ new_width, new_height, offset_x, offset_y);
+}
+
+static void
+gimp_layer_flip (GimpItem *item,
+ GimpContext *context,
+ GimpOrientationType flip_type,
+ gdouble axis,
+ gboolean clip_result)
+{
+ GimpLayer *layer = GIMP_LAYER (item);
+
+ GIMP_LAYER_GET_CLASS (layer)->flip (layer, context, flip_type, axis,
+ clip_result);
+
+ if (layer->mask)
+ gimp_item_flip (GIMP_ITEM (layer->mask), context,
+ flip_type, axis, clip_result);
+}
+
+static void
+gimp_layer_rotate (GimpItem *item,
+ GimpContext *context,
+ GimpRotationType rotate_type,
+ gdouble center_x,
+ gdouble center_y,
+ gboolean clip_result)
+{
+ GimpLayer *layer = GIMP_LAYER (item);
+
+ GIMP_LAYER_GET_CLASS (layer)->rotate (layer, context,
+ rotate_type, center_x, center_y,
+ clip_result);
+
+ if (layer->mask)
+ gimp_item_rotate (GIMP_ITEM (layer->mask), context,
+ rotate_type, center_x, center_y, clip_result);
+}
+
+static void
+gimp_layer_transform (GimpItem *item,
+ GimpContext *context,
+ const GimpMatrix3 *matrix,
+ GimpTransformDirection direction,
+ GimpInterpolationType interpolation_type,
+ GimpTransformResize clip_result,
+ GimpProgress *progress)
+{
+ GimpLayer *layer = GIMP_LAYER (item);
+ GimpObjectQueue *queue = NULL;
+
+ if (progress && layer->mask)
+ {
+ GimpLayerMask *mask;
+
+ queue = gimp_object_queue_new (progress);
+ progress = GIMP_PROGRESS (queue);
+
+ /* temporarily set layer->mask to NULL, so that its size won't be counted
+ * when pushing the layer to the queue.
+ */
+ mask = layer->mask;
+ layer->mask = NULL;
+
+ gimp_object_queue_push (queue, layer);
+ gimp_object_queue_push (queue, mask);
+
+ layer->mask = mask;
+ }
+
+ if (queue)
+ gimp_object_queue_pop (queue);
+
+ GIMP_LAYER_GET_CLASS (layer)->transform (layer, context, matrix, direction,
+ interpolation_type,
+ clip_result,
+ progress);
+
+ if (layer->mask)
+ {
+ if (queue)
+ gimp_object_queue_pop (queue);
+
+ gimp_item_transform (GIMP_ITEM (layer->mask), context,
+ matrix, direction,
+ interpolation_type,
+ clip_result, progress);
+ }
+
+ g_clear_object (&queue);
+}
+
+static void
+gimp_layer_to_selection (GimpItem *item,
+ GimpChannelOps op,
+ gboolean antialias,
+ gboolean feather,
+ gdouble feather_radius_x,
+ gdouble feather_radius_y)
+{
+ GimpLayer *layer = GIMP_LAYER (item);
+ GimpImage *image = gimp_item_get_image (item);
+
+ gimp_channel_select_alpha (gimp_image_get_mask (image),
+ GIMP_DRAWABLE (layer),
+ op,
+ feather, feather_radius_x, feather_radius_y);
+}
+
+static void
+gimp_layer_alpha_changed (GimpDrawable *drawable)
+{
+ if (GIMP_DRAWABLE_CLASS (parent_class)->alpha_changed)
+ GIMP_DRAWABLE_CLASS (parent_class)->alpha_changed (drawable);
+
+ /* When we add/remove alpha, whatever cached color transforms in
+ * view renderers need to be recreated because they cache the wrong
+ * lcms formats. See bug 478528.
+ */
+ gimp_color_managed_profile_changed (GIMP_COLOR_MANAGED (drawable));
+}
+
+static gint64
+gimp_layer_estimate_memsize (GimpDrawable *drawable,
+ GimpComponentType component_type,
+ gint width,
+ gint height)
+{
+ GimpLayer *layer = GIMP_LAYER (drawable);
+ gint64 memsize = 0;
+
+ if (layer->mask)
+ memsize += gimp_drawable_estimate_memsize (GIMP_DRAWABLE (layer->mask),
+ component_type,
+ width, height);
+
+ return memsize +
+ GIMP_DRAWABLE_CLASS (parent_class)->estimate_memsize (drawable,
+ component_type,
+ width, height);
+}
+
+static gboolean
+gimp_layer_supports_alpha (GimpDrawable *drawable)
+{
+ return TRUE;
+}
+
+static void
+gimp_layer_convert_type (GimpDrawable *drawable,
+ GimpImage *dest_image,
+ const Babl *new_format,
+ GimpColorProfile *dest_profile,
+ GeglDitherMethod layer_dither_type,
+ GeglDitherMethod mask_dither_type,
+ gboolean push_undo,
+ GimpProgress *progress)
+{
+ GimpLayer *layer = GIMP_LAYER (drawable);
+ GimpObjectQueue *queue = NULL;
+ gboolean convert_mask;
+
+ convert_mask = layer->mask &&
+ gimp_babl_format_get_precision (new_format) !=
+ gimp_drawable_get_precision (GIMP_DRAWABLE (layer->mask));
+
+ if (progress && convert_mask)
+ {
+ GimpLayerMask *mask;
+
+ queue = gimp_object_queue_new (progress);
+ progress = GIMP_PROGRESS (queue);
+
+ /* temporarily set layer->mask to NULL, so that its size won't be counted
+ * when pushing the layer to the queue.
+ */
+ mask = layer->mask;
+ layer->mask = NULL;
+
+ gimp_object_queue_push (queue, layer);
+ gimp_object_queue_push (queue, mask);
+
+ layer->mask = mask;
+ }
+
+ if (queue)
+ gimp_object_queue_pop (queue);
+
+ GIMP_LAYER_GET_CLASS (layer)->convert_type (layer, dest_image, new_format,
+ dest_profile, layer_dither_type,
+ mask_dither_type, push_undo,
+ progress);
+
+ if (convert_mask)
+ {
+ if (queue)
+ gimp_object_queue_pop (queue);
+
+ gimp_drawable_convert_type (GIMP_DRAWABLE (layer->mask), dest_image,
+ GIMP_GRAY,
+ gimp_babl_format_get_precision (new_format),
+ gimp_drawable_has_alpha (GIMP_DRAWABLE (layer->mask)),
+ NULL,
+ layer_dither_type, mask_dither_type,
+ push_undo, progress);
+ }
+
+ g_clear_object (&queue);
+}
+
+static void
+gimp_layer_invalidate_boundary (GimpDrawable *drawable)
+{
+ GimpLayer *layer = GIMP_LAYER (drawable);
+
+ if (gimp_item_is_attached (GIMP_ITEM (drawable)) &&
+ gimp_item_is_visible (GIMP_ITEM (drawable)))
+ {
+ GimpImage *image = gimp_item_get_image (GIMP_ITEM (drawable));
+ GimpChannel *mask = gimp_image_get_mask (image);
+
+ /* Turn the current selection off */
+ gimp_image_selection_invalidate (image);
+
+ /* Only bother with the bounds if there is a selection */
+ if (! gimp_channel_is_empty (mask))
+ {
+ mask->bounds_known = FALSE;
+ mask->boundary_known = FALSE;
+ }
+ }
+
+ if (gimp_layer_is_floating_sel (layer))
+ floating_sel_invalidate (layer);
+}
+
+static void
+gimp_layer_get_active_components (GimpDrawable *drawable,
+ gboolean *active)
+{
+ GimpLayer *layer = GIMP_LAYER (drawable);
+ GimpImage *image = gimp_item_get_image (GIMP_ITEM (drawable));
+ const Babl *format = gimp_drawable_get_format (drawable);
+
+ /* first copy the image active channels */
+ gimp_image_get_active_array (image, active);
+
+ if (gimp_drawable_has_alpha (drawable) && layer->lock_alpha)
+ active[babl_format_get_n_components (format) - 1] = FALSE;
+}
+
+static GimpComponentMask
+gimp_layer_get_active_mask (GimpDrawable *drawable)
+{
+ GimpLayer *layer = GIMP_LAYER (drawable);
+ GimpImage *image = gimp_item_get_image (GIMP_ITEM (drawable));
+ GimpComponentMask mask = gimp_image_get_active_mask (image);
+
+ if (gimp_drawable_has_alpha (drawable) && layer->lock_alpha)
+ mask &= ~GIMP_COMPONENT_MASK_ALPHA;
+
+ return mask;
+}
+
+static void
+gimp_layer_set_buffer (GimpDrawable *drawable,
+ gboolean push_undo,
+ const gchar *undo_desc,
+ GeglBuffer *buffer,
+ const GeglRectangle *bounds)
+{
+ GeglBuffer *old_buffer = gimp_drawable_get_buffer (drawable);
+ gint old_linear = -1;
+
+ if (old_buffer)
+ old_linear = gimp_drawable_get_linear (drawable);
+
+ GIMP_DRAWABLE_CLASS (parent_class)->set_buffer (drawable,
+ push_undo, undo_desc,
+ buffer, bounds);
+
+ if (gimp_filter_peek_node (GIMP_FILTER (drawable)))
+ {
+ if (gimp_drawable_get_linear (drawable) != old_linear)
+ gimp_layer_update_mode_node (GIMP_LAYER (drawable));
+ }
+}
+
+static GeglRectangle
+gimp_layer_get_bounding_box (GimpDrawable *drawable)
+{
+ GimpLayer *layer = GIMP_LAYER (drawable);
+ GimpLayerMask *mask = gimp_layer_get_mask (layer);
+ GeglRectangle bounding_box;
+
+ if (mask && gimp_layer_get_show_mask (layer))
+ {
+ bounding_box = gimp_drawable_get_bounding_box (GIMP_DRAWABLE (mask));
+ }
+ else
+ {
+ bounding_box = GIMP_LAYER_GET_CLASS (layer)->get_bounding_box (layer);
+
+ if (mask && gimp_layer_get_apply_mask (layer))
+ {
+ GeglRectangle mask_bounding_box;
+
+ mask_bounding_box = gimp_drawable_get_bounding_box (
+ GIMP_DRAWABLE (mask));
+
+ gegl_rectangle_intersect (&bounding_box,
+ &bounding_box, &mask_bounding_box);
+ }
+ }
+
+ return bounding_box;
+}
+
+static GimpColorProfile *
+gimp_layer_get_color_profile (GimpColorManaged *managed)
+{
+ GimpImage *image = gimp_item_get_image (GIMP_ITEM (managed));
+
+ return gimp_color_managed_get_color_profile (GIMP_COLOR_MANAGED (image));
+}
+
+static gdouble
+gimp_layer_get_opacity_at (GimpPickable *pickable,
+ gint x,
+ gint y)
+{
+ GimpLayer *layer = GIMP_LAYER (pickable);
+ gdouble value = GIMP_OPACITY_TRANSPARENT;
+
+ if (x >= 0 && x < gimp_item_get_width (GIMP_ITEM (layer)) &&
+ y >= 0 && y < gimp_item_get_height (GIMP_ITEM (layer)) &&
+ gimp_item_is_visible (GIMP_ITEM (layer)))
+ {
+ if (! gimp_drawable_has_alpha (GIMP_DRAWABLE (layer)))
+ {
+ value = GIMP_OPACITY_OPAQUE;
+ }
+ else
+ {
+ gegl_buffer_sample (gimp_drawable_get_buffer (GIMP_DRAWABLE (layer)),
+ x, y, NULL, &value, babl_format ("A double"),
+ GEGL_SAMPLER_NEAREST, GEGL_ABYSS_NONE);
+ }
+
+ if (gimp_layer_get_mask (layer) &&
+ gimp_layer_get_apply_mask (layer))
+ {
+ gdouble mask_value;
+
+ mask_value = gimp_pickable_get_opacity_at (GIMP_PICKABLE (layer->mask),
+ x, y);
+
+ value *= mask_value;
+ }
+ }
+
+ return value;
+}
+
+static void
+gimp_layer_pixel_to_srgb (GimpPickable *pickable,
+ const Babl *format,
+ gpointer pixel,
+ GimpRGB *color)
+{
+ GimpImage *image = gimp_item_get_image (GIMP_ITEM (pickable));
+
+ gimp_pickable_pixel_to_srgb (GIMP_PICKABLE (image), format, pixel, color);
+}
+
+static void
+gimp_layer_srgb_to_pixel (GimpPickable *pickable,
+ const GimpRGB *color,
+ const Babl *format,
+ gpointer pixel)
+{
+ GimpImage *image = gimp_item_get_image (GIMP_ITEM (pickable));
+
+ gimp_pickable_srgb_to_pixel (GIMP_PICKABLE (image), color, format, pixel);
+}
+
+static void
+gimp_layer_real_translate (GimpLayer *layer,
+ gint offset_x,
+ gint offset_y)
+{
+ /* update the old region */
+ gimp_drawable_update (GIMP_DRAWABLE (layer), 0, 0, -1, -1);
+
+ /* invalidate the selection boundary because of a layer modification */
+ gimp_drawable_invalidate_boundary (GIMP_DRAWABLE (layer));
+
+ GIMP_ITEM_CLASS (parent_class)->translate (GIMP_ITEM (layer),
+ offset_x, offset_y,
+ FALSE);
+
+ /* update the new region */
+ gimp_drawable_update (GIMP_DRAWABLE (layer), 0, 0, -1, -1);
+}
+
+static void
+gimp_layer_real_scale (GimpLayer *layer,
+ gint new_width,
+ gint new_height,
+ gint new_offset_x,
+ gint new_offset_y,
+ GimpInterpolationType interpolation_type,
+ GimpProgress *progress)
+{
+ GIMP_ITEM_CLASS (parent_class)->scale (GIMP_ITEM (layer),
+ new_width, new_height,
+ new_offset_x, new_offset_y,
+ interpolation_type, progress);
+}
+
+static void
+gimp_layer_real_resize (GimpLayer *layer,
+ GimpContext *context,
+ GimpFillType fill_type,
+ gint new_width,
+ gint new_height,
+ gint offset_x,
+ gint offset_y)
+{
+ if (fill_type == GIMP_FILL_TRANSPARENT &&
+ ! gimp_drawable_has_alpha (GIMP_DRAWABLE (layer)))
+ {
+ fill_type = GIMP_FILL_BACKGROUND;
+ }
+
+ GIMP_ITEM_CLASS (parent_class)->resize (GIMP_ITEM (layer),
+ context, fill_type,
+ new_width, new_height,
+ offset_x, offset_y);
+}
+
+static void
+gimp_layer_real_flip (GimpLayer *layer,
+ GimpContext *context,
+ GimpOrientationType flip_type,
+ gdouble axis,
+ gboolean clip_result)
+{
+ GIMP_ITEM_CLASS (parent_class)->flip (GIMP_ITEM (layer),
+ context, flip_type, axis, clip_result);
+}
+
+static void
+gimp_layer_real_rotate (GimpLayer *layer,
+ GimpContext *context,
+ GimpRotationType rotate_type,
+ gdouble center_x,
+ gdouble center_y,
+ gboolean clip_result)
+{
+ GIMP_ITEM_CLASS (parent_class)->rotate (GIMP_ITEM (layer),
+ context, rotate_type,
+ center_x, center_y,
+ clip_result);
+}
+
+static void
+gimp_layer_real_transform (GimpLayer *layer,
+ GimpContext *context,
+ const GimpMatrix3 *matrix,
+ GimpTransformDirection direction,
+ GimpInterpolationType interpolation_type,
+ GimpTransformResize clip_result,
+ GimpProgress *progress)
+{
+ /* FIXME: make interpolated transformations work on layers without alpha */
+ if (interpolation_type != GIMP_INTERPOLATION_NONE &&
+ ! gimp_drawable_has_alpha (GIMP_DRAWABLE (layer)))
+ gimp_layer_add_alpha (layer);
+
+ GIMP_ITEM_CLASS (parent_class)->transform (GIMP_ITEM (layer),
+ context, matrix, direction,
+ interpolation_type,
+ clip_result,
+ progress);
+}
+
+static void
+gimp_layer_real_convert_type (GimpLayer *layer,
+ GimpImage *dest_image,
+ const Babl *new_format,
+ GimpColorProfile *dest_profile,
+ GeglDitherMethod layer_dither_type,
+ GeglDitherMethod mask_dither_type,
+ gboolean push_undo,
+ GimpProgress *progress)
+{
+ GimpDrawable *drawable = GIMP_DRAWABLE (layer);
+ GeglBuffer *src_buffer;
+ GeglBuffer *dest_buffer;
+
+ if (layer_dither_type == GEGL_DITHER_NONE)
+ {
+ src_buffer = g_object_ref (gimp_drawable_get_buffer (drawable));
+ }
+ else
+ {
+ gint bits;
+
+ src_buffer =
+ gegl_buffer_new (GEGL_RECTANGLE (0, 0,
+ gimp_item_get_width (GIMP_ITEM (layer)),
+ gimp_item_get_height (GIMP_ITEM (layer))),
+ gimp_drawable_get_format (drawable));
+
+ bits = (babl_format_get_bytes_per_pixel (new_format) * 8 /
+ babl_format_get_n_components (new_format));
+
+ gimp_gegl_apply_dither (gimp_drawable_get_buffer (drawable),
+ NULL, NULL,
+ src_buffer, 1 << bits, layer_dither_type);
+ }
+
+ dest_buffer =
+ gegl_buffer_new (GEGL_RECTANGLE (0, 0,
+ gimp_item_get_width (GIMP_ITEM (layer)),
+ gimp_item_get_height (GIMP_ITEM (layer))),
+ new_format);
+
+ if (dest_profile)
+ {
+ GimpColorProfile *src_profile =
+ gimp_color_managed_get_color_profile (GIMP_COLOR_MANAGED (layer));
+
+ gimp_gegl_convert_color_profile (src_buffer, NULL, src_profile,
+ dest_buffer, NULL, dest_profile,
+ GIMP_COLOR_RENDERING_INTENT_PERCEPTUAL,
+ TRUE, progress);
+ }
+ else
+ {
+ gimp_gegl_buffer_copy (src_buffer, NULL, GEGL_ABYSS_NONE,
+ dest_buffer, NULL);
+ }
+
+ gimp_drawable_set_buffer (drawable, push_undo, NULL, dest_buffer);
+
+ g_object_unref (src_buffer);
+ g_object_unref (dest_buffer);
+}
+
+static GeglRectangle
+gimp_layer_real_get_bounding_box (GimpLayer *layer)
+{
+ return GIMP_DRAWABLE_CLASS (parent_class)->get_bounding_box (
+ GIMP_DRAWABLE (layer));
+}
+
+static void
+gimp_layer_real_get_effective_mode (GimpLayer *layer,
+ GimpLayerMode *mode,
+ GimpLayerColorSpace *blend_space,
+ GimpLayerColorSpace *composite_space,
+ GimpLayerCompositeMode *composite_mode)
+{
+ *mode = gimp_layer_get_mode (layer);
+ *blend_space = gimp_layer_get_real_blend_space (layer);
+ *composite_space = gimp_layer_get_real_composite_space (layer);
+ *composite_mode = gimp_layer_get_real_composite_mode (layer);
+}
+
+static gboolean
+gimp_layer_real_get_excludes_backdrop (GimpLayer *layer)
+{
+ GimpLayerCompositeRegion included_region;
+
+ included_region = gimp_layer_mode_get_included_region (layer->mode,
+ layer->effective_composite_mode);
+
+ return ! (included_region & GIMP_LAYER_COMPOSITE_REGION_DESTINATION);
+}
+
+static void
+gimp_layer_layer_mask_update (GimpDrawable *drawable,
+ gint x,
+ gint y,
+ gint width,
+ gint height,
+ GimpLayer *layer)
+{
+ if (gimp_layer_get_apply_mask (layer) ||
+ gimp_layer_get_show_mask (layer))
+ {
+ gimp_drawable_update (GIMP_DRAWABLE (layer),
+ x, y, width, height);
+ }
+}
+
+
+/* public functions */
+
+GimpLayer *
+gimp_layer_get_parent (GimpLayer *layer)
+{
+ g_return_val_if_fail (GIMP_IS_LAYER (layer), NULL);
+
+ return GIMP_LAYER (gimp_viewable_get_parent (GIMP_VIEWABLE (layer)));
+}
+
+GimpLayerMask *
+gimp_layer_get_mask (GimpLayer *layer)
+{
+ g_return_val_if_fail (GIMP_IS_LAYER (layer), NULL);
+
+ return layer->mask;
+}
+
+GimpLayerMask *
+gimp_layer_add_mask (GimpLayer *layer,
+ GimpLayerMask *mask,
+ gboolean push_undo,
+ GError **error)
+{
+ GimpImage *image;
+
+ g_return_val_if_fail (GIMP_IS_LAYER (layer), NULL);
+ g_return_val_if_fail (GIMP_IS_LAYER_MASK (mask), NULL);
+ g_return_val_if_fail (gimp_item_get_image (GIMP_ITEM (layer)) ==
+ gimp_item_get_image (GIMP_ITEM (mask)), NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ if (! gimp_item_is_attached (GIMP_ITEM (layer)))
+ push_undo = FALSE;
+
+ image = gimp_item_get_image (GIMP_ITEM (layer));
+
+ if (layer->mask)
+ {
+ g_set_error_literal (error, GIMP_ERROR, GIMP_FAILED,
+ _("Unable to add a layer mask since "
+ "the layer already has one."));
+ return NULL;
+ }
+
+ if ((gimp_item_get_width (GIMP_ITEM (layer)) !=
+ gimp_item_get_width (GIMP_ITEM (mask))) ||
+ (gimp_item_get_height (GIMP_ITEM (layer)) !=
+ gimp_item_get_height (GIMP_ITEM (mask))))
+ {
+ g_set_error_literal (error, GIMP_ERROR, GIMP_FAILED,
+ _("Cannot add layer mask of different "
+ "dimensions than specified layer."));
+ return NULL;
+ }
+
+ if (push_undo)
+ gimp_image_undo_push_layer_mask_add (image, C_("undo-type", "Add Layer Mask"),
+ layer, mask);
+
+ layer->mask = g_object_ref_sink (mask);
+ layer->apply_mask = TRUE;
+ layer->edit_mask = TRUE;
+ layer->show_mask = FALSE;
+
+ gimp_layer_mask_set_layer (mask, layer);
+
+ if (gimp_filter_peek_node (GIMP_FILTER (layer)))
+ {
+ GeglNode *mode_node;
+ GeglNode *mask;
+
+ mode_node = gimp_drawable_get_mode_node (GIMP_DRAWABLE (layer));
+
+ mask = gimp_drawable_get_source_node (GIMP_DRAWABLE (layer->mask));
+
+ gegl_node_connect_to (mask, "output",
+ layer->mask_offset_node, "input");
+
+ if (layer->show_mask)
+ {
+ gegl_node_connect_to (layer->mask_offset_node, "output",
+ mode_node, "aux");
+ }
+ else
+ {
+ gegl_node_connect_to (layer->mask_offset_node, "output",
+ mode_node, "aux2");
+ }
+
+ gimp_layer_update_mode_node (layer);
+ }
+
+ gimp_drawable_update_bounding_box (GIMP_DRAWABLE (layer));
+
+ gimp_layer_update_effective_mode (layer);
+ gimp_layer_update_excludes_backdrop (layer);
+
+ if (gimp_layer_get_apply_mask (layer) ||
+ gimp_layer_get_show_mask (layer))
+ {
+ gimp_drawable_update (GIMP_DRAWABLE (layer), 0, 0, -1, -1);
+ }
+
+ g_signal_connect (mask, "update",
+ G_CALLBACK (gimp_layer_layer_mask_update),
+ layer);
+
+ g_signal_emit (layer, layer_signals[MASK_CHANGED], 0);
+
+ g_object_notify (G_OBJECT (layer), "mask");
+
+ /* if the mask came from the undo stack, reset its "removed" state */
+ if (gimp_item_is_removed (GIMP_ITEM (mask)))
+ gimp_item_unset_removed (GIMP_ITEM (mask));
+
+ return layer->mask;
+}
+
+GimpLayerMask *
+gimp_layer_create_mask (GimpLayer *layer,
+ GimpAddMaskType add_mask_type,
+ GimpChannel *channel)
+{
+ GimpDrawable *drawable;
+ GimpItem *item;
+ GimpLayerMask *mask;
+ GimpImage *image;
+ gchar *mask_name;
+ GimpRGB black = { 0.0, 0.0, 0.0, GIMP_OPACITY_OPAQUE };
+
+ g_return_val_if_fail (GIMP_IS_LAYER (layer), NULL);
+ g_return_val_if_fail (add_mask_type != GIMP_ADD_MASK_CHANNEL ||
+ GIMP_IS_CHANNEL (channel), NULL);
+
+ drawable = GIMP_DRAWABLE (layer);
+ item = GIMP_ITEM (layer);
+ image = gimp_item_get_image (item);
+
+ mask_name = g_strdup_printf (_("%s mask"),
+ gimp_object_get_name (layer));
+
+ mask = gimp_layer_mask_new (image,
+ gimp_item_get_width (item),
+ gimp_item_get_height (item),
+ mask_name, &black);
+
+ g_free (mask_name);
+
+ switch (add_mask_type)
+ {
+ case GIMP_ADD_MASK_WHITE:
+ gimp_channel_all (GIMP_CHANNEL (mask), FALSE);
+ break;
+
+ case GIMP_ADD_MASK_BLACK:
+ gimp_channel_clear (GIMP_CHANNEL (mask), NULL, FALSE);
+ break;
+
+ case GIMP_ADD_MASK_ALPHA:
+ case GIMP_ADD_MASK_ALPHA_TRANSFER:
+ if (gimp_drawable_has_alpha (drawable))
+ {
+ GeglBuffer *dest_buffer;
+ const Babl *component_format;
+
+ dest_buffer = gimp_drawable_get_buffer (GIMP_DRAWABLE (mask));
+
+ component_format =
+ gimp_image_get_component_format (image, GIMP_CHANNEL_ALPHA);
+
+ gegl_buffer_set_format (dest_buffer, component_format);
+ gimp_gegl_buffer_copy (gimp_drawable_get_buffer (drawable), NULL,
+ GEGL_ABYSS_NONE,
+ dest_buffer, NULL);
+ gegl_buffer_set_format (dest_buffer, NULL);
+
+ if (add_mask_type == GIMP_ADD_MASK_ALPHA_TRANSFER)
+ {
+ gimp_drawable_push_undo (drawable,
+ C_("undo-type", "Transfer Alpha to Mask"),
+ NULL,
+ 0, 0,
+ gimp_item_get_width (item),
+ gimp_item_get_height (item));
+
+ gimp_gegl_apply_set_alpha (gimp_drawable_get_buffer (drawable),
+ NULL, NULL,
+ gimp_drawable_get_buffer (drawable),
+ 1.0);
+ }
+ }
+ break;
+
+ case GIMP_ADD_MASK_SELECTION:
+ case GIMP_ADD_MASK_CHANNEL:
+ {
+ gboolean channel_empty;
+ gint offset_x, offset_y;
+ gint copy_x, copy_y;
+ gint copy_width, copy_height;
+
+ if (add_mask_type == GIMP_ADD_MASK_SELECTION)
+ channel = GIMP_CHANNEL (gimp_image_get_mask (image));
+
+ channel_empty = gimp_channel_is_empty (channel);
+
+ gimp_item_get_offset (item, &offset_x, &offset_y);
+
+ gimp_rectangle_intersect (0, 0,
+ gimp_image_get_width (image),
+ gimp_image_get_height (image),
+ offset_x, offset_y,
+ gimp_item_get_width (item),
+ gimp_item_get_height (item),
+ &copy_x, &copy_y,
+ &copy_width, &copy_height);
+
+ if (copy_width < gimp_item_get_width (item) ||
+ copy_height < gimp_item_get_height (item) ||
+ channel_empty)
+ gimp_channel_clear (GIMP_CHANNEL (mask), NULL, FALSE);
+
+ if ((copy_width || copy_height) && ! channel_empty)
+ {
+ GeglBuffer *src;
+ GeglBuffer *dest;
+ const Babl *format;
+
+ src = gimp_drawable_get_buffer (GIMP_DRAWABLE (channel));
+ dest = gimp_drawable_get_buffer (GIMP_DRAWABLE (mask));
+
+ format = gegl_buffer_get_format (src);
+
+ /* make sure no gamma conversion happens */
+ gegl_buffer_set_format (dest, format);
+ gimp_gegl_buffer_copy (src,
+ GEGL_RECTANGLE (copy_x, copy_y,
+ copy_width, copy_height),
+ GEGL_ABYSS_NONE,
+ dest,
+ GEGL_RECTANGLE (copy_x - offset_x,
+ copy_y - offset_y,
+ 0, 0));
+ gegl_buffer_set_format (dest, NULL);
+
+ GIMP_CHANNEL (mask)->bounds_known = FALSE;
+ }
+ }
+ break;
+
+ case GIMP_ADD_MASK_COPY:
+ {
+ GeglBuffer *src_buffer;
+ GeglBuffer *dest_buffer;
+
+ if (! gimp_drawable_is_gray (drawable))
+ {
+ const Babl *copy_format =
+ gimp_image_get_format (image, GIMP_GRAY,
+ gimp_drawable_get_precision (drawable),
+ gimp_drawable_has_alpha (drawable));
+
+ src_buffer =
+ gegl_buffer_new (GEGL_RECTANGLE (0, 0,
+ gimp_item_get_width (item),
+ gimp_item_get_height (item)),
+ copy_format);
+
+ gimp_gegl_buffer_copy (gimp_drawable_get_buffer (drawable), NULL,
+ GEGL_ABYSS_NONE,
+ src_buffer, NULL);
+ }
+ else
+ {
+ src_buffer = gimp_drawable_get_buffer (drawable);
+ g_object_ref (src_buffer);
+ }
+
+ dest_buffer = gimp_drawable_get_buffer (GIMP_DRAWABLE (mask));
+
+ if (gimp_drawable_has_alpha (drawable))
+ {
+ GimpRGB background;
+
+ gimp_rgba_set (&background, 0.0, 0.0, 0.0, 0.0);
+
+ gimp_gegl_apply_flatten (src_buffer, NULL, NULL,
+ dest_buffer, &background,
+ GIMP_LAYER_COLOR_SPACE_RGB_LINEAR);
+ }
+ else
+ {
+ gimp_gegl_buffer_copy (src_buffer, NULL, GEGL_ABYSS_NONE,
+ dest_buffer, NULL);
+ }
+
+ g_object_unref (src_buffer);
+ }
+
+ GIMP_CHANNEL (mask)->bounds_known = FALSE;
+ break;
+ }
+
+ return mask;
+}
+
+void
+gimp_layer_apply_mask (GimpLayer *layer,
+ GimpMaskApplyMode mode,
+ gboolean push_undo)
+{
+ GimpItem *item;
+ GimpImage *image;
+ GimpLayerMask *mask;
+ gboolean view_changed = FALSE;
+
+ g_return_if_fail (GIMP_IS_LAYER (layer));
+
+ mask = gimp_layer_get_mask (layer);
+
+ if (! mask)
+ return;
+
+ /* APPLY can not be done to group layers */
+ g_return_if_fail (! gimp_viewable_get_children (GIMP_VIEWABLE (layer)) ||
+ mode == GIMP_MASK_DISCARD);
+
+ /* APPLY can only be done to layers with an alpha channel */
+ g_return_if_fail (gimp_drawable_has_alpha (GIMP_DRAWABLE (layer)) ||
+ mode == GIMP_MASK_DISCARD || push_undo == TRUE);
+
+ item = GIMP_ITEM (layer);
+ image = gimp_item_get_image (item);
+
+ if (! image)
+ return;
+
+ if (push_undo)
+ {
+ gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_LAYER_APPLY_MASK,
+ (mode == GIMP_MASK_APPLY) ?
+ C_("undo-type", "Apply Layer Mask") :
+ C_("undo-type", "Delete Layer Mask"));
+
+ gimp_image_undo_push_layer_mask_show (image, NULL, layer);
+ gimp_image_undo_push_layer_mask_apply (image, NULL, layer);
+ gimp_image_undo_push_layer_mask_remove (image, NULL, layer, mask);
+
+ if (mode == GIMP_MASK_APPLY &&
+ ! gimp_drawable_has_alpha (GIMP_DRAWABLE (layer)))
+ {
+ gimp_layer_add_alpha (layer);
+ }
+ }
+
+ /* check if applying the mask changes the projection */
+ if (gimp_layer_get_show_mask (layer) ||
+ (mode == GIMP_MASK_APPLY && ! gimp_layer_get_apply_mask (layer)) ||
+ (mode == GIMP_MASK_DISCARD && gimp_layer_get_apply_mask (layer)))
+ {
+ view_changed = TRUE;
+ }
+
+ if (mode == GIMP_MASK_APPLY)
+ {
+ GeglBuffer *mask_buffer;
+ GeglBuffer *dest_buffer;
+
+ if (push_undo)
+ gimp_drawable_push_undo (GIMP_DRAWABLE (layer), NULL,
+ NULL,
+ 0, 0,
+ gimp_item_get_width (item),
+ gimp_item_get_height (item));
+
+ /* Combine the current layer's alpha channel and the mask */
+ mask_buffer = gimp_drawable_get_buffer (GIMP_DRAWABLE (mask));
+ dest_buffer = gimp_drawable_get_buffer (GIMP_DRAWABLE (layer));
+
+ gimp_gegl_apply_opacity (gimp_drawable_get_buffer (GIMP_DRAWABLE (layer)),
+ NULL, NULL, dest_buffer,
+ mask_buffer, 0, 0, 1.0);
+ }
+
+ g_signal_handlers_disconnect_by_func (mask,
+ gimp_layer_layer_mask_update,
+ layer);
+
+ gimp_item_removed (GIMP_ITEM (mask));
+ g_object_unref (mask);
+ layer->mask = NULL;
+
+ if (push_undo)
+ gimp_image_undo_group_end (image);
+
+ if (gimp_filter_peek_node (GIMP_FILTER (layer)))
+ {
+ GeglNode *mode_node;
+
+ mode_node = gimp_drawable_get_mode_node (GIMP_DRAWABLE (layer));
+
+ if (layer->show_mask)
+ {
+ gegl_node_connect_to (layer->layer_offset_node, "output",
+ mode_node, "aux");
+ }
+ else
+ {
+ gegl_node_disconnect (mode_node, "aux2");
+ }
+
+ gimp_layer_update_mode_node (layer);
+ }
+
+ gimp_drawable_update_bounding_box (GIMP_DRAWABLE (layer));
+
+ gimp_layer_update_effective_mode (layer);
+ gimp_layer_update_excludes_backdrop (layer);
+
+ /* If applying actually changed the view */
+ if (view_changed)
+ {
+ gimp_drawable_update (GIMP_DRAWABLE (layer), 0, 0, -1, -1);
+ }
+ else
+ {
+ gimp_viewable_invalidate_preview (GIMP_VIEWABLE (layer));
+ }
+
+ g_signal_emit (layer, layer_signals[MASK_CHANGED], 0);
+
+ g_object_notify (G_OBJECT (layer), "mask");
+}
+
+void
+gimp_layer_set_apply_mask (GimpLayer *layer,
+ gboolean apply,
+ gboolean push_undo)
+{
+ g_return_if_fail (GIMP_IS_LAYER (layer));
+ g_return_if_fail (layer->mask != NULL);
+
+ if (layer->apply_mask != apply)
+ {
+ GimpImage *image = gimp_item_get_image (GIMP_ITEM (layer));
+
+ if (push_undo && gimp_item_is_attached (GIMP_ITEM (layer)))
+ gimp_image_undo_push_layer_mask_apply (image,
+ apply ?
+ C_("undo-type", "Enable Layer Mask") :
+ C_("undo-type", "Disable Layer Mask"),
+ layer);
+
+ layer->apply_mask = apply ? TRUE : FALSE;
+
+ if (gimp_filter_peek_node (GIMP_FILTER (layer)) &&
+ ! gimp_layer_get_show_mask (layer))
+ {
+ GeglNode *mode_node;
+
+ mode_node = gimp_drawable_get_mode_node (GIMP_DRAWABLE (layer));
+
+ if (layer->apply_mask)
+ {
+ gegl_node_connect_to (layer->mask_offset_node, "output",
+ mode_node, "aux2");
+ }
+ else
+ {
+ gegl_node_disconnect (mode_node, "aux2");
+ }
+ }
+
+ gimp_drawable_update_bounding_box (GIMP_DRAWABLE (layer));
+
+ gimp_layer_update_effective_mode (layer);
+ gimp_layer_update_excludes_backdrop (layer);
+
+ gimp_drawable_update (GIMP_DRAWABLE (layer), 0, 0, -1, -1);
+
+ g_signal_emit (layer, layer_signals[APPLY_MASK_CHANGED], 0);
+ }
+}
+
+gboolean
+gimp_layer_get_apply_mask (GimpLayer *layer)
+{
+ g_return_val_if_fail (GIMP_IS_LAYER (layer), FALSE);
+ g_return_val_if_fail (layer->mask, FALSE);
+
+ return layer->apply_mask;
+}
+
+void
+gimp_layer_set_edit_mask (GimpLayer *layer,
+ gboolean edit)
+{
+ g_return_if_fail (GIMP_IS_LAYER (layer));
+ g_return_if_fail (layer->mask != NULL);
+
+ if (layer->edit_mask != edit)
+ {
+ layer->edit_mask = edit ? TRUE : FALSE;
+
+ g_signal_emit (layer, layer_signals[EDIT_MASK_CHANGED], 0);
+ }
+}
+
+gboolean
+gimp_layer_get_edit_mask (GimpLayer *layer)
+{
+ g_return_val_if_fail (GIMP_IS_LAYER (layer), FALSE);
+ g_return_val_if_fail (layer->mask, FALSE);
+
+ return layer->edit_mask;
+}
+
+void
+gimp_layer_set_show_mask (GimpLayer *layer,
+ gboolean show,
+ gboolean push_undo)
+{
+ g_return_if_fail (GIMP_IS_LAYER (layer));
+ g_return_if_fail (layer->mask != NULL);
+
+ if (layer->show_mask != show)
+ {
+ GimpImage *image = gimp_item_get_image (GIMP_ITEM (layer));
+
+ if (push_undo)
+ gimp_image_undo_push_layer_mask_show (image,
+ C_("undo-type", "Show Layer Mask"),
+ layer);
+
+ layer->show_mask = show ? TRUE : FALSE;
+
+ if (gimp_filter_peek_node (GIMP_FILTER (layer)))
+ {
+ GeglNode *mode_node;
+
+ mode_node = gimp_drawable_get_mode_node (GIMP_DRAWABLE (layer));
+
+ if (layer->show_mask)
+ {
+ gegl_node_disconnect (mode_node, "aux2");
+
+ gegl_node_connect_to (layer->mask_offset_node, "output",
+ mode_node, "aux");
+ }
+ else
+ {
+ gegl_node_connect_to (layer->layer_offset_node, "output",
+ mode_node, "aux");
+
+ if (gimp_layer_get_apply_mask (layer))
+ {
+ gegl_node_connect_to (layer->mask_offset_node, "output",
+ mode_node, "aux2");
+ }
+ }
+
+ gimp_layer_update_mode_node (layer);
+ }
+
+ gimp_drawable_update_bounding_box (GIMP_DRAWABLE (layer));
+
+ gimp_layer_update_effective_mode (layer);
+ gimp_layer_update_excludes_backdrop (layer);
+
+ gimp_drawable_update (GIMP_DRAWABLE (layer), 0, 0, -1, -1);
+
+ g_signal_emit (layer, layer_signals[SHOW_MASK_CHANGED], 0);
+ }
+}
+
+gboolean
+gimp_layer_get_show_mask (GimpLayer *layer)
+{
+ g_return_val_if_fail (GIMP_IS_LAYER (layer), FALSE);
+ g_return_val_if_fail (layer->mask, FALSE);
+
+ return layer->show_mask;
+}
+
+void
+gimp_layer_add_alpha (GimpLayer *layer)
+{
+ GimpItem *item;
+ GimpDrawable *drawable;
+ GeglBuffer *new_buffer;
+
+ g_return_if_fail (GIMP_IS_LAYER (layer));
+
+ if (gimp_drawable_has_alpha (GIMP_DRAWABLE (layer)))
+ return;
+
+ item = GIMP_ITEM (layer);
+ drawable = GIMP_DRAWABLE (layer);
+
+ new_buffer = gegl_buffer_new (GEGL_RECTANGLE (0, 0,
+ gimp_item_get_width (item),
+ gimp_item_get_height (item)),
+ gimp_drawable_get_format_with_alpha (drawable));
+
+ gimp_gegl_buffer_copy (
+ gimp_drawable_get_buffer (drawable), NULL, GEGL_ABYSS_NONE,
+ new_buffer, NULL);
+
+ gimp_drawable_set_buffer (GIMP_DRAWABLE (layer),
+ gimp_item_is_attached (GIMP_ITEM (layer)),
+ C_("undo-type", "Add Alpha Channel"),
+ new_buffer);
+ g_object_unref (new_buffer);
+}
+
+void
+gimp_layer_remove_alpha (GimpLayer *layer,
+ GimpContext *context)
+{
+ GeglBuffer *new_buffer;
+ GimpRGB background;
+
+ g_return_if_fail (GIMP_IS_LAYER (layer));
+ g_return_if_fail (GIMP_IS_CONTEXT (context));
+
+ if (! gimp_drawable_has_alpha (GIMP_DRAWABLE (layer)))
+ return;
+
+ new_buffer =
+ gegl_buffer_new (GEGL_RECTANGLE (0, 0,
+ gimp_item_get_width (GIMP_ITEM (layer)),
+ gimp_item_get_height (GIMP_ITEM (layer))),
+ gimp_drawable_get_format_without_alpha (GIMP_DRAWABLE (layer)));
+
+ gimp_context_get_background (context, &background);
+ gimp_pickable_srgb_to_image_color (GIMP_PICKABLE (layer),
+ &background, &background);
+
+ gimp_gegl_apply_flatten (gimp_drawable_get_buffer (GIMP_DRAWABLE (layer)),
+ NULL, NULL,
+ new_buffer, &background,
+ gimp_layer_get_real_composite_space (layer));
+
+ gimp_drawable_set_buffer (GIMP_DRAWABLE (layer),
+ gimp_item_is_attached (GIMP_ITEM (layer)),
+ C_("undo-type", "Remove Alpha Channel"),
+ new_buffer);
+ g_object_unref (new_buffer);
+}
+
+void
+gimp_layer_resize_to_image (GimpLayer *layer,
+ GimpContext *context,
+ GimpFillType fill_type)
+{
+ GimpImage *image;
+ gint offset_x;
+ gint offset_y;
+
+ g_return_if_fail (GIMP_IS_LAYER (layer));
+ g_return_if_fail (gimp_item_is_attached (GIMP_ITEM (layer)));
+ g_return_if_fail (GIMP_IS_CONTEXT (context));
+
+ image = gimp_item_get_image (GIMP_ITEM (layer));
+
+ gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_ITEM_RESIZE,
+ C_("undo-type", "Layer to Image Size"));
+
+ gimp_item_get_offset (GIMP_ITEM (layer), &offset_x, &offset_y);
+ gimp_item_resize (GIMP_ITEM (layer), context, fill_type,
+ gimp_image_get_width (image),
+ gimp_image_get_height (image),
+ offset_x, offset_y);
+
+ gimp_image_undo_group_end (image);
+}
+
+/**********************/
+/* access functions */
+/**********************/
+
+GimpDrawable *
+gimp_layer_get_floating_sel_drawable (GimpLayer *layer)
+{
+ g_return_val_if_fail (GIMP_IS_LAYER (layer), NULL);
+
+ return layer->fs.drawable;
+}
+
+void
+gimp_layer_set_floating_sel_drawable (GimpLayer *layer,
+ GimpDrawable *drawable)
+{
+ g_return_if_fail (GIMP_IS_LAYER (layer));
+ g_return_if_fail (drawable == NULL || GIMP_IS_DRAWABLE (drawable));
+
+ if (g_set_object (&layer->fs.drawable, drawable))
+ {
+ if (layer->fs.segs)
+ {
+ g_clear_pointer (&layer->fs.segs, g_free);
+ layer->fs.num_segs = 0;
+ }
+
+ g_object_notify (G_OBJECT (layer), "floating-selection");
+ }
+}
+
+gboolean
+gimp_layer_is_floating_sel (GimpLayer *layer)
+{
+ g_return_val_if_fail (GIMP_IS_LAYER (layer), FALSE);
+
+ return (gimp_layer_get_floating_sel_drawable (layer) != NULL);
+}
+
+void
+gimp_layer_set_opacity (GimpLayer *layer,
+ gdouble opacity,
+ gboolean push_undo)
+{
+ g_return_if_fail (GIMP_IS_LAYER (layer));
+
+ opacity = CLAMP (opacity, GIMP_OPACITY_TRANSPARENT, GIMP_OPACITY_OPAQUE);
+
+ if (layer->opacity != opacity)
+ {
+ if (push_undo && gimp_item_is_attached (GIMP_ITEM (layer)))
+ {
+ GimpImage *image = gimp_item_get_image (GIMP_ITEM (layer));
+
+ gimp_image_undo_push_layer_opacity (image, NULL, layer);
+ }
+
+ layer->opacity = opacity;
+
+ g_signal_emit (layer, layer_signals[OPACITY_CHANGED], 0);
+ g_object_notify (G_OBJECT (layer), "opacity");
+
+ if (gimp_filter_peek_node (GIMP_FILTER (layer)))
+ gimp_layer_update_mode_node (layer);
+
+ gimp_drawable_update (GIMP_DRAWABLE (layer), 0, 0, -1, -1);
+ }
+}
+
+gdouble
+gimp_layer_get_opacity (GimpLayer *layer)
+{
+ g_return_val_if_fail (GIMP_IS_LAYER (layer), GIMP_OPACITY_OPAQUE);
+
+ return layer->opacity;
+}
+
+void
+gimp_layer_set_mode (GimpLayer *layer,
+ GimpLayerMode mode,
+ gboolean push_undo)
+{
+ g_return_if_fail (GIMP_IS_LAYER (layer));
+
+ if (gimp_viewable_get_children (GIMP_VIEWABLE (layer)) == NULL)
+ {
+ g_return_if_fail (gimp_layer_mode_get_context (mode) &
+ GIMP_LAYER_MODE_CONTEXT_LAYER);
+ }
+ else
+ {
+ g_return_if_fail (gimp_layer_mode_get_context (mode) &
+ GIMP_LAYER_MODE_CONTEXT_GROUP);
+ }
+
+ if (layer->mode != mode)
+ {
+ if (gimp_item_is_attached (GIMP_ITEM (layer)))
+ {
+ GimpImage *image = gimp_item_get_image (GIMP_ITEM (layer));
+
+ gimp_image_unset_default_new_layer_mode (image);
+
+ if (push_undo)
+ gimp_image_undo_push_layer_mode (image, NULL, layer);
+ }
+
+ g_object_freeze_notify (G_OBJECT (layer));
+
+ layer->mode = mode;
+
+ g_signal_emit (layer, layer_signals[MODE_CHANGED], 0);
+ g_object_notify (G_OBJECT (layer), "mode");
+
+ /* when changing modes, we always switch to AUTO blend and
+ * composite in order to avoid confusion
+ */
+ if (layer->blend_space != GIMP_LAYER_COLOR_SPACE_AUTO)
+ {
+ layer->blend_space = GIMP_LAYER_COLOR_SPACE_AUTO;
+
+ g_signal_emit (layer, layer_signals[BLEND_SPACE_CHANGED], 0);
+ g_object_notify (G_OBJECT (layer), "blend-space");
+ }
+
+ if (layer->composite_space != GIMP_LAYER_COLOR_SPACE_AUTO)
+ {
+ layer->composite_space = GIMP_LAYER_COLOR_SPACE_AUTO;
+
+ g_signal_emit (layer, layer_signals[COMPOSITE_SPACE_CHANGED], 0);
+ g_object_notify (G_OBJECT (layer), "composite-space");
+ }
+
+ if (layer->composite_mode != GIMP_LAYER_COMPOSITE_AUTO)
+ {
+ layer->composite_mode = GIMP_LAYER_COMPOSITE_AUTO;
+
+ g_signal_emit (layer, layer_signals[COMPOSITE_MODE_CHANGED], 0);
+ g_object_notify (G_OBJECT (layer), "composite-mode");
+ }
+
+ g_object_thaw_notify (G_OBJECT (layer));
+
+ gimp_layer_update_effective_mode (layer);
+ gimp_layer_update_excludes_backdrop (layer);
+ }
+}
+
+GimpLayerMode
+gimp_layer_get_mode (GimpLayer *layer)
+{
+ g_return_val_if_fail (GIMP_IS_LAYER (layer), GIMP_LAYER_MODE_NORMAL);
+
+ return layer->mode;
+}
+
+void
+gimp_layer_set_blend_space (GimpLayer *layer,
+ GimpLayerColorSpace blend_space,
+ gboolean push_undo)
+{
+ g_return_if_fail (GIMP_IS_LAYER (layer));
+
+ if (! gimp_layer_mode_is_blend_space_mutable (layer->mode))
+ return;
+
+ if (layer->blend_space != blend_space)
+ {
+ if (push_undo && gimp_item_is_attached (GIMP_ITEM (layer)))
+ {
+ GimpImage *image = gimp_item_get_image (GIMP_ITEM (layer));
+
+ gimp_image_undo_push_layer_mode (image, NULL, layer);
+ }
+
+ layer->blend_space = blend_space;
+
+ g_signal_emit (layer, layer_signals[BLEND_SPACE_CHANGED], 0);
+ g_object_notify (G_OBJECT (layer), "blend-space");
+
+ gimp_layer_update_effective_mode (layer);
+ }
+}
+
+GimpLayerColorSpace
+gimp_layer_get_blend_space (GimpLayer *layer)
+{
+ g_return_val_if_fail (GIMP_IS_LAYER (layer), GIMP_LAYER_COLOR_SPACE_AUTO);
+
+ return layer->blend_space;
+}
+
+GimpLayerColorSpace
+gimp_layer_get_real_blend_space (GimpLayer *layer)
+{
+ g_return_val_if_fail (GIMP_IS_LAYER (layer), GIMP_LAYER_COLOR_SPACE_RGB_LINEAR);
+
+ if (layer->blend_space == GIMP_LAYER_COLOR_SPACE_AUTO)
+ return gimp_layer_mode_get_blend_space (layer->mode);
+ else
+ return layer->blend_space;
+}
+
+void
+gimp_layer_set_composite_space (GimpLayer *layer,
+ GimpLayerColorSpace composite_space,
+ gboolean push_undo)
+{
+ g_return_if_fail (GIMP_IS_LAYER (layer));
+
+ if (! gimp_layer_mode_is_composite_space_mutable (layer->mode))
+ return;
+
+ if (layer->composite_space != composite_space)
+ {
+ if (push_undo && gimp_item_is_attached (GIMP_ITEM (layer)))
+ {
+ GimpImage *image = gimp_item_get_image (GIMP_ITEM (layer));
+
+ gimp_image_undo_push_layer_mode (image, NULL, layer);
+ }
+
+ layer->composite_space = composite_space;
+
+ g_signal_emit (layer, layer_signals[COMPOSITE_SPACE_CHANGED], 0);
+ g_object_notify (G_OBJECT (layer), "composite-space");
+
+ gimp_layer_update_effective_mode (layer);
+ }
+}
+
+GimpLayerColorSpace
+gimp_layer_get_composite_space (GimpLayer *layer)
+{
+ g_return_val_if_fail (GIMP_IS_LAYER (layer), GIMP_LAYER_COLOR_SPACE_AUTO);
+
+ return layer->composite_space;
+}
+
+GimpLayerColorSpace
+gimp_layer_get_real_composite_space (GimpLayer *layer)
+{
+ g_return_val_if_fail (GIMP_IS_LAYER (layer), GIMP_LAYER_COLOR_SPACE_RGB_LINEAR);
+
+ if (layer->composite_space == GIMP_LAYER_COLOR_SPACE_AUTO)
+ return gimp_layer_mode_get_composite_space (layer->mode);
+ else
+ return layer->composite_space;
+}
+
+void
+gimp_layer_set_composite_mode (GimpLayer *layer,
+ GimpLayerCompositeMode composite_mode,
+ gboolean push_undo)
+{
+ g_return_if_fail (GIMP_IS_LAYER (layer));
+
+ if (! gimp_layer_mode_is_composite_mode_mutable (layer->mode))
+ return;
+
+ if (layer->composite_mode != composite_mode)
+ {
+ if (push_undo && gimp_item_is_attached (GIMP_ITEM (layer)))
+ {
+ GimpImage *image = gimp_item_get_image (GIMP_ITEM (layer));
+
+ gimp_image_undo_push_layer_mode (image, NULL, layer);
+ }
+
+ layer->composite_mode = composite_mode;
+
+ g_signal_emit (layer, layer_signals[COMPOSITE_MODE_CHANGED], 0);
+ g_object_notify (G_OBJECT (layer), "composite-mode");
+
+ gimp_layer_update_effective_mode (layer);
+ gimp_layer_update_excludes_backdrop (layer);
+ }
+}
+
+GimpLayerCompositeMode
+gimp_layer_get_composite_mode (GimpLayer *layer)
+{
+ g_return_val_if_fail (GIMP_IS_LAYER (layer), GIMP_LAYER_COMPOSITE_AUTO);
+
+ return layer->composite_mode;
+}
+
+GimpLayerCompositeMode
+gimp_layer_get_real_composite_mode (GimpLayer *layer)
+{
+ g_return_val_if_fail (GIMP_IS_LAYER (layer), GIMP_LAYER_COMPOSITE_UNION);
+
+ if (layer->composite_mode == GIMP_LAYER_COMPOSITE_AUTO)
+ return gimp_layer_mode_get_composite_mode (layer->mode);
+ else
+ return layer->composite_mode;
+}
+
+void
+gimp_layer_get_effective_mode (GimpLayer *layer,
+ GimpLayerMode *mode,
+ GimpLayerColorSpace *blend_space,
+ GimpLayerColorSpace *composite_space,
+ GimpLayerCompositeMode *composite_mode)
+{
+ g_return_if_fail (GIMP_IS_LAYER (layer));
+
+ if (mode) *mode = layer->effective_mode;
+ if (blend_space) *blend_space = layer->effective_blend_space;
+ if (composite_space) *composite_space = layer->effective_composite_space;
+ if (composite_mode) *composite_mode = layer->effective_composite_mode;
+}
+
+gboolean
+gimp_layer_get_excludes_backdrop (GimpLayer *layer)
+{
+ g_return_val_if_fail (GIMP_IS_LAYER (layer), FALSE);
+
+ return layer->excludes_backdrop;
+}
+
+void
+gimp_layer_set_lock_alpha (GimpLayer *layer,
+ gboolean lock_alpha,
+ gboolean push_undo)
+{
+ g_return_if_fail (GIMP_IS_LAYER (layer));
+ g_return_if_fail (gimp_layer_can_lock_alpha (layer));
+
+ lock_alpha = lock_alpha ? TRUE : FALSE;
+
+ if (layer->lock_alpha != lock_alpha)
+ {
+ if (push_undo && gimp_item_is_attached (GIMP_ITEM (layer)))
+ {
+ GimpImage *image = gimp_item_get_image (GIMP_ITEM (layer));
+
+ gimp_image_undo_push_layer_lock_alpha (image, NULL, layer);
+ }
+
+ layer->lock_alpha = lock_alpha;
+
+ g_signal_emit (layer, layer_signals[LOCK_ALPHA_CHANGED], 0);
+ g_object_notify (G_OBJECT (layer), "lock-alpha");
+ }
+}
+
+gboolean
+gimp_layer_get_lock_alpha (GimpLayer *layer)
+{
+ g_return_val_if_fail (GIMP_IS_LAYER (layer), FALSE);
+
+ return layer->lock_alpha;
+}
+
+gboolean
+gimp_layer_can_lock_alpha (GimpLayer *layer)
+{
+ g_return_val_if_fail (GIMP_IS_LAYER (layer), FALSE);
+
+ if (gimp_viewable_get_children (GIMP_VIEWABLE (layer)))
+ return FALSE;
+
+ return TRUE;
+}
+
+
+/* protected functions */
+
+void
+gimp_layer_update_effective_mode (GimpLayer *layer)
+{
+ GimpLayerMode mode;
+ GimpLayerColorSpace blend_space;
+ GimpLayerColorSpace composite_space;
+ GimpLayerCompositeMode composite_mode;
+
+ g_return_if_fail (GIMP_IS_LAYER (layer));
+
+ if (layer->mask && layer->show_mask)
+ {
+ mode = GIMP_LAYER_MODE_NORMAL;
+ blend_space = GIMP_LAYER_COLOR_SPACE_AUTO;
+ composite_space = GIMP_LAYER_COLOR_SPACE_AUTO;
+ composite_mode = GIMP_LAYER_COMPOSITE_AUTO;
+
+ /* This makes sure that masks of LEGACY-mode layers are
+ * composited in PERCEPTUAL space, and non-LEGACY layers in
+ * LINEAR space, or whatever composite space was chosen in the
+ * layer attributes dialog
+ */
+ composite_space = gimp_layer_get_real_composite_space (layer);
+ }
+ else
+ {
+ GIMP_LAYER_GET_CLASS (layer)->get_effective_mode (layer,
+ &mode,
+ &blend_space,
+ &composite_space,
+ &composite_mode);
+ }
+
+ if (mode != layer->effective_mode ||
+ blend_space != layer->effective_blend_space ||
+ composite_space != layer->effective_composite_space ||
+ composite_mode != layer->effective_composite_mode)
+ {
+ layer->effective_mode = mode;
+ layer->effective_blend_space = blend_space;
+ layer->effective_composite_space = composite_space;
+ layer->effective_composite_mode = composite_mode;
+
+ g_signal_emit (layer, layer_signals[EFFECTIVE_MODE_CHANGED], 0);
+
+ if (gimp_filter_peek_node (GIMP_FILTER (layer)))
+ gimp_layer_update_mode_node (layer);
+
+ gimp_drawable_update (GIMP_DRAWABLE (layer), 0, 0, -1, -1);
+ }
+}
+
+void
+gimp_layer_update_excludes_backdrop (GimpLayer *layer)
+{
+ gboolean excludes_backdrop;
+
+ g_return_if_fail (GIMP_IS_LAYER (layer));
+
+ excludes_backdrop =
+ GIMP_LAYER_GET_CLASS (layer)->get_excludes_backdrop (layer);
+
+ if (excludes_backdrop != layer->excludes_backdrop)
+ {
+ layer->excludes_backdrop = excludes_backdrop;
+
+ g_signal_emit (layer, layer_signals[EXCLUDES_BACKDROP_CHANGED], 0);
+ g_object_notify (G_OBJECT (layer), "excludes-backdrop");
+ }
+}
diff --git a/app/core/gimplayer.h b/app/core/gimplayer.h
new file mode 100644
index 0000000..f8c9d82
--- /dev/null
+++ b/app/core/gimplayer.h
@@ -0,0 +1,243 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_LAYER_H__
+#define __GIMP_LAYER_H__
+
+
+#include "gimpdrawable.h"
+
+
+#define GIMP_TYPE_LAYER (gimp_layer_get_type ())
+#define GIMP_LAYER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_LAYER, GimpLayer))
+#define GIMP_LAYER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_LAYER, GimpLayerClass))
+#define GIMP_IS_LAYER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_LAYER))
+#define GIMP_IS_LAYER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_LAYER))
+#define GIMP_LAYER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_LAYER, GimpLayerClass))
+
+
+typedef struct _GimpLayerClass GimpLayerClass;
+
+struct _GimpLayer
+{
+ GimpDrawable parent_instance;
+
+ gdouble opacity; /* layer opacity */
+ GimpLayerMode mode; /* layer combination mode */
+ GimpLayerColorSpace blend_space; /* layer blend space */
+ GimpLayerColorSpace composite_space; /* layer composite space */
+ GimpLayerCompositeMode composite_mode; /* layer composite mode */
+ GimpLayerMode effective_mode; /* layer effective combination mode */
+ GimpLayerColorSpace effective_blend_space; /* layer effective blend space */
+ GimpLayerColorSpace effective_composite_space; /* layer effective composite space */
+ GimpLayerCompositeMode effective_composite_mode; /* layer effective composite mode */
+ gboolean excludes_backdrop; /* layer clips backdrop */
+ gboolean lock_alpha; /* lock the alpha channel */
+
+ GimpLayerMask *mask; /* possible layer mask */
+ gboolean apply_mask; /* controls mask application */
+ gboolean edit_mask; /* edit mask or layer? */
+ gboolean show_mask; /* show mask or layer? */
+
+ GSList *move_stack; /* ancestors affected by move */
+
+ GeglNode *layer_offset_node;
+ GeglNode *mask_offset_node;
+
+ /* Floating selections */
+ struct
+ {
+ GimpDrawable *drawable; /* floating sel is attached to */
+ gboolean boundary_known; /* is the current boundary valid */
+ GimpBoundSeg *segs; /* boundary of floating sel */
+ gint num_segs; /* number of segs in boundary */
+ } fs;
+};
+
+struct _GimpLayerClass
+{
+ GimpDrawableClass parent_class;
+
+ /* signals */
+ void (* opacity_changed) (GimpLayer *layer);
+ void (* mode_changed) (GimpLayer *layer);
+ void (* blend_space_changed) (GimpLayer *layer);
+ void (* composite_space_changed) (GimpLayer *layer);
+ void (* composite_mode_changed) (GimpLayer *layer);
+ void (* effective_mode_changed) (GimpLayer *layer);
+ void (* excludes_backdrop_changed) (GimpLayer *layer);
+ void (* lock_alpha_changed) (GimpLayer *layer);
+ void (* mask_changed) (GimpLayer *layer);
+ void (* apply_mask_changed) (GimpLayer *layer);
+ void (* edit_mask_changed) (GimpLayer *layer);
+ void (* show_mask_changed) (GimpLayer *layer);
+
+ /* virtual functions */
+ void (* translate) (GimpLayer *layer,
+ gint offset_x,
+ gint offset_y);
+ void (* scale) (GimpLayer *layer,
+ gint new_width,
+ gint new_height,
+ gint new_offset_x,
+ gint new_offset_y,
+ GimpInterpolationType interpolation_type,
+ GimpProgress *progress);
+ void (* resize) (GimpLayer *layer,
+ GimpContext *context,
+ GimpFillType fill_type,
+ gint new_width,
+ gint new_height,
+ gint offset_x,
+ gint offset_y);
+ void (* flip) (GimpLayer *layer,
+ GimpContext *context,
+ GimpOrientationType flip_type,
+ gdouble axis,
+ gboolean clip_result);
+ void (* rotate) (GimpLayer *layer,
+ GimpContext *context,
+ GimpRotationType rotate_type,
+ gdouble center_x,
+ gdouble center_y,
+ gboolean clip_result);
+ void (* transform) (GimpLayer *layer,
+ GimpContext *context,
+ const GimpMatrix3 *matrix,
+ GimpTransformDirection direction,
+ GimpInterpolationType interpolation_type,
+ GimpTransformResize clip_result,
+ GimpProgress *progress);
+ void (* convert_type) (GimpLayer *layer,
+ GimpImage *dest_image,
+ const Babl *new_format,
+ GimpColorProfile *dest_profile,
+ GeglDitherMethod layer_dither_type,
+ GeglDitherMethod mask_dither_type,
+ gboolean push_undo,
+ GimpProgress *progress);
+ GeglRectangle (* get_bounding_box) (GimpLayer *layer);
+ void (* get_effective_mode) (GimpLayer *layer,
+ GimpLayerMode *mode,
+ GimpLayerColorSpace *blend_space,
+ GimpLayerColorSpace *composite_space,
+ GimpLayerCompositeMode *composite_mode);
+ gboolean (* get_excludes_backdrop) (GimpLayer *layer);
+};
+
+
+/* function declarations */
+
+GType gimp_layer_get_type (void) G_GNUC_CONST;
+
+GimpLayer * gimp_layer_get_parent (GimpLayer *layer);
+
+GimpLayerMask * gimp_layer_get_mask (GimpLayer *layer);
+GimpLayerMask * gimp_layer_create_mask (GimpLayer *layer,
+ GimpAddMaskType mask_type,
+ GimpChannel *channel);
+GimpLayerMask * gimp_layer_add_mask (GimpLayer *layer,
+ GimpLayerMask *mask,
+ gboolean push_undo,
+ GError **error);
+void gimp_layer_apply_mask (GimpLayer *layer,
+ GimpMaskApplyMode mode,
+ gboolean push_undo);
+
+void gimp_layer_set_apply_mask (GimpLayer *layer,
+ gboolean apply,
+ gboolean push_undo);
+gboolean gimp_layer_get_apply_mask (GimpLayer *layer);
+
+void gimp_layer_set_edit_mask (GimpLayer *layer,
+ gboolean edit);
+gboolean gimp_layer_get_edit_mask (GimpLayer *layer);
+
+void gimp_layer_set_show_mask (GimpLayer *layer,
+ gboolean show,
+ gboolean push_undo);
+gboolean gimp_layer_get_show_mask (GimpLayer *layer);
+
+void gimp_layer_add_alpha (GimpLayer *layer);
+void gimp_layer_remove_alpha (GimpLayer *layer,
+ GimpContext *context);
+
+void gimp_layer_resize_to_image (GimpLayer *layer,
+ GimpContext *context,
+ GimpFillType fill_type);
+
+GimpDrawable * gimp_layer_get_floating_sel_drawable (GimpLayer *layer);
+void gimp_layer_set_floating_sel_drawable (GimpLayer *layer,
+ GimpDrawable *drawable);
+gboolean gimp_layer_is_floating_sel (GimpLayer *layer);
+
+void gimp_layer_set_opacity (GimpLayer *layer,
+ gdouble opacity,
+ gboolean push_undo);
+gdouble gimp_layer_get_opacity (GimpLayer *layer);
+
+void gimp_layer_set_mode (GimpLayer *layer,
+ GimpLayerMode mode,
+ gboolean push_undo);
+GimpLayerMode gimp_layer_get_mode (GimpLayer *layer);
+
+void gimp_layer_set_blend_space (GimpLayer *layer,
+ GimpLayerColorSpace blend_space,
+ gboolean push_undo);
+GimpLayerColorSpace
+ gimp_layer_get_blend_space (GimpLayer *layer);
+GimpLayerColorSpace
+ gimp_layer_get_real_blend_space (GimpLayer *layer);
+
+void gimp_layer_set_composite_space (GimpLayer *layer,
+ GimpLayerColorSpace composite_space,
+ gboolean push_undo);
+GimpLayerColorSpace
+ gimp_layer_get_composite_space (GimpLayer *layer);
+GimpLayerColorSpace
+ gimp_layer_get_real_composite_space (GimpLayer *layer);
+
+void gimp_layer_set_composite_mode (GimpLayer *layer,
+ GimpLayerCompositeMode composite_mode,
+ gboolean push_undo);
+GimpLayerCompositeMode
+ gimp_layer_get_composite_mode (GimpLayer *layer);
+GimpLayerCompositeMode
+ gimp_layer_get_real_composite_mode (GimpLayer *layer);
+
+void gimp_layer_get_effective_mode (GimpLayer *layer,
+ GimpLayerMode *mode,
+ GimpLayerColorSpace *blend_space,
+ GimpLayerColorSpace *composite_space,
+ GimpLayerCompositeMode *composite_mode);
+
+gboolean gimp_layer_get_excludes_backdrop (GimpLayer *layer);
+
+void gimp_layer_set_lock_alpha (GimpLayer *layer,
+ gboolean lock_alpha,
+ gboolean push_undo);
+gboolean gimp_layer_get_lock_alpha (GimpLayer *layer);
+gboolean gimp_layer_can_lock_alpha (GimpLayer *layer);
+
+
+/* protected */
+
+void gimp_layer_update_effective_mode (GimpLayer *layer);
+void gimp_layer_update_excludes_backdrop (GimpLayer *layer);
+
+
+#endif /* __GIMP_LAYER_H__ */
diff --git a/app/core/gimplayermask.c b/app/core/gimplayermask.c
new file mode 100644
index 0000000..ec0dbfb
--- /dev/null
+++ b/app/core/gimplayermask.c
@@ -0,0 +1,297 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "core-types.h"
+
+#include "gegl/gimp-babl.h"
+
+#include "gimperror.h"
+#include "gimpimage.h"
+#include "gimplayer.h"
+#include "gimplayermask.h"
+
+#include "gimp-intl.h"
+
+
+static void gimp_layer_mask_preview_freeze (GimpViewable *viewable);
+static void gimp_layer_mask_preview_thaw (GimpViewable *viewable);
+
+static gboolean gimp_layer_mask_is_attached (GimpItem *item);
+static gboolean gimp_layer_mask_is_content_locked (GimpItem *item);
+static gboolean gimp_layer_mask_is_position_locked (GimpItem *item);
+static GimpItemTree * gimp_layer_mask_get_tree (GimpItem *item);
+static GimpItem * gimp_layer_mask_duplicate (GimpItem *item,
+ GType new_type);
+static gboolean gimp_layer_mask_rename (GimpItem *item,
+ const gchar *new_name,
+ const gchar *undo_desc,
+ GError **error);
+
+static void gimp_layer_mask_bounding_box_changed (GimpDrawable *drawable);
+static void gimp_layer_mask_convert_type (GimpDrawable *drawable,
+ GimpImage *dest_image,
+ const Babl *new_format,
+ GimpColorProfile *dest_profile,
+ GeglDitherMethod layer_dither_type,
+ GeglDitherMethod mask_dither_type,
+ gboolean push_undo,
+ GimpProgress *progress);
+
+
+G_DEFINE_TYPE (GimpLayerMask, gimp_layer_mask, GIMP_TYPE_CHANNEL)
+
+#define parent_class gimp_layer_mask_parent_class
+
+
+static void
+gimp_layer_mask_class_init (GimpLayerMaskClass *klass)
+{
+ GimpViewableClass *viewable_class = GIMP_VIEWABLE_CLASS (klass);
+ GimpItemClass *item_class = GIMP_ITEM_CLASS (klass);
+ GimpDrawableClass *drawable_class = GIMP_DRAWABLE_CLASS (klass);
+
+ viewable_class->default_icon_name = "gimp-layer-mask";
+
+ viewable_class->preview_freeze = gimp_layer_mask_preview_freeze;
+ viewable_class->preview_thaw = gimp_layer_mask_preview_thaw;
+
+ item_class->is_attached = gimp_layer_mask_is_attached;
+ item_class->is_content_locked = gimp_layer_mask_is_content_locked;
+ item_class->is_position_locked = gimp_layer_mask_is_position_locked;
+ item_class->get_tree = gimp_layer_mask_get_tree;
+ item_class->duplicate = gimp_layer_mask_duplicate;
+ item_class->rename = gimp_layer_mask_rename;
+ item_class->translate_desc = C_("undo-type", "Move Layer Mask");
+ item_class->to_selection_desc = C_("undo-type", "Layer Mask to Selection");
+
+ drawable_class->bounding_box_changed = gimp_layer_mask_bounding_box_changed;
+ drawable_class->convert_type = gimp_layer_mask_convert_type;
+}
+
+static void
+gimp_layer_mask_init (GimpLayerMask *layer_mask)
+{
+ layer_mask->layer = NULL;
+}
+
+static void
+gimp_layer_mask_preview_freeze (GimpViewable *viewable)
+{
+ GimpLayerMask *mask = GIMP_LAYER_MASK (viewable);
+ GimpLayer *layer = gimp_layer_mask_get_layer (mask);
+
+ if (layer)
+ {
+ GimpViewable *parent = gimp_viewable_get_parent (GIMP_VIEWABLE (layer));
+
+ if (! parent && gimp_item_is_attached (GIMP_ITEM (layer)))
+ parent = GIMP_VIEWABLE (gimp_item_get_image (GIMP_ITEM (layer)));
+
+ if (parent)
+ gimp_viewable_preview_freeze (parent);
+ }
+}
+
+static void
+gimp_layer_mask_preview_thaw (GimpViewable *viewable)
+{
+ GimpLayerMask *mask = GIMP_LAYER_MASK (viewable);
+ GimpLayer *layer = gimp_layer_mask_get_layer (mask);
+
+ if (layer)
+ {
+ GimpViewable *parent = gimp_viewable_get_parent (GIMP_VIEWABLE (layer));
+
+ if (! parent && gimp_item_is_attached (GIMP_ITEM (layer)))
+ parent = GIMP_VIEWABLE (gimp_item_get_image (GIMP_ITEM (layer)));
+
+ if (parent)
+ gimp_viewable_preview_thaw (parent);
+ }
+}
+
+static gboolean
+gimp_layer_mask_is_content_locked (GimpItem *item)
+{
+ GimpLayerMask *mask = GIMP_LAYER_MASK (item);
+ GimpLayer *layer = gimp_layer_mask_get_layer (mask);
+
+ if (layer)
+ return gimp_item_is_content_locked (GIMP_ITEM (layer));
+
+ return FALSE;
+}
+
+static gboolean
+gimp_layer_mask_is_position_locked (GimpItem *item)
+{
+ GimpLayerMask *mask = GIMP_LAYER_MASK (item);
+ GimpLayer *layer = gimp_layer_mask_get_layer (mask);
+
+ if (layer)
+ return gimp_item_is_position_locked (GIMP_ITEM (layer));
+
+ return FALSE;
+}
+
+static gboolean
+gimp_layer_mask_is_attached (GimpItem *item)
+{
+ GimpLayerMask *mask = GIMP_LAYER_MASK (item);
+ GimpLayer *layer = gimp_layer_mask_get_layer (mask);
+
+ return (GIMP_IS_IMAGE (gimp_item_get_image (item)) &&
+ GIMP_IS_LAYER (layer) &&
+ gimp_layer_get_mask (layer) == mask &&
+ gimp_item_is_attached (GIMP_ITEM (layer)));
+}
+
+static GimpItemTree *
+gimp_layer_mask_get_tree (GimpItem *item)
+{
+ return NULL;
+}
+
+static GimpItem *
+gimp_layer_mask_duplicate (GimpItem *item,
+ GType new_type)
+{
+ GimpItem *new_item;
+
+ g_return_val_if_fail (g_type_is_a (new_type, GIMP_TYPE_DRAWABLE), NULL);
+
+ new_item = GIMP_ITEM_CLASS (parent_class)->duplicate (item, new_type);
+
+ return new_item;
+}
+
+static gboolean
+gimp_layer_mask_rename (GimpItem *item,
+ const gchar *new_name,
+ const gchar *undo_desc,
+ GError **error)
+{
+ /* reject renaming, layer masks are always named "<layer name> mask" */
+
+ g_set_error (error, GIMP_ERROR, GIMP_FAILED,
+ _("Cannot rename layer masks."));
+
+ return FALSE;
+}
+
+static void
+gimp_layer_mask_bounding_box_changed (GimpDrawable *drawable)
+{
+ GimpLayerMask *mask = GIMP_LAYER_MASK (drawable);
+ GimpLayer *layer = gimp_layer_mask_get_layer (mask);
+
+ if (GIMP_DRAWABLE_CLASS (parent_class)->bounding_box_changed)
+ GIMP_DRAWABLE_CLASS (parent_class)->bounding_box_changed (drawable);
+
+ if (layer)
+ gimp_drawable_update_bounding_box (GIMP_DRAWABLE (layer));
+}
+
+static void
+gimp_layer_mask_convert_type (GimpDrawable *drawable,
+ GimpImage *dest_image,
+ const Babl *new_format,
+ GimpColorProfile *dest_profile,
+ GeglDitherMethod layer_dither_type,
+ GeglDitherMethod mask_dither_type,
+ gboolean push_undo,
+ GimpProgress *progress)
+{
+ new_format =
+ gimp_babl_mask_format (gimp_babl_format_get_precision (new_format));
+
+ GIMP_DRAWABLE_CLASS (parent_class)->convert_type (drawable, dest_image,
+ new_format,
+ dest_profile,
+ layer_dither_type,
+ mask_dither_type,
+ push_undo,
+ progress);
+}
+
+GimpLayerMask *
+gimp_layer_mask_new (GimpImage *image,
+ gint width,
+ gint height,
+ const gchar *name,
+ const GimpRGB *color)
+{
+ GimpLayerMask *layer_mask;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+ g_return_val_if_fail (width > 0, NULL);
+ g_return_val_if_fail (height > 0, NULL);
+ g_return_val_if_fail (color != NULL, NULL);
+
+ layer_mask =
+ GIMP_LAYER_MASK (gimp_drawable_new (GIMP_TYPE_LAYER_MASK,
+ image, name,
+ 0, 0, width, height,
+ gimp_image_get_mask_format (image)));
+
+ /* set the layer_mask color and opacity */
+ gimp_channel_set_color (GIMP_CHANNEL (layer_mask), color, FALSE);
+ gimp_channel_set_show_masked (GIMP_CHANNEL (layer_mask), TRUE);
+
+ /* selection mask variables */
+ GIMP_CHANNEL (layer_mask)->x2 = width;
+ GIMP_CHANNEL (layer_mask)->y2 = height;
+
+ return layer_mask;
+}
+
+void
+gimp_layer_mask_set_layer (GimpLayerMask *layer_mask,
+ GimpLayer *layer)
+{
+ g_return_if_fail (GIMP_IS_LAYER_MASK (layer_mask));
+ g_return_if_fail (layer == NULL || GIMP_IS_LAYER (layer));
+
+ layer_mask->layer = layer;
+
+ if (layer)
+ {
+ gchar *mask_name;
+ gint offset_x;
+ gint offset_y;
+
+ gimp_item_get_offset (GIMP_ITEM (layer), &offset_x, &offset_y);
+ gimp_item_set_offset (GIMP_ITEM (layer_mask), offset_x, offset_y);
+
+ mask_name = g_strdup_printf (_("%s mask"), gimp_object_get_name (layer));
+
+ gimp_object_take_name (GIMP_OBJECT (layer_mask), mask_name);
+ }
+}
+
+GimpLayer *
+gimp_layer_mask_get_layer (GimpLayerMask *layer_mask)
+{
+ g_return_val_if_fail (GIMP_IS_LAYER_MASK (layer_mask), NULL);
+
+ return layer_mask->layer;
+}
diff --git a/app/core/gimplayermask.h b/app/core/gimplayermask.h
new file mode 100644
index 0000000..5ee77fd
--- /dev/null
+++ b/app/core/gimplayermask.h
@@ -0,0 +1,61 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_LAYER_MASK_H__
+#define __GIMP_LAYER_MASK_H__
+
+
+#include "gimpchannel.h"
+
+
+#define GIMP_TYPE_LAYER_MASK (gimp_layer_mask_get_type ())
+#define GIMP_LAYER_MASK(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_LAYER_MASK, GimpLayerMask))
+#define GIMP_LAYER_MASK_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_LAYER_MASK, GimpLayerMaskClass))
+#define GIMP_IS_LAYER_MASK(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_LAYER_MASK))
+#define GIMP_IS_LAYER_MASK_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_LAYER_MASK))
+#define GIMP_LAYER_MASK_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_LAYER_MASK, GimpLayerMaskClass))
+
+
+typedef struct _GimpLayerMaskClass GimpLayerMaskClass;
+
+struct _GimpLayerMask
+{
+ GimpChannel parent_instance;
+
+ GimpLayer *layer;
+};
+
+struct _GimpLayerMaskClass
+{
+ GimpChannelClass parent_class;
+};
+
+
+GType gimp_layer_mask_get_type (void) G_GNUC_CONST;
+
+GimpLayerMask * gimp_layer_mask_new (GimpImage *image,
+ gint width,
+ gint height,
+ const gchar *name,
+ const GimpRGB *color);
+
+void gimp_layer_mask_set_layer (GimpLayerMask *layer_mask,
+ GimpLayer *layer);
+GimpLayer * gimp_layer_mask_get_layer (GimpLayerMask *layer_mask);
+
+
+#endif /* __GIMP_LAYER_MASK_H__ */
diff --git a/app/core/gimplayermaskpropundo.c b/app/core/gimplayermaskpropundo.c
new file mode 100644
index 0000000..7913202
--- /dev/null
+++ b/app/core/gimplayermaskpropundo.c
@@ -0,0 +1,122 @@
+/* Gimp - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "core-types.h"
+
+#include "gimplayer.h"
+#include "gimplayermaskpropundo.h"
+
+
+static void gimp_layer_mask_prop_undo_constructed (GObject *object);
+
+static void gimp_layer_mask_prop_undo_pop (GimpUndo *undo,
+ GimpUndoMode undo_mode,
+ GimpUndoAccumulator *accum);
+
+
+G_DEFINE_TYPE (GimpLayerMaskPropUndo, gimp_layer_mask_prop_undo,
+ GIMP_TYPE_ITEM_UNDO)
+
+#define parent_class gimp_layer_mask_prop_undo_parent_class
+
+
+static void
+gimp_layer_mask_prop_undo_class_init (GimpLayerMaskPropUndoClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpUndoClass *undo_class = GIMP_UNDO_CLASS (klass);
+
+ object_class->constructed = gimp_layer_mask_prop_undo_constructed;
+
+ undo_class->pop = gimp_layer_mask_prop_undo_pop;
+}
+
+static void
+gimp_layer_mask_prop_undo_init (GimpLayerMaskPropUndo *undo)
+{
+}
+
+static void
+gimp_layer_mask_prop_undo_constructed (GObject *object)
+{
+ GimpLayerMaskPropUndo *layer_mask_prop_undo;
+ GimpLayer *layer;
+
+ layer_mask_prop_undo = GIMP_LAYER_MASK_PROP_UNDO (object);
+
+ G_OBJECT_CLASS (parent_class)->constructed (object);
+
+ gimp_assert (GIMP_IS_LAYER (GIMP_ITEM_UNDO (object)->item));
+
+ layer = GIMP_LAYER (GIMP_ITEM_UNDO (object)->item);
+
+ switch (GIMP_UNDO (object)->undo_type)
+ {
+ case GIMP_UNDO_LAYER_MASK_APPLY:
+ layer_mask_prop_undo->apply = gimp_layer_get_apply_mask (layer);
+ break;
+
+ case GIMP_UNDO_LAYER_MASK_SHOW:
+ layer_mask_prop_undo->show = gimp_layer_get_show_mask (layer);
+ break;
+
+ default:
+ g_return_if_reached ();
+ }
+}
+
+static void
+gimp_layer_mask_prop_undo_pop (GimpUndo *undo,
+ GimpUndoMode undo_mode,
+ GimpUndoAccumulator *accum)
+{
+ GimpLayerMaskPropUndo *layer_mask_prop_undo = GIMP_LAYER_MASK_PROP_UNDO (undo);
+ GimpLayer *layer = GIMP_LAYER (GIMP_ITEM_UNDO (undo)->item);
+
+ GIMP_UNDO_CLASS (parent_class)->pop (undo, undo_mode, accum);
+
+ switch (undo->undo_type)
+ {
+ case GIMP_UNDO_LAYER_MASK_APPLY:
+ {
+ gboolean apply;
+
+ apply = gimp_layer_get_apply_mask (layer);
+ gimp_layer_set_apply_mask (layer, layer_mask_prop_undo->apply, FALSE);
+ layer_mask_prop_undo->apply = apply;
+ }
+ break;
+
+ case GIMP_UNDO_LAYER_MASK_SHOW:
+ {
+ gboolean show;
+
+ show = gimp_layer_get_show_mask (layer);
+ gimp_layer_set_show_mask (layer, layer_mask_prop_undo->show, FALSE);
+ layer_mask_prop_undo->show = show;
+ }
+ break;
+
+ default:
+ g_return_if_reached ();
+ }
+}
diff --git a/app/core/gimplayermaskpropundo.h b/app/core/gimplayermaskpropundo.h
new file mode 100644
index 0000000..a416e43
--- /dev/null
+++ b/app/core/gimplayermaskpropundo.h
@@ -0,0 +1,53 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_LAYER_MASK_PROP_UNDO_H__
+#define __GIMP_LAYER_MASK_PROP_UNDO_H__
+
+
+#include "gimpitemundo.h"
+
+
+#define GIMP_TYPE_LAYER_MASK_PROP_UNDO (gimp_layer_mask_prop_undo_get_type ())
+#define GIMP_LAYER_MASK_PROP_UNDO(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_LAYER_MASK_PROP_UNDO, GimpLayerMaskPropUndo))
+#define GIMP_LAYER_MASK_PROP_UNDO_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_LAYER_MASK_PROP_UNDO, GimpLayerMaskPropUndoClass))
+#define GIMP_IS_LAYER_MASK_PROP_UNDO(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_LAYER_MASK_PROP_UNDO))
+#define GIMP_IS_LAYER_MASK_PROP_UNDO_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_LAYER_MASK_PROP_UNDO))
+#define GIMP_LAYER_MASK_PROP_UNDO_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_LAYER_MASK_PROP_UNDO, GimpLayerMaskPropUndoClass))
+
+
+typedef struct _GimpLayerMaskPropUndo GimpLayerMaskPropUndo;
+typedef struct _GimpLayerMaskPropUndoClass GimpLayerMaskPropUndoClass;
+
+struct _GimpLayerMaskPropUndo
+{
+ GimpItemUndo parent_instance;
+
+ gboolean apply;
+ gboolean show;
+};
+
+struct _GimpLayerMaskPropUndoClass
+{
+ GimpItemUndoClass parent_class;
+};
+
+
+GType gimp_layer_mask_prop_undo_get_type (void) G_GNUC_CONST;
+
+
+#endif /* __GIMP_LAYER_MASK_PROP_UNDO_H__ */
diff --git a/app/core/gimplayermaskundo.c b/app/core/gimplayermaskundo.c
new file mode 100644
index 0000000..cc49eb7
--- /dev/null
+++ b/app/core/gimplayermaskundo.c
@@ -0,0 +1,195 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "core-types.h"
+
+#include "gimpimage.h"
+#include "gimplayer.h"
+#include "gimplayermask.h"
+#include "gimplayermaskundo.h"
+
+
+enum
+{
+ PROP_0,
+ PROP_LAYER_MASK
+};
+
+
+static void gimp_layer_mask_undo_constructed (GObject *object);
+static void gimp_layer_mask_undo_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_layer_mask_undo_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static gint64 gimp_layer_mask_undo_get_memsize (GimpObject *object,
+ gint64 *gui_size);
+
+static void gimp_layer_mask_undo_pop (GimpUndo *undo,
+ GimpUndoMode undo_mode,
+ GimpUndoAccumulator *accum);
+static void gimp_layer_mask_undo_free (GimpUndo *undo,
+ GimpUndoMode undo_mode);
+
+
+G_DEFINE_TYPE (GimpLayerMaskUndo, gimp_layer_mask_undo, GIMP_TYPE_ITEM_UNDO)
+
+#define parent_class gimp_layer_mask_undo_parent_class
+
+
+static void
+gimp_layer_mask_undo_class_init (GimpLayerMaskUndoClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpObjectClass *gimp_object_class = GIMP_OBJECT_CLASS (klass);
+ GimpUndoClass *undo_class = GIMP_UNDO_CLASS (klass);
+
+ object_class->constructed = gimp_layer_mask_undo_constructed;
+ object_class->set_property = gimp_layer_mask_undo_set_property;
+ object_class->get_property = gimp_layer_mask_undo_get_property;
+
+ gimp_object_class->get_memsize = gimp_layer_mask_undo_get_memsize;
+
+ undo_class->pop = gimp_layer_mask_undo_pop;
+ undo_class->free = gimp_layer_mask_undo_free;
+
+ g_object_class_install_property (object_class, PROP_LAYER_MASK,
+ g_param_spec_object ("layer-mask", NULL, NULL,
+ GIMP_TYPE_LAYER_MASK,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+}
+
+static void
+gimp_layer_mask_undo_init (GimpLayerMaskUndo *undo)
+{
+}
+
+static void
+gimp_layer_mask_undo_constructed (GObject *object)
+{
+ GimpLayerMaskUndo *layer_mask_undo = GIMP_LAYER_MASK_UNDO (object);
+
+ G_OBJECT_CLASS (parent_class)->constructed (object);
+
+ gimp_assert (GIMP_IS_LAYER (GIMP_ITEM_UNDO (object)->item));
+ gimp_assert (GIMP_IS_LAYER_MASK (layer_mask_undo->layer_mask));
+}
+
+static void
+gimp_layer_mask_undo_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpLayerMaskUndo *layer_mask_undo = GIMP_LAYER_MASK_UNDO (object);
+
+ switch (property_id)
+ {
+ case PROP_LAYER_MASK:
+ layer_mask_undo->layer_mask = g_value_dup_object (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_layer_mask_undo_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpLayerMaskUndo *layer_mask_undo = GIMP_LAYER_MASK_UNDO (object);
+
+ switch (property_id)
+ {
+ case PROP_LAYER_MASK:
+ g_value_set_object (value, layer_mask_undo->layer_mask);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static gint64
+gimp_layer_mask_undo_get_memsize (GimpObject *object,
+ gint64 *gui_size)
+{
+ GimpLayerMaskUndo *layer_mask_undo = GIMP_LAYER_MASK_UNDO (object);
+ GimpLayer *layer = GIMP_LAYER (GIMP_ITEM_UNDO (object)->item);
+ gint64 memsize = 0;
+
+ /* don't use !gimp_item_is_attached() here */
+ if (gimp_layer_get_mask (layer) != layer_mask_undo->layer_mask)
+ memsize += gimp_object_get_memsize (GIMP_OBJECT (layer_mask_undo->layer_mask),
+ gui_size);
+
+ return memsize + GIMP_OBJECT_CLASS (parent_class)->get_memsize (object,
+ gui_size);
+}
+
+static void
+gimp_layer_mask_undo_pop (GimpUndo *undo,
+ GimpUndoMode undo_mode,
+ GimpUndoAccumulator *accum)
+{
+ GimpLayerMaskUndo *layer_mask_undo = GIMP_LAYER_MASK_UNDO (undo);
+ GimpLayer *layer = GIMP_LAYER (GIMP_ITEM_UNDO (undo)->item);
+
+ GIMP_UNDO_CLASS (parent_class)->pop (undo, undo_mode, accum);
+
+ if ((undo_mode == GIMP_UNDO_MODE_UNDO &&
+ undo->undo_type == GIMP_UNDO_LAYER_MASK_ADD) ||
+ (undo_mode == GIMP_UNDO_MODE_REDO &&
+ undo->undo_type == GIMP_UNDO_LAYER_MASK_REMOVE))
+ {
+ /* remove layer mask */
+
+ gimp_layer_apply_mask (layer, GIMP_MASK_DISCARD, FALSE);
+ }
+ else
+ {
+ /* restore layer mask */
+
+ gimp_layer_add_mask (layer, layer_mask_undo->layer_mask, FALSE, NULL);
+ }
+}
+
+static void
+gimp_layer_mask_undo_free (GimpUndo *undo,
+ GimpUndoMode undo_mode)
+{
+ GimpLayerMaskUndo *layer_mask_undo = GIMP_LAYER_MASK_UNDO (undo);
+
+ g_clear_object (&layer_mask_undo->layer_mask);
+
+ GIMP_UNDO_CLASS (parent_class)->free (undo, undo_mode);
+}
diff --git a/app/core/gimplayermaskundo.h b/app/core/gimplayermaskundo.h
new file mode 100644
index 0000000..c8591c1
--- /dev/null
+++ b/app/core/gimplayermaskundo.h
@@ -0,0 +1,52 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_LAYER_MASK_UNDO_H__
+#define __GIMP_LAYER_MASK_UNDO_H__
+
+
+#include "gimpitemundo.h"
+
+
+#define GIMP_TYPE_LAYER_MASK_UNDO (gimp_layer_mask_undo_get_type ())
+#define GIMP_LAYER_MASK_UNDO(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_LAYER_MASK_UNDO, GimpLayerMaskUndo))
+#define GIMP_LAYER_MASK_UNDO_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_LAYER_MASK_UNDO, GimpLayerMaskUndoClass))
+#define GIMP_IS_LAYER_MASK_UNDO(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_LAYER_MASK_UNDO))
+#define GIMP_IS_LAYER_MASK_UNDO_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_LAYER_MASK_UNDO))
+#define GIMP_LAYER_MASK_UNDO_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_LAYER_MASK_UNDO, GimpLayerMaskUndoClass))
+
+
+typedef struct _GimpLayerMaskUndo GimpLayerMaskUndo;
+typedef struct _GimpLayerMaskUndoClass GimpLayerMaskUndoClass;
+
+struct _GimpLayerMaskUndo
+{
+ GimpItemUndo parent_instance;
+
+ GimpLayerMask *layer_mask;
+};
+
+struct _GimpLayerMaskUndoClass
+{
+ GimpItemUndoClass parent_class;
+};
+
+
+GType gimp_layer_mask_undo_get_type (void) G_GNUC_CONST;
+
+
+#endif /* __GIMP_LAYER_MASK_UNDO_H__ */
diff --git a/app/core/gimplayerpropundo.c b/app/core/gimplayerpropundo.c
new file mode 100644
index 0000000..73e3e02
--- /dev/null
+++ b/app/core/gimplayerpropundo.c
@@ -0,0 +1,157 @@
+/* Gimp - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpbase/gimpbase.h"
+
+#include "core-types.h"
+
+#include "gimpimage.h"
+#include "gimplayer.h"
+#include "gimplayerpropundo.h"
+
+
+static void gimp_layer_prop_undo_constructed (GObject *object);
+
+static void gimp_layer_prop_undo_pop (GimpUndo *undo,
+ GimpUndoMode undo_mode,
+ GimpUndoAccumulator *accum);
+
+
+G_DEFINE_TYPE (GimpLayerPropUndo, gimp_layer_prop_undo, GIMP_TYPE_ITEM_UNDO)
+
+#define parent_class gimp_layer_prop_undo_parent_class
+
+
+static void
+gimp_layer_prop_undo_class_init (GimpLayerPropUndoClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpUndoClass *undo_class = GIMP_UNDO_CLASS (klass);
+
+ object_class->constructed = gimp_layer_prop_undo_constructed;
+
+ undo_class->pop = gimp_layer_prop_undo_pop;
+}
+
+static void
+gimp_layer_prop_undo_init (GimpLayerPropUndo *undo)
+{
+}
+
+static void
+gimp_layer_prop_undo_constructed (GObject *object)
+{
+ GimpLayerPropUndo *layer_prop_undo = GIMP_LAYER_PROP_UNDO (object);
+ GimpLayer *layer;
+
+ G_OBJECT_CLASS (parent_class)->constructed (object);
+
+ gimp_assert (GIMP_IS_LAYER (GIMP_ITEM_UNDO (object)->item));
+
+ layer = GIMP_LAYER (GIMP_ITEM_UNDO (object)->item);
+
+ switch (GIMP_UNDO (object)->undo_type)
+ {
+ case GIMP_UNDO_LAYER_MODE:
+ layer_prop_undo->mode = gimp_layer_get_mode (layer);
+ layer_prop_undo->blend_space = gimp_layer_get_blend_space (layer);
+ layer_prop_undo->composite_space = gimp_layer_get_composite_space (layer);
+ layer_prop_undo->composite_mode = gimp_layer_get_composite_mode (layer);
+ break;
+
+ case GIMP_UNDO_LAYER_OPACITY:
+ layer_prop_undo->opacity = gimp_layer_get_opacity (layer);
+ break;
+
+ case GIMP_UNDO_LAYER_LOCK_ALPHA:
+ layer_prop_undo->lock_alpha = gimp_layer_get_lock_alpha (layer);
+ break;
+
+ default:
+ g_return_if_reached ();
+ }
+}
+
+static void
+gimp_layer_prop_undo_pop (GimpUndo *undo,
+ GimpUndoMode undo_mode,
+ GimpUndoAccumulator *accum)
+{
+ GimpLayerPropUndo *layer_prop_undo = GIMP_LAYER_PROP_UNDO (undo);
+ GimpLayer *layer = GIMP_LAYER (GIMP_ITEM_UNDO (undo)->item);
+
+ GIMP_UNDO_CLASS (parent_class)->pop (undo, undo_mode, accum);
+
+ switch (undo->undo_type)
+ {
+ case GIMP_UNDO_LAYER_MODE:
+ {
+ GimpLayerMode mode;
+ GimpLayerColorSpace blend_space;
+ GimpLayerColorSpace composite_space;
+ GimpLayerCompositeMode composite_mode;
+
+ mode = gimp_layer_get_mode (layer);
+ blend_space = gimp_layer_get_blend_space (layer);
+ composite_space = gimp_layer_get_composite_space (layer);
+ composite_mode = gimp_layer_get_composite_mode (layer);
+
+ gimp_layer_set_mode (layer, layer_prop_undo->mode,
+ FALSE);
+ gimp_layer_set_blend_space (layer, layer_prop_undo->blend_space,
+ FALSE);
+ gimp_layer_set_composite_space (layer, layer_prop_undo->composite_space,
+ FALSE);
+ gimp_layer_set_composite_mode (layer, layer_prop_undo->composite_mode,
+ FALSE);
+
+ layer_prop_undo->mode = mode;
+ layer_prop_undo->blend_space = blend_space;
+ layer_prop_undo->composite_space = composite_space;
+ layer_prop_undo->composite_mode = composite_mode;
+ }
+ break;
+
+ case GIMP_UNDO_LAYER_OPACITY:
+ {
+ gdouble opacity;
+
+ opacity = gimp_layer_get_opacity (layer);
+ gimp_layer_set_opacity (layer, layer_prop_undo->opacity, FALSE);
+ layer_prop_undo->opacity = opacity;
+ }
+ break;
+
+ case GIMP_UNDO_LAYER_LOCK_ALPHA:
+ {
+ gboolean lock_alpha;
+
+ lock_alpha = gimp_layer_get_lock_alpha (layer);
+ gimp_layer_set_lock_alpha (layer, layer_prop_undo->lock_alpha, FALSE);
+ layer_prop_undo->lock_alpha = lock_alpha;
+ }
+ break;
+
+ default:
+ g_return_if_reached ();
+ }
+}
diff --git a/app/core/gimplayerpropundo.h b/app/core/gimplayerpropundo.h
new file mode 100644
index 0000000..5a3b478
--- /dev/null
+++ b/app/core/gimplayerpropundo.h
@@ -0,0 +1,57 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_LAYER_PROP_UNDO_H__
+#define __GIMP_LAYER_PROP_UNDO_H__
+
+
+#include "gimpitemundo.h"
+
+
+#define GIMP_TYPE_LAYER_PROP_UNDO (gimp_layer_prop_undo_get_type ())
+#define GIMP_LAYER_PROP_UNDO(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_LAYER_PROP_UNDO, GimpLayerPropUndo))
+#define GIMP_LAYER_PROP_UNDO_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_LAYER_PROP_UNDO, GimpLayerPropUndoClass))
+#define GIMP_IS_LAYER_PROP_UNDO(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_LAYER_PROP_UNDO))
+#define GIMP_IS_LAYER_PROP_UNDO_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_LAYER_PROP_UNDO))
+#define GIMP_LAYER_PROP_UNDO_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_LAYER_PROP_UNDO, GimpLayerPropUndoClass))
+
+
+typedef struct _GimpLayerPropUndo GimpLayerPropUndo;
+typedef struct _GimpLayerPropUndoClass GimpLayerPropUndoClass;
+
+struct _GimpLayerPropUndo
+{
+ GimpItemUndo parent_instance;
+
+ GimpLayerMode mode;
+ GimpLayerColorSpace blend_space;
+ GimpLayerColorSpace composite_space;
+ GimpLayerCompositeMode composite_mode;
+ gdouble opacity;
+ gboolean lock_alpha;
+};
+
+struct _GimpLayerPropUndoClass
+{
+ GimpItemUndoClass parent_class;
+};
+
+
+GType gimp_layer_prop_undo_get_type (void) G_GNUC_CONST;
+
+
+#endif /* __GIMP_LAYER_PROP_UNDO_H__ */
diff --git a/app/core/gimplayerstack.c b/app/core/gimplayerstack.c
new file mode 100644
index 0000000..1540189
--- /dev/null
+++ b/app/core/gimplayerstack.c
@@ -0,0 +1,243 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-1997 Spencer Kimball and Peter Mattis
+ *
+ * gimplayerstack.c
+ * Copyright (C) 2017 Ell
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "core-types.h"
+
+#include "gimplayer.h"
+#include "gimplayerstack.h"
+
+
+/* local function prototypes */
+
+static void gimp_layer_stack_constructed (GObject *object);
+
+static void gimp_layer_stack_add (GimpContainer *container,
+ GimpObject *object);
+static void gimp_layer_stack_remove (GimpContainer *container,
+ GimpObject *object);
+static void gimp_layer_stack_reorder (GimpContainer *container,
+ GimpObject *object,
+ gint new_index);
+
+static void gimp_layer_stack_layer_active (GimpLayer *layer,
+ GimpLayerStack *stack);
+static void gimp_layer_stack_layer_excludes_backdrop (GimpLayer *layer,
+ GimpLayerStack *stack);
+
+static void gimp_layer_stack_update_backdrop (GimpLayerStack *stack,
+ GimpLayer *layer,
+ gboolean ignore_active,
+ gboolean ignore_excludes_backdrop);
+static void gimp_layer_stack_update_range (GimpLayerStack *stack,
+ gint first,
+ gint last);
+
+
+G_DEFINE_TYPE (GimpLayerStack, gimp_layer_stack, GIMP_TYPE_DRAWABLE_STACK)
+
+#define parent_class gimp_layer_stack_parent_class
+
+
+static void
+gimp_layer_stack_class_init (GimpLayerStackClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpContainerClass *container_class = GIMP_CONTAINER_CLASS (klass);
+
+ object_class->constructed = gimp_layer_stack_constructed;
+
+ container_class->add = gimp_layer_stack_add;
+ container_class->remove = gimp_layer_stack_remove;
+ container_class->reorder = gimp_layer_stack_reorder;
+}
+
+static void
+gimp_layer_stack_init (GimpLayerStack *stack)
+{
+}
+
+static void
+gimp_layer_stack_constructed (GObject *object)
+{
+ GimpContainer *container = GIMP_CONTAINER (object);
+
+ G_OBJECT_CLASS (parent_class)->constructed (object);
+
+ gimp_assert (g_type_is_a (gimp_container_get_children_type (container),
+ GIMP_TYPE_LAYER));
+
+ gimp_container_add_handler (container, "active-changed",
+ G_CALLBACK (gimp_layer_stack_layer_active),
+ container);
+ gimp_container_add_handler (container, "excludes-backdrop-changed",
+ G_CALLBACK (gimp_layer_stack_layer_excludes_backdrop),
+ container);
+}
+
+static void
+gimp_layer_stack_add (GimpContainer *container,
+ GimpObject *object)
+{
+ GimpLayerStack *stack = GIMP_LAYER_STACK (container);
+
+ GIMP_CONTAINER_CLASS (parent_class)->add (container, object);
+
+ gimp_layer_stack_update_backdrop (stack, GIMP_LAYER (object), FALSE, FALSE);
+}
+
+static void
+gimp_layer_stack_remove (GimpContainer *container,
+ GimpObject *object)
+{
+ GimpLayerStack *stack = GIMP_LAYER_STACK (container);
+ gboolean update_backdrop;
+ gint index;
+
+ update_backdrop = gimp_filter_get_active (GIMP_FILTER (object)) &&
+ gimp_layer_get_excludes_backdrop (GIMP_LAYER (object));
+
+ if (update_backdrop)
+ index = gimp_container_get_child_index (container, object);
+
+ GIMP_CONTAINER_CLASS (parent_class)->remove (container, object);
+
+ if (update_backdrop)
+ gimp_layer_stack_update_range (stack, index, -1);
+}
+
+static void
+gimp_layer_stack_reorder (GimpContainer *container,
+ GimpObject *object,
+ gint new_index)
+{
+ GimpLayerStack *stack = GIMP_LAYER_STACK (container);
+ gboolean update_backdrop;
+ gint index;
+
+ update_backdrop = gimp_filter_get_active (GIMP_FILTER (object)) &&
+ gimp_layer_get_excludes_backdrop (GIMP_LAYER (object));
+
+ if (update_backdrop)
+ index = gimp_container_get_child_index (container, object);
+
+ GIMP_CONTAINER_CLASS (parent_class)->reorder (container, object, new_index);
+
+ if (update_backdrop)
+ gimp_layer_stack_update_range (stack, index, new_index);
+}
+
+
+/* public functions */
+
+GimpContainer *
+gimp_layer_stack_new (GType layer_type)
+{
+ g_return_val_if_fail (g_type_is_a (layer_type, GIMP_TYPE_LAYER), NULL);
+
+ return g_object_new (GIMP_TYPE_LAYER_STACK,
+ "name", g_type_name (layer_type),
+ "children-type", layer_type,
+ "policy", GIMP_CONTAINER_POLICY_STRONG,
+ NULL);
+}
+
+
+/* private functions */
+
+static void
+gimp_layer_stack_layer_active (GimpLayer *layer,
+ GimpLayerStack *stack)
+{
+ gimp_layer_stack_update_backdrop (stack, layer, TRUE, FALSE);
+}
+
+static void
+gimp_layer_stack_layer_excludes_backdrop (GimpLayer *layer,
+ GimpLayerStack *stack)
+{
+ gimp_layer_stack_update_backdrop (stack, layer, FALSE, TRUE);
+}
+
+static void
+gimp_layer_stack_update_backdrop (GimpLayerStack *stack,
+ GimpLayer *layer,
+ gboolean ignore_active,
+ gboolean ignore_excludes_backdrop)
+{
+ if ((ignore_active || gimp_filter_get_active (GIMP_FILTER (layer))) &&
+ (ignore_excludes_backdrop || gimp_layer_get_excludes_backdrop (layer)))
+ {
+ gint index;
+
+ index = gimp_container_get_child_index (GIMP_CONTAINER (stack),
+ GIMP_OBJECT (layer));
+
+ gimp_layer_stack_update_range (stack, index + 1, -1);
+ }
+}
+
+static void
+gimp_layer_stack_update_range (GimpLayerStack *stack,
+ gint first,
+ gint last)
+{
+ GList *iter;
+
+ g_return_if_fail (first >= 0 && last >= -1);
+
+ /* if the range is reversed, flip first and last; note that last == -1 is
+ * used to update all layers from first onward.
+ */
+ if (last >= 0 && last < first)
+ {
+ gint temp = first;
+
+ first = last + 1;
+ last = temp + 1;
+ }
+
+ iter = gimp_item_stack_get_item_iter (GIMP_ITEM_STACK (stack));
+
+ for (iter = g_list_nth (iter, first);
+ iter && first != last;
+ iter = g_list_next (iter), first++)
+ {
+ GimpItem *item = iter->data;
+
+ if (gimp_filter_get_active (GIMP_FILTER (item)))
+ {
+ GeglRectangle bounding_box;
+
+ bounding_box = gimp_drawable_get_bounding_box (GIMP_DRAWABLE (item));
+
+ bounding_box.x += gimp_item_get_offset_x (item);
+ bounding_box.y += gimp_item_get_offset_y (item);
+
+ gimp_drawable_stack_update (GIMP_DRAWABLE_STACK (stack),
+ bounding_box.x, bounding_box.y,
+ bounding_box.width, bounding_box.height);
+ }
+ }
+}
diff --git a/app/core/gimplayerstack.h b/app/core/gimplayerstack.h
new file mode 100644
index 0000000..598d21c
--- /dev/null
+++ b/app/core/gimplayerstack.h
@@ -0,0 +1,51 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-1997 Spencer Kimball and Peter Mattis
+ *
+ * gimplayerstack.h
+ * Copyright (C) 2017 Ell
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_LAYER_STACK_H__
+#define __GIMP_LAYER_STACK_H__
+
+#include "gimpdrawablestack.h"
+
+
+#define GIMP_TYPE_LAYER_STACK (gimp_layer_stack_get_type ())
+#define GIMP_LAYER_STACK(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_LAYER_STACK, GimpLayerStack))
+#define GIMP_LAYER_STACK_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_LAYER_STACK, GimpLayerStackClass))
+#define GIMP_IS_LAYER_STACK(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_LAYER_STACK))
+#define GIMP_IS_LAYER_STACK_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_LAYER_STACK))
+
+
+typedef struct _GimpLayerStackClass GimpLayerStackClass;
+
+struct _GimpLayerStack
+{
+ GimpDrawableStack parent_instance;
+};
+
+struct _GimpLayerStackClass
+{
+ GimpDrawableStackClass parent_class;
+};
+
+
+GType gimp_layer_stack_get_type (void) G_GNUC_CONST;
+GimpContainer * gimp_layer_stack_new (GType layer_type);
+
+
+#endif /* __GIMP_LAYER_STACK_H__ */
diff --git a/app/core/gimplayerundo.c b/app/core/gimplayerundo.c
new file mode 100644
index 0000000..64a1de3
--- /dev/null
+++ b/app/core/gimplayerundo.c
@@ -0,0 +1,212 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "core-types.h"
+
+#include "gimpimage.h"
+#include "gimplayer.h"
+#include "gimplayerundo.h"
+
+
+enum
+{
+ PROP_0,
+ PROP_PREV_PARENT,
+ PROP_PREV_POSITION,
+ PROP_PREV_LAYER
+};
+
+
+static void gimp_layer_undo_constructed (GObject *object);
+static void gimp_layer_undo_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_layer_undo_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static gint64 gimp_layer_undo_get_memsize (GimpObject *object,
+ gint64 *gui_size);
+
+static void gimp_layer_undo_pop (GimpUndo *undo,
+ GimpUndoMode undo_mode,
+ GimpUndoAccumulator *accum);
+
+
+G_DEFINE_TYPE (GimpLayerUndo, gimp_layer_undo, GIMP_TYPE_ITEM_UNDO)
+
+#define parent_class gimp_layer_undo_parent_class
+
+
+static void
+gimp_layer_undo_class_init (GimpLayerUndoClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpObjectClass *gimp_object_class = GIMP_OBJECT_CLASS (klass);
+ GimpUndoClass *undo_class = GIMP_UNDO_CLASS (klass);
+
+ object_class->constructed = gimp_layer_undo_constructed;
+ object_class->set_property = gimp_layer_undo_set_property;
+ object_class->get_property = gimp_layer_undo_get_property;
+
+ gimp_object_class->get_memsize = gimp_layer_undo_get_memsize;
+
+ undo_class->pop = gimp_layer_undo_pop;
+
+ g_object_class_install_property (object_class, PROP_PREV_PARENT,
+ g_param_spec_object ("prev-parent",
+ NULL, NULL,
+ GIMP_TYPE_LAYER,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+
+ g_object_class_install_property (object_class, PROP_PREV_POSITION,
+ g_param_spec_int ("prev-position", NULL, NULL,
+ 0, G_MAXINT, 0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+
+ g_object_class_install_property (object_class, PROP_PREV_LAYER,
+ g_param_spec_object ("prev-layer", NULL, NULL,
+ GIMP_TYPE_LAYER,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+}
+
+static void
+gimp_layer_undo_init (GimpLayerUndo *undo)
+{
+}
+
+static void
+gimp_layer_undo_constructed (GObject *object)
+{
+ G_OBJECT_CLASS (parent_class)->constructed (object);
+
+ gimp_assert (GIMP_IS_LAYER (GIMP_ITEM_UNDO (object)->item));
+}
+
+static void
+gimp_layer_undo_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpLayerUndo *layer_undo = GIMP_LAYER_UNDO (object);
+
+ switch (property_id)
+ {
+ case PROP_PREV_PARENT:
+ layer_undo->prev_parent = g_value_get_object (value);
+ break;
+ case PROP_PREV_POSITION:
+ layer_undo->prev_position = g_value_get_int (value);
+ break;
+ case PROP_PREV_LAYER:
+ layer_undo->prev_layer = g_value_get_object (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_layer_undo_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpLayerUndo *layer_undo = GIMP_LAYER_UNDO (object);
+
+ switch (property_id)
+ {
+ case PROP_PREV_PARENT:
+ g_value_set_object (value, layer_undo->prev_parent);
+ break;
+ case PROP_PREV_POSITION:
+ g_value_set_int (value, layer_undo->prev_position);
+ break;
+ case PROP_PREV_LAYER:
+ g_value_set_object (value, layer_undo->prev_layer);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static gint64
+gimp_layer_undo_get_memsize (GimpObject *object,
+ gint64 *gui_size)
+{
+ GimpItemUndo *item_undo = GIMP_ITEM_UNDO (object);
+ gint64 memsize = 0;
+
+ if (! gimp_item_is_attached (item_undo->item))
+ memsize += gimp_object_get_memsize (GIMP_OBJECT (item_undo->item),
+ gui_size);
+
+ return memsize + GIMP_OBJECT_CLASS (parent_class)->get_memsize (object,
+ gui_size);
+}
+
+static void
+gimp_layer_undo_pop (GimpUndo *undo,
+ GimpUndoMode undo_mode,
+ GimpUndoAccumulator *accum)
+{
+ GimpLayerUndo *layer_undo = GIMP_LAYER_UNDO (undo);
+ GimpLayer *layer = GIMP_LAYER (GIMP_ITEM_UNDO (undo)->item);
+
+ GIMP_UNDO_CLASS (parent_class)->pop (undo, undo_mode, accum);
+
+ if ((undo_mode == GIMP_UNDO_MODE_UNDO &&
+ undo->undo_type == GIMP_UNDO_LAYER_ADD) ||
+ (undo_mode == GIMP_UNDO_MODE_REDO &&
+ undo->undo_type == GIMP_UNDO_LAYER_REMOVE))
+ {
+ /* remove layer */
+
+ /* record the current parent and position */
+ layer_undo->prev_parent = gimp_layer_get_parent (layer);
+ layer_undo->prev_position = gimp_item_get_index (GIMP_ITEM (layer));
+
+ gimp_image_remove_layer (undo->image, layer, FALSE,
+ layer_undo->prev_layer);
+ }
+ else
+ {
+ /* restore layer */
+
+ /* record the active layer */
+ layer_undo->prev_layer = gimp_image_get_active_layer (undo->image);
+
+ gimp_image_add_layer (undo->image, layer,
+ layer_undo->prev_parent,
+ layer_undo->prev_position, FALSE);
+ }
+}
diff --git a/app/core/gimplayerundo.h b/app/core/gimplayerundo.h
new file mode 100644
index 0000000..700fce8
--- /dev/null
+++ b/app/core/gimplayerundo.h
@@ -0,0 +1,54 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_LAYER_UNDO_H__
+#define __GIMP_LAYER_UNDO_H__
+
+
+#include "gimpitemundo.h"
+
+
+#define GIMP_TYPE_LAYER_UNDO (gimp_layer_undo_get_type ())
+#define GIMP_LAYER_UNDO(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_LAYER_UNDO, GimpLayerUndo))
+#define GIMP_LAYER_UNDO_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_LAYER_UNDO, GimpLayerUndoClass))
+#define GIMP_IS_LAYER_UNDO(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_LAYER_UNDO))
+#define GIMP_IS_LAYER_UNDO_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_LAYER_UNDO))
+#define GIMP_LAYER_UNDO_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_LAYER_UNDO, GimpLayerUndoClass))
+
+
+typedef struct _GimpLayerUndo GimpLayerUndo;
+typedef struct _GimpLayerUndoClass GimpLayerUndoClass;
+
+struct _GimpLayerUndo
+{
+ GimpItemUndo parent_instance;
+
+ GimpLayer *prev_parent;
+ gint prev_position; /* former position in list */
+ GimpLayer *prev_layer; /* previous active layer */
+};
+
+struct _GimpLayerUndoClass
+{
+ GimpItemUndoClass parent_class;
+};
+
+
+GType gimp_layer_undo_get_type (void) G_GNUC_CONST;
+
+
+#endif /* __GIMP_LAYER_UNDO_H__ */
diff --git a/app/core/gimplineart.c b/app/core/gimplineart.c
new file mode 100644
index 0000000..bf8460c
--- /dev/null
+++ b/app/core/gimplineart.c
@@ -0,0 +1,2979 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * Copyright (C) 2017 Sébastien Fourey & David Tchumperlé
+ * Copyright (C) 2018 Jehan
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpmath/gimpmath.h"
+
+#include "core-types.h"
+
+#include "gegl/gimp-gegl-loops.h"
+#include "gegl/gimp-gegl-utils.h"
+
+#include "gimp-parallel.h"
+#include "gimp-priorities.h"
+#include "gimp-utils.h" /* GIMP_TIMER */
+#include "gimpasync.h"
+#include "gimpcancelable.h"
+#include "gimpdrawable.h"
+#include "gimpimage.h"
+#include "gimplineart.h"
+#include "gimpmarshal.h"
+#include "gimppickable.h"
+#include "gimpprojection.h"
+#include "gimpviewable.h"
+#include "gimpwaitable.h"
+
+#include "gimp-intl.h"
+
+enum
+{
+ COMPUTING_START,
+ COMPUTING_END,
+ LAST_SIGNAL,
+};
+
+enum
+{
+ PROP_0,
+ PROP_SELECT_TRANSPARENT,
+ PROP_MAX_GROW,
+ PROP_THRESHOLD,
+ PROP_SPLINE_MAX_LEN,
+ PROP_SEGMENT_MAX_LEN,
+};
+
+typedef struct _GimpLineArtPrivate GimpLineArtPrivate;
+
+struct _GimpLineArtPrivate
+{
+ gboolean frozen;
+ gboolean compute_after_thaw;
+
+ GimpAsync *async;
+
+ gint idle_id;
+
+ GimpPickable *input;
+ GeglBuffer *closed;
+ gfloat *distmap;
+
+ /* Used in the closing step. */
+ gboolean select_transparent;
+ gdouble threshold;
+ gint spline_max_len;
+ gint segment_max_len;
+ gboolean max_len_bound;
+
+ /* Used in the grow step. */
+ gint max_grow;
+};
+
+typedef struct
+{
+ GeglBuffer *buffer;
+
+ gboolean select_transparent;
+ gdouble threshold;
+ gint spline_max_len;
+ gint segment_max_len;
+} LineArtData;
+
+typedef struct
+{
+ GeglBuffer *closed;
+ gfloat *distmap;
+} LineArtResult;
+
+static int DeltaX[4] = {+1, -1, 0, 0};
+static int DeltaY[4] = {0, 0, +1, -1};
+
+static const GimpVector2 Direction2Normal[4] =
+{
+ { 1.0f, 0.0f },
+ { -1.0f, 0.0f },
+ { 0.0f, 1.0f },
+ { 0.0f, -1.0f }
+};
+
+typedef enum _Direction
+{
+ XPlusDirection = 0,
+ XMinusDirection = 1,
+ YPlusDirection = 2,
+ YMinusDirection = 3
+} Direction;
+
+typedef GimpVector2 Pixel;
+
+typedef struct _SplineCandidate
+{
+ Pixel p1;
+ Pixel p2;
+ float quality;
+} SplineCandidate;
+
+typedef struct _Edgel
+{
+ gint x, y;
+ Direction direction;
+
+ gfloat x_normal;
+ gfloat y_normal;
+ gfloat curvature;
+ guint next, previous;
+} Edgel;
+
+
+static void gimp_line_art_finalize (GObject *object);
+static void gimp_line_art_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_line_art_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+/* Functions for asynchronous computation. */
+
+static void gimp_line_art_compute (GimpLineArt *line_art);
+static void gimp_line_art_compute_cb (GimpAsync *async,
+ GimpLineArt *line_art);
+
+static GimpAsync * gimp_line_art_prepare_async (GimpLineArt *line_art,
+ gint priority);
+static void gimp_line_art_prepare_async_func (GimpAsync *async,
+ LineArtData *data);
+static LineArtData * line_art_data_new (GeglBuffer *buffer,
+ GimpLineArt *line_art);
+static void line_art_data_free (LineArtData *data);
+static LineArtResult * line_art_result_new (GeglBuffer *line_art,
+ gfloat *distmap);
+static void line_art_result_free (LineArtResult *result);
+
+static gboolean gimp_line_art_idle (GimpLineArt *line_art);
+static void gimp_line_art_input_invalidate_preview (GimpViewable *viewable,
+ GimpLineArt *line_art);
+
+
+/* All actual computation functions. */
+
+static GeglBuffer * gimp_line_art_close (GeglBuffer *buffer,
+ gboolean select_transparent,
+ gdouble stroke_threshold,
+ gint spline_max_length,
+ gint segment_max_length,
+ gint minimal_lineart_area,
+ gint normal_estimate_mask_size,
+ gfloat end_point_rate,
+ gfloat spline_max_angle,
+ gint end_point_connectivity,
+ gfloat spline_roundness,
+ gboolean allow_self_intersections,
+ gint created_regions_significant_area,
+ gint created_regions_minimum_area,
+ gboolean small_segments_from_spline_sources,
+ gfloat **lineart_distmap,
+ GimpAsync *async);
+
+static void gimp_lineart_denoise (GeglBuffer *buffer,
+ int size,
+ GimpAsync *async);
+static void gimp_lineart_compute_normals_curvatures (GeglBuffer *mask,
+ gfloat *normals,
+ gfloat *curvatures,
+ gfloat *smoothed_curvatures,
+ int normal_estimate_mask_size,
+ GimpAsync *async);
+static gfloat * gimp_lineart_get_smooth_curvatures (GArray *edgelset,
+ GimpAsync *async);
+static GArray * gimp_lineart_curvature_extremums (gfloat *curvatures,
+ gfloat *smoothed_curvatures,
+ gint curvatures_width,
+ gint curvatures_height,
+ GimpAsync *async);
+static gint gimp_spline_candidate_cmp (const SplineCandidate *a,
+ const SplineCandidate *b,
+ gpointer user_data);
+static GList * gimp_lineart_find_spline_candidates (GArray *max_positions,
+ gfloat *normals,
+ gint width,
+ gint distance_threshold,
+ gfloat max_angle_deg,
+ GimpAsync *async);
+
+static GArray * gimp_lineart_discrete_spline (Pixel p0,
+ GimpVector2 n0,
+ Pixel p1,
+ GimpVector2 n1);
+
+static gint gimp_number_of_transitions (GArray *pixels,
+ GeglBuffer *buffer);
+static gboolean gimp_line_art_allow_closure (GeglBuffer *mask,
+ GArray *pixels,
+ GList **fill_pixels,
+ int significant_size,
+ int minimum_size);
+static GArray * gimp_lineart_line_segment_until_hit (const GeglBuffer *buffer,
+ Pixel start,
+ GimpVector2 direction,
+ int size);
+static gfloat * gimp_lineart_estimate_strokes_radii (GeglBuffer *mask,
+ GimpAsync *async);
+static void gimp_line_art_simple_fill (GeglBuffer *buffer,
+ gint x,
+ gint y,
+ gint *counter);
+
+/* Some callback-type functions. */
+
+static guint visited_hash_fun (Pixel *key);
+static gboolean visited_equal_fun (Pixel *e1,
+ Pixel *e2);
+
+static inline gboolean border_in_direction (GeglBuffer *mask,
+ Pixel p,
+ int direction);
+static inline GimpVector2 pair2normal (Pixel p,
+ gfloat *normals,
+ gint width);
+
+/* Edgel */
+
+static Edgel * gimp_edgel_new (int x,
+ int y,
+ Direction direction);
+static void gimp_edgel_init (Edgel *edgel);
+static void gimp_edgel_clear (Edgel **edgel);
+static int gimp_edgel_cmp (const Edgel *e1,
+ const Edgel *e2);
+static guint edgel2index_hash_fun (Edgel *key);
+static gboolean edgel2index_equal_fun (Edgel *e1,
+ Edgel *e2);
+
+static glong gimp_edgel_track_mark (GeglBuffer *mask,
+ Edgel edgel,
+ long size_limit);
+static glong gimp_edgel_region_area (const GeglBuffer *mask,
+ Edgel start_edgel);
+
+/* Edgel set */
+
+static GArray * gimp_edgelset_new (GeglBuffer *buffer,
+ GimpAsync *async);
+static void gimp_edgelset_add (GArray *set,
+ int x,
+ int y,
+ Direction direction,
+ GHashTable *edgel2index);
+static void gimp_edgelset_init_normals (GArray *set);
+static void gimp_edgelset_smooth_normals (GArray *set,
+ int mask_size,
+ GimpAsync *async);
+static void gimp_edgelset_compute_curvature (GArray *set,
+ GimpAsync *async);
+
+static void gimp_edgelset_build_graph (GArray *set,
+ GeglBuffer *buffer,
+ GHashTable *edgel2index,
+ GimpAsync *async);
+static void gimp_edgelset_next8 (const GeglBuffer *buffer,
+ Edgel *it,
+ Edgel *n);
+
+G_DEFINE_TYPE_WITH_CODE (GimpLineArt, gimp_line_art, GIMP_TYPE_OBJECT,
+ G_ADD_PRIVATE (GimpLineArt))
+
+static guint gimp_line_art_signals[LAST_SIGNAL] = { 0 };
+
+static void
+gimp_line_art_class_init (GimpLineArtClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ gimp_line_art_signals[COMPUTING_START] =
+ g_signal_new ("computing-start",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpLineArtClass, computing_start),
+ NULL, NULL,
+ gimp_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+ gimp_line_art_signals[COMPUTING_END] =
+ g_signal_new ("computing-end",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpLineArtClass, computing_end),
+ NULL, NULL,
+ gimp_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ object_class->finalize = gimp_line_art_finalize;
+ object_class->set_property = gimp_line_art_set_property;
+ object_class->get_property = gimp_line_art_get_property;
+
+ g_object_class_install_property (object_class, PROP_SELECT_TRANSPARENT,
+ g_param_spec_boolean ("select-transparent",
+ _("Select transparent pixels instead of gray ones"),
+ _("Select transparent pixels instead of gray ones"),
+ TRUE,
+ G_PARAM_CONSTRUCT | GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_THRESHOLD,
+ g_param_spec_double ("threshold",
+ _("Line art detection threshold"),
+ _("Threshold to detect contour (higher values will include more pixels)"),
+ 0.0, 1.0, 0.92,
+ G_PARAM_CONSTRUCT | GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_MAX_GROW,
+ g_param_spec_int ("max-grow",
+ _("Maximum growing size"),
+ _("Maximum number of pixels grown under the line art"),
+ 1, 100, 3,
+ G_PARAM_CONSTRUCT | GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_SPLINE_MAX_LEN,
+ g_param_spec_int ("spline-max-length",
+ _("Maximum curved closing length"),
+ _("Maximum curved length (in pixels) to close the line art"),
+ 0, 1000, 100,
+ G_PARAM_CONSTRUCT | GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_SEGMENT_MAX_LEN,
+ g_param_spec_int ("segment-max-length",
+ _("Maximum straight closing length"),
+ _("Maximum straight length (in pixels) to close the line art"),
+ 0, 1000, 100,
+ G_PARAM_CONSTRUCT | GIMP_PARAM_READWRITE));
+}
+
+static void
+gimp_line_art_init (GimpLineArt *line_art)
+{
+ line_art->priv = gimp_line_art_get_instance_private (line_art);
+}
+
+static void
+gimp_line_art_finalize (GObject *object)
+{
+ GimpLineArt *line_art = GIMP_LINE_ART (object);
+
+ line_art->priv->frozen = FALSE;
+
+ gimp_line_art_set_input (line_art, NULL);
+}
+
+static void
+gimp_line_art_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpLineArt *line_art = GIMP_LINE_ART (object);
+
+ switch (property_id)
+ {
+ case PROP_SELECT_TRANSPARENT:
+ if (line_art->priv->select_transparent != g_value_get_boolean (value))
+ {
+ line_art->priv->select_transparent = g_value_get_boolean (value);
+ gimp_line_art_compute (line_art);
+ }
+ break;
+ case PROP_MAX_GROW:
+ line_art->priv->max_grow = g_value_get_int (value);
+ break;
+ case PROP_THRESHOLD:
+ if (line_art->priv->threshold != g_value_get_double (value))
+ {
+ line_art->priv->threshold = g_value_get_double (value);
+ gimp_line_art_compute (line_art);
+ }
+ break;
+ case PROP_SPLINE_MAX_LEN:
+ if (line_art->priv->spline_max_len != g_value_get_int (value))
+ {
+ line_art->priv->spline_max_len = g_value_get_int (value);
+ if (line_art->priv->max_len_bound)
+ line_art->priv->segment_max_len = line_art->priv->spline_max_len;
+ gimp_line_art_compute (line_art);
+ }
+ break;
+ case PROP_SEGMENT_MAX_LEN:
+ if (line_art->priv->segment_max_len != g_value_get_int (value))
+ {
+ line_art->priv->segment_max_len = g_value_get_int (value);
+ if (line_art->priv->max_len_bound)
+ line_art->priv->spline_max_len = line_art->priv->segment_max_len;
+ gimp_line_art_compute (line_art);
+ }
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_line_art_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpLineArt *line_art = GIMP_LINE_ART (object);
+
+ switch (property_id)
+ {
+ case PROP_SELECT_TRANSPARENT:
+ g_value_set_boolean (value, line_art->priv->select_transparent);
+ break;
+ case PROP_MAX_GROW:
+ g_value_set_int (value, line_art->priv->max_grow);
+ break;
+ case PROP_THRESHOLD:
+ g_value_set_double (value, line_art->priv->threshold);
+ break;
+ case PROP_SPLINE_MAX_LEN:
+ g_value_set_int (value, line_art->priv->spline_max_len);
+ break;
+ case PROP_SEGMENT_MAX_LEN:
+ g_value_set_int (value, line_art->priv->segment_max_len);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+/* Public functions */
+
+GimpLineArt *
+gimp_line_art_new (void)
+{
+ return g_object_new (GIMP_TYPE_LINE_ART,
+ NULL);
+}
+
+void
+gimp_line_art_bind_gap_length (GimpLineArt *line_art,
+ gboolean bound)
+{
+ line_art->priv->max_len_bound = bound;
+}
+
+void
+gimp_line_art_set_input (GimpLineArt *line_art,
+ GimpPickable *pickable)
+{
+ g_return_if_fail (pickable == NULL || GIMP_IS_VIEWABLE (pickable));
+
+ if (pickable != line_art->priv->input)
+ {
+ if (line_art->priv->input)
+ g_signal_handlers_disconnect_by_data (line_art->priv->input, line_art);
+
+ g_set_object (&line_art->priv->input, pickable);
+
+ gimp_line_art_compute (line_art);
+
+ if (pickable)
+ {
+ g_signal_connect (pickable, "invalidate-preview",
+ G_CALLBACK (gimp_line_art_input_invalidate_preview),
+ line_art);
+ }
+ }
+}
+
+GimpPickable *
+gimp_line_art_get_input (GimpLineArt *line_art)
+{
+ return line_art->priv->input;
+}
+
+void
+gimp_line_art_freeze (GimpLineArt *line_art)
+{
+ g_return_if_fail (! line_art->priv->frozen);
+
+ line_art->priv->frozen = TRUE;
+ line_art->priv->compute_after_thaw = FALSE;
+}
+
+void
+gimp_line_art_thaw (GimpLineArt *line_art)
+{
+ g_return_if_fail (line_art->priv->frozen);
+
+ line_art->priv->frozen = FALSE;
+ if (line_art->priv->compute_after_thaw)
+ {
+ gimp_line_art_compute (line_art);
+ line_art->priv->compute_after_thaw = FALSE;
+ }
+}
+
+gboolean
+gimp_line_art_is_frozen (GimpLineArt *line_art)
+{
+ return line_art->priv->frozen;
+}
+
+GeglBuffer *
+gimp_line_art_get (GimpLineArt *line_art,
+ gfloat **distmap)
+{
+ g_return_val_if_fail (line_art->priv->input, NULL);
+
+ if (line_art->priv->async)
+ {
+ gimp_waitable_wait (GIMP_WAITABLE (line_art->priv->async));
+ }
+ else if (! line_art->priv->closed)
+ {
+ gimp_line_art_compute (line_art);
+ if (line_art->priv->async)
+ gimp_waitable_wait (GIMP_WAITABLE (line_art->priv->async));
+ }
+
+ g_return_val_if_fail (line_art->priv->closed, NULL);
+
+ if (distmap)
+ *distmap = line_art->priv->distmap;
+
+ return line_art->priv->closed;
+}
+
+/* Functions for asynchronous computation. */
+
+static void
+gimp_line_art_compute (GimpLineArt *line_art)
+{
+ if (line_art->priv->frozen)
+ {
+ line_art->priv->compute_after_thaw = TRUE;
+ return;
+ }
+
+ if (line_art->priv->async)
+ {
+ /* we cancel the async, but don't wait for it to finish, since
+ * it might take a while to respond. instead gimp_line_art_compute_cb()
+ * bails if the async has been canceled, to avoid accessing the line art.
+ */
+ g_signal_emit (line_art, gimp_line_art_signals[COMPUTING_END], 0);
+ gimp_cancelable_cancel (GIMP_CANCELABLE (line_art->priv->async));
+ g_clear_object (&line_art->priv->async);
+ }
+
+ if (line_art->priv->idle_id)
+ {
+ g_source_remove (line_art->priv->idle_id);
+ line_art->priv->idle_id = 0;
+ }
+
+ g_clear_object (&line_art->priv->closed);
+ g_clear_pointer (&line_art->priv->distmap, g_free);
+
+ if (line_art->priv->input)
+ {
+ /* gimp_line_art_prepare_async() will flush the pickable, which
+ * may trigger this signal handler, and will leak a line art (as
+ * line_art->priv->async has not been set yet).
+ */
+ g_signal_handlers_block_by_func (
+ line_art->priv->input,
+ G_CALLBACK (gimp_line_art_input_invalidate_preview),
+ line_art);
+ line_art->priv->async = gimp_line_art_prepare_async (line_art, +1);
+ g_signal_emit (line_art, gimp_line_art_signals[COMPUTING_START], 0);
+ g_signal_handlers_unblock_by_func (
+ line_art->priv->input,
+ G_CALLBACK (gimp_line_art_input_invalidate_preview),
+ line_art);
+
+ gimp_async_add_callback_for_object (line_art->priv->async,
+ (GimpAsyncCallback) gimp_line_art_compute_cb,
+ line_art, line_art);
+ }
+}
+
+static void
+gimp_line_art_compute_cb (GimpAsync *async,
+ GimpLineArt *line_art)
+{
+ if (gimp_async_is_canceled (async))
+ return;
+
+ if (gimp_async_is_finished (async))
+ {
+ LineArtResult *result;
+
+ result = gimp_async_get_result (async);
+
+ line_art->priv->closed = g_object_ref (result->closed);
+ line_art->priv->distmap = result->distmap;
+ result->distmap = NULL;
+ g_signal_emit (line_art, gimp_line_art_signals[COMPUTING_END], 0);
+ }
+
+ g_clear_object (&line_art->priv->async);
+}
+
+static GimpAsync *
+gimp_line_art_prepare_async (GimpLineArt *line_art,
+ gint priority)
+{
+ GeglBuffer *buffer;
+ GimpAsync *async;
+ LineArtData *data;
+
+ g_return_val_if_fail (GIMP_IS_PICKABLE (line_art->priv->input), NULL);
+
+ gimp_pickable_flush (line_art->priv->input);
+
+ buffer = gimp_gegl_buffer_dup (
+ gimp_pickable_get_buffer (line_art->priv->input));
+
+ data = line_art_data_new (buffer, line_art);
+
+ g_object_unref (buffer);
+
+ async = gimp_parallel_run_async_full (
+ priority,
+ (GimpRunAsyncFunc) gimp_line_art_prepare_async_func,
+ data, (GDestroyNotify) line_art_data_free);
+
+ return async;
+}
+
+static void
+gimp_line_art_prepare_async_func (GimpAsync *async,
+ LineArtData *data)
+{
+ GeglBuffer *buffer;
+ GeglBuffer *closed = NULL;
+ gfloat *distmap = NULL;
+ gint buffer_x;
+ gint buffer_y;
+ gboolean has_alpha;
+ gboolean select_transparent = FALSE;
+
+ has_alpha = babl_format_has_alpha (gegl_buffer_get_format (data->buffer));
+
+ if (has_alpha)
+ {
+ if (data->select_transparent)
+ {
+ /* don't select transparent regions if there are no fully
+ * transparent pixels.
+ */
+ GeglBufferIterator *gi;
+
+ gi = gegl_buffer_iterator_new (data->buffer, NULL, 0,
+ babl_format ("A u8"),
+ GEGL_ACCESS_READ, GEGL_ABYSS_NONE, 3);
+ while (gegl_buffer_iterator_next (gi))
+ {
+ guint8 *p = (guint8*) gi->items[0].data;
+ gint k;
+
+ if (gimp_async_is_canceled (async))
+ {
+ gegl_buffer_iterator_stop (gi);
+
+ gimp_async_abort (async);
+
+ line_art_data_free (data);
+
+ return;
+ }
+
+ for (k = 0; k < gi->length; k++)
+ {
+ if (! *p)
+ {
+ select_transparent = TRUE;
+ break;
+ }
+ p++;
+ }
+ if (select_transparent)
+ break;
+ }
+ if (select_transparent)
+ gegl_buffer_iterator_stop (gi);
+ }
+ }
+
+ buffer = data->buffer;
+ buffer_x = gegl_buffer_get_x (data->buffer);
+ buffer_y = gegl_buffer_get_y (data->buffer);
+
+ if (buffer_x != 0 || buffer_y != 0)
+ {
+ buffer = g_object_new (GEGL_TYPE_BUFFER,
+ "source", buffer,
+ "shift-x", buffer_x,
+ "shift-y", buffer_y,
+ NULL);
+ }
+
+ /* For smart selection, we generate a binarized image with close
+ * regions, then run a composite selection with no threshold on
+ * this intermediate buffer.
+ */
+ GIMP_TIMER_START();
+
+ closed = gimp_line_art_close (buffer,
+ select_transparent,
+ data->threshold,
+ data->spline_max_len,
+ data->segment_max_len,
+ /*minimal_lineart_area,*/
+ 5,
+ /*normal_estimate_mask_size,*/
+ 5,
+ /*end_point_rate,*/
+ 0.85,
+ /*spline_max_angle,*/
+ 90.0,
+ /*end_point_connectivity,*/
+ 2,
+ /*spline_roundness,*/
+ 1.0,
+ /*allow_self_intersections,*/
+ TRUE,
+ /*created_regions_significant_area,*/
+ 4,
+ /*created_regions_minimum_area,*/
+ 100,
+ /*small_segments_from_spline_sources,*/
+ TRUE,
+ &distmap,
+ async);
+
+ GIMP_TIMER_END("close line-art");
+
+ if (buffer != data->buffer)
+ g_object_unref (buffer);
+
+ if (! gimp_async_is_stopped (async))
+ {
+ if (buffer_x != 0 || buffer_y != 0)
+ {
+ buffer = g_object_new (GEGL_TYPE_BUFFER,
+ "source", closed,
+ "shift-x", -buffer_x,
+ "shift-y", -buffer_y,
+ NULL);
+
+ g_object_unref (closed);
+
+ closed = buffer;
+ }
+
+ gimp_async_finish_full (async,
+ line_art_result_new (closed, distmap),
+ (GDestroyNotify) line_art_result_free);
+ }
+
+ line_art_data_free (data);
+}
+
+static LineArtData *
+line_art_data_new (GeglBuffer *buffer,
+ GimpLineArt *line_art)
+{
+ LineArtData *data = g_slice_new (LineArtData);
+
+ data->buffer = g_object_ref (buffer);
+ data->select_transparent = line_art->priv->select_transparent;
+ data->threshold = line_art->priv->threshold;
+ data->spline_max_len = line_art->priv->spline_max_len;
+ data->segment_max_len = line_art->priv->segment_max_len;
+
+ return data;
+}
+
+static void
+line_art_data_free (LineArtData *data)
+{
+ g_object_unref (data->buffer);
+
+ g_slice_free (LineArtData, data);
+}
+
+static LineArtResult *
+line_art_result_new (GeglBuffer *closed,
+ gfloat *distmap)
+{
+ LineArtResult *data;
+
+ data = g_slice_new (LineArtResult);
+ data->closed = closed;
+ data->distmap = distmap;
+
+ return data;
+}
+
+static void
+line_art_result_free (LineArtResult *data)
+{
+ g_object_unref (data->closed);
+ g_clear_pointer (&data->distmap, g_free);
+
+ g_slice_free (LineArtResult, data);
+}
+
+static gboolean
+gimp_line_art_idle (GimpLineArt *line_art)
+{
+ line_art->priv->idle_id = 0;
+
+ gimp_line_art_compute (line_art);
+
+ return G_SOURCE_REMOVE;
+}
+
+static void
+gimp_line_art_input_invalidate_preview (GimpViewable *viewable,
+ GimpLineArt *line_art)
+{
+ if (! line_art->priv->idle_id)
+ {
+ line_art->priv->idle_id = g_idle_add_full (
+ GIMP_PRIORITY_VIEWABLE_IDLE,
+ (GSourceFunc) gimp_line_art_idle,
+ line_art, NULL);
+ }
+}
+
+/* All actual computation functions. */
+
+/**
+ * gimp_line_art_close:
+ * @buffer: the input #GeglBuffer.
+ * @select_transparent: whether we binarize the alpha channel or the
+ * luminosity.
+ * @stroke_threshold: [0-1] threshold value for detecting stroke pixels
+ * (higher values will detect more stroke pixels).
+ * @spline_max_length: the maximum length for creating splines between
+ * end points.
+ * @segment_max_length: the maximum length for creating segments
+ * between end points. Unlike splines, segments
+ * are straight lines.
+ * @minimal_lineart_area: the minimum size in number pixels for area to
+ * be considered as line art.
+ * @normal_estimate_mask_size:
+ * @end_point_rate: threshold to estimate if a curvature is an end-point
+ * in [0-1] range value.
+ * @spline_max_angle: the maximum angle between end point normals for
+ * creating splines between them.
+ * @end_point_connectivity:
+ * @spline_roundness:
+ * @allow_self_intersections: whether to allow created splines and
+ * segments to intersect.
+ * @created_regions_significant_area:
+ * @created_regions_minimum_area:
+ * @small_segments_from_spline_sources:
+ * @closed_distmap: a distance map of the closed line art pixels.
+ * @async: the #GimpAsync associated with the computation
+ *
+ * Creates a binarized version of the strokes of @buffer, detected either
+ * with luminosity (light means background) or alpha values depending on
+ * @select_transparent. This binary version of the strokes will have closed
+ * regions allowing adequate selection of "nearly closed regions".
+ * This algorithm is meant for digital painting (and in particular on the
+ * sketch-only step), and therefore will likely produce unexpected results on
+ * other types of input.
+ *
+ * The algorithm is the first step from the research paper "A Fast and
+ * Efficient Semi-guided Algorithm for Flat Coloring Line-arts", by Sébastian
+ * Fourey, David Tschumperlé, David Revoy.
+ * https://hal.archives-ouvertes.fr/hal-01891876
+ *
+ * Returns: a new #GeglBuffer of format "Y u8" representing the
+ * binarized @line_art. If @lineart_distmap is not #NULL, a
+ * newly allocated float buffer is returned, which can be used
+ * for overflowing created masks later.
+ */
+static GeglBuffer *
+gimp_line_art_close (GeglBuffer *buffer,
+ gboolean select_transparent,
+ gdouble stroke_threshold,
+ gint spline_max_length,
+ gint segment_max_length,
+ gint minimal_lineart_area,
+ gint normal_estimate_mask_size,
+ gfloat end_point_rate,
+ gfloat spline_max_angle,
+ gint end_point_connectivity,
+ gfloat spline_roundness,
+ gboolean allow_self_intersections,
+ gint created_regions_significant_area,
+ gint created_regions_minimum_area,
+ gboolean small_segments_from_spline_sources,
+ gfloat **closed_distmap,
+ GimpAsync *async)
+{
+ const Babl *gray_format;
+ GeglBufferIterator *gi;
+ GeglBuffer *closed = NULL;
+ GeglBuffer *strokes = NULL;
+ guchar max_value = 0;
+ gint width = gegl_buffer_get_width (buffer);
+ gint height = gegl_buffer_get_height (buffer);
+ gint i;
+
+ if (select_transparent)
+ /* Keep alpha channel as gray levels */
+ gray_format = babl_format ("A u8");
+ else
+ /* Keep luminance */
+ gray_format = babl_format ("Y' u8");
+
+ /* Transform the line art from any format to gray. */
+ strokes = gegl_buffer_new (gegl_buffer_get_extent (buffer),
+ gray_format);
+ gimp_gegl_buffer_copy (buffer, NULL, GEGL_ABYSS_NONE, strokes, NULL);
+ gegl_buffer_set_format (strokes, babl_format ("Y' u8"));
+
+ if (! select_transparent)
+ {
+ /* Compute the biggest value */
+ gi = gegl_buffer_iterator_new (strokes, NULL, 0, NULL,
+ GEGL_ACCESS_READ, GEGL_ABYSS_NONE, 1);
+ while (gegl_buffer_iterator_next (gi))
+ {
+ guchar *data = (guchar*) gi->items[0].data;
+ gint k;
+
+ if (gimp_async_is_canceled (async))
+ {
+ gegl_buffer_iterator_stop (gi);
+
+ gimp_async_abort (async);
+
+ goto end1;
+ }
+
+ for (k = 0; k < gi->length; k++)
+ {
+ if (*data > max_value)
+ max_value = *data;
+ data++;
+ }
+ }
+ }
+
+ /* Make the image binary: 1 is stroke, 0 background */
+ gi = gegl_buffer_iterator_new (strokes, NULL, 0, NULL,
+ GEGL_ACCESS_READWRITE, GEGL_ABYSS_NONE, 1);
+ while (gegl_buffer_iterator_next (gi))
+ {
+ guchar *data = (guchar*) gi->items[0].data;
+ gint k;
+
+ if (gimp_async_is_canceled (async))
+ {
+ gegl_buffer_iterator_stop (gi);
+
+ gimp_async_abort (async);
+
+ goto end1;
+ }
+
+ for (k = 0; k < gi->length; k++)
+ {
+ if (! select_transparent)
+ /* Negate the value. */
+ *data = max_value - *data;
+ /* Apply a threshold. */
+ if (*data > (guchar) (255.0f * (1.0f - stroke_threshold)))
+ *data = 1;
+ else
+ *data = 0;
+ data++;
+ }
+ }
+
+ /* Denoise (remove small connected components) */
+ gimp_lineart_denoise (strokes, minimal_lineart_area, async);
+ if (gimp_async_is_stopped (async))
+ goto end1;
+
+ closed = g_object_ref (strokes);
+
+ if (spline_max_length > 0 || segment_max_length > 0)
+ {
+ GArray *keypoints = NULL;
+ GHashTable *visited = NULL;
+ gfloat *radii = NULL;
+ gfloat *normals = NULL;
+ gfloat *curvatures = NULL;
+ gfloat *smoothed_curvatures = NULL;
+ gfloat threshold;
+ gfloat clamped_threshold;
+ GList *fill_pixels = NULL;
+ GList *iter;
+
+ normals = g_new0 (gfloat, width * height * 2);
+ curvatures = g_new0 (gfloat, width * height);
+ smoothed_curvatures = g_new0 (gfloat, width * height);
+
+ /* Estimate normals & curvature */
+ gimp_lineart_compute_normals_curvatures (strokes, normals, curvatures,
+ smoothed_curvatures,
+ normal_estimate_mask_size,
+ async);
+ if (gimp_async_is_stopped (async))
+ goto end2;
+
+ radii = gimp_lineart_estimate_strokes_radii (strokes, async);
+ if (gimp_async_is_stopped (async))
+ goto end2;
+ threshold = 1.0f - end_point_rate;
+ clamped_threshold = MAX (0.25f, threshold);
+ for (i = 0; i < width; i++)
+ {
+ gint j;
+
+ if (gimp_async_is_canceled (async))
+ {
+ gimp_async_abort (async);
+
+ goto end2;
+ }
+
+ for (j = 0; j < height; j++)
+ {
+ if (smoothed_curvatures[i + j * width] >= (threshold / MAX (1.0f, radii[i + j * width])) ||
+ curvatures[i + j * width] >= clamped_threshold)
+ curvatures[i + j * width] = 1.0;
+ else
+ curvatures[i + j * width] = 0.0;
+ }
+ }
+ g_clear_pointer (&radii, g_free);
+
+ keypoints = gimp_lineart_curvature_extremums (curvatures, smoothed_curvatures,
+ width, height, async);
+ if (gimp_async_is_stopped (async))
+ goto end2;
+
+ visited = g_hash_table_new_full ((GHashFunc) visited_hash_fun,
+ (GEqualFunc) visited_equal_fun,
+ (GDestroyNotify) g_free, NULL);
+
+ if (spline_max_length > 0)
+ {
+ GList *candidates;
+ SplineCandidate *candidate;
+
+ candidates = gimp_lineart_find_spline_candidates (keypoints, normals, width,
+ spline_max_length,
+ spline_max_angle,
+ async);
+ if (gimp_async_is_stopped (async))
+ goto end3;
+
+ g_object_unref (closed);
+ closed = gimp_gegl_buffer_dup (strokes);
+
+ /* Draw splines */
+ while (candidates)
+ {
+ Pixel *p1;
+ Pixel *p2;
+ gboolean inserted = FALSE;
+
+ if (gimp_async_is_canceled (async))
+ {
+ gimp_async_abort (async);
+
+ goto end3;
+ }
+
+ p1 = g_new (Pixel, 1);
+ p2 = g_new (Pixel, 1);
+
+ candidate = (SplineCandidate *) candidates->data;
+ p1->x = candidate->p1.x;
+ p1->y = candidate->p1.y;
+ p2->x = candidate->p2.x;
+ p2->y = candidate->p2.y;
+
+ g_free (candidate);
+ candidates = g_list_delete_link (candidates, candidates);
+
+ if ((! g_hash_table_contains (visited, p1) ||
+ GPOINTER_TO_INT (g_hash_table_lookup (visited, p1)) < end_point_connectivity) &&
+ (! g_hash_table_contains (visited, p2) ||
+ GPOINTER_TO_INT (g_hash_table_lookup (visited, p2)) < end_point_connectivity))
+ {
+ GArray *discrete_curve;
+ GimpVector2 vect1 = pair2normal (*p1, normals, width);
+ GimpVector2 vect2 = pair2normal (*p2, normals, width);
+ gfloat distance = gimp_vector2_length_val (gimp_vector2_sub_val (*p1, *p2));
+ gint transitions;
+
+ gimp_vector2_mul (&vect1, distance);
+ gimp_vector2_mul (&vect1, spline_roundness);
+ gimp_vector2_mul (&vect2, distance);
+ gimp_vector2_mul (&vect2, spline_roundness);
+
+ discrete_curve = gimp_lineart_discrete_spline (*p1, vect1, *p2, vect2);
+
+ transitions = allow_self_intersections ?
+ gimp_number_of_transitions (discrete_curve, strokes) :
+ gimp_number_of_transitions (discrete_curve, closed);
+
+ if (transitions == 2 &&
+ gimp_line_art_allow_closure (closed, discrete_curve,
+ &fill_pixels,
+ created_regions_significant_area,
+ created_regions_minimum_area))
+ {
+ for (i = 0; i < discrete_curve->len; i++)
+ {
+ Pixel p = g_array_index (discrete_curve, Pixel, i);
+
+ if (p.x >= 0 && p.x < gegl_buffer_get_width (closed) &&
+ p.y >= 0 && p.y < gegl_buffer_get_height (closed))
+ {
+ guchar val = 2;
+
+ gegl_buffer_set (closed, GEGL_RECTANGLE ((gint) p.x, (gint) p.y, 1, 1), 0,
+ NULL, &val, GEGL_AUTO_ROWSTRIDE);
+ }
+ }
+ g_hash_table_replace (visited, p1,
+ GINT_TO_POINTER (GPOINTER_TO_INT (g_hash_table_lookup (visited, p1)) + 1));
+ g_hash_table_replace (visited, p2,
+ GINT_TO_POINTER (GPOINTER_TO_INT (g_hash_table_lookup (visited, p2)) + 1));
+ inserted = TRUE;
+ }
+ g_array_free (discrete_curve, TRUE);
+ }
+ if (! inserted)
+ {
+ g_free (p1);
+ g_free (p2);
+ }
+ }
+
+ end3:
+ g_list_free_full (candidates, g_free);
+
+ if (gimp_async_is_stopped (async))
+ goto end2;
+ }
+
+ g_clear_object (&strokes);
+
+ /* Draw straight line segments */
+ if (segment_max_length > 0)
+ {
+ Pixel *point;
+
+ point = (Pixel *) keypoints->data;
+ for (i = 0; i < keypoints->len; i++)
+ {
+ Pixel *p;
+ gboolean inserted = FALSE;
+
+ if (gimp_async_is_canceled (async))
+ {
+ gimp_async_abort (async);
+
+ goto end2;
+ }
+
+ p = g_new (Pixel, 1);
+ *p = *point;
+
+ if (! g_hash_table_contains (visited, p) ||
+ (small_segments_from_spline_sources &&
+ GPOINTER_TO_INT (g_hash_table_lookup (visited, p)) < end_point_connectivity))
+ {
+ GArray *segment = gimp_lineart_line_segment_until_hit (closed, *point,
+ pair2normal (*point, normals, width),
+ segment_max_length);
+
+ if (segment->len &&
+ gimp_line_art_allow_closure (closed, segment, &fill_pixels,
+ created_regions_significant_area,
+ created_regions_minimum_area))
+ {
+ gint j;
+
+ for (j = 0; j < segment->len; j++)
+ {
+ Pixel p2 = g_array_index (segment, Pixel, j);
+ guchar val = 2;
+
+ gegl_buffer_set (closed, GEGL_RECTANGLE ((gint) p2.x, (gint) p2.y, 1, 1), 0,
+ NULL, &val, GEGL_AUTO_ROWSTRIDE);
+ }
+ g_hash_table_replace (visited, p,
+ GINT_TO_POINTER (GPOINTER_TO_INT (g_hash_table_lookup (visited, p)) + 1));
+ inserted = TRUE;
+ }
+ g_array_free (segment, TRUE);
+ }
+ if (! inserted)
+ g_free (p);
+ point++;
+ }
+ }
+
+ for (iter = fill_pixels; iter; iter = iter->next)
+ {
+ Pixel *p = iter->data;
+ gint fill_max = created_regions_significant_area - 1;
+
+ if (gimp_async_is_canceled (async))
+ {
+ gimp_async_abort (async);
+
+ goto end2;
+ }
+
+ /* XXX A best approach would be to generalize
+ * gimp_drawable_bucket_fill() to work on any buffer (the code
+ * is already mostly there) rather than reimplementing a naive
+ * bucket fill.
+ * This is mostly a quick'n dirty first implementation which I
+ * will improve later.
+ */
+ gimp_line_art_simple_fill (closed, (gint) p->x, (gint) p->y, &fill_max);
+ }
+
+ end2:
+ g_list_free_full (fill_pixels, g_free);
+ g_free (normals);
+ g_free (curvatures);
+ g_free (smoothed_curvatures);
+ g_clear_pointer (&radii, g_free);
+ if (keypoints)
+ g_array_free (keypoints, TRUE);
+ g_clear_pointer (&visited, g_hash_table_destroy);
+
+ if (gimp_async_is_stopped (async))
+ goto end1;
+ }
+ else
+ {
+ g_clear_object (&strokes);
+ }
+
+ if (closed_distmap)
+ {
+ GeglNode *graph;
+ GeglNode *input;
+ GeglNode *op;
+
+ /* Flooding needs a distance map for closed line art. */
+ *closed_distmap = g_new (gfloat, width * height);
+
+ graph = gegl_node_new ();
+ input = gegl_node_new_child (graph,
+ "operation", "gegl:buffer-source",
+ "buffer", closed,
+ NULL);
+ op = gegl_node_new_child (graph,
+ "operation", "gegl:distance-transform",
+ "metric", GEGL_DISTANCE_METRIC_EUCLIDEAN,
+ "normalize", FALSE,
+ NULL);
+ gegl_node_connect_to (input, "output",
+ op, "input");
+ gegl_node_blit (op, 1.0, gegl_buffer_get_extent (closed),
+ NULL, *closed_distmap,
+ GEGL_AUTO_ROWSTRIDE, GEGL_BLIT_DEFAULT);
+ g_object_unref (graph);
+ }
+
+ end1:
+ g_clear_object (&strokes);
+
+ if (gimp_async_is_stopped (async))
+ g_clear_object (&closed);
+
+ return closed;
+}
+
+static void
+gimp_lineart_denoise (GeglBuffer *buffer,
+ int minimum_area,
+ GimpAsync *async)
+{
+ /* Keep connected regions with significant area. */
+ GArray *region;
+ GQueue *q = g_queue_new ();
+ gint width = gegl_buffer_get_width (buffer);
+ gint height = gegl_buffer_get_height (buffer);
+ gboolean *visited = g_new0 (gboolean, width * height);
+ gint x, y;
+
+ region = g_array_sized_new (TRUE, TRUE, sizeof (Pixel *), minimum_area);
+
+ for (y = 0; y < height; ++y)
+ for (x = 0; x < width; ++x)
+ {
+ guchar has_stroke;
+
+ if (gimp_async_is_canceled (async))
+ {
+ gimp_async_abort (async);
+
+ goto end;
+ }
+
+ gegl_buffer_sample (buffer, x, y, NULL, &has_stroke, NULL,
+ GEGL_SAMPLER_NEAREST, GEGL_ABYSS_NONE);
+ if (has_stroke && ! visited[x + y * width])
+ {
+ Pixel *p = g_new (Pixel, 1);
+ gint regionSize = 0;
+
+ p->x = x;
+ p->y = y;
+
+ g_queue_push_tail (q, p);
+ visited[x + y * width] = TRUE;
+
+ while (! g_queue_is_empty (q))
+ {
+ Pixel *p;
+ gint p2x;
+ gint p2y;
+
+ if (gimp_async_is_canceled (async))
+ {
+ gimp_async_abort (async);
+
+ goto end;
+ }
+
+ p = (Pixel *) g_queue_pop_head (q);
+
+ p2x = p->x + 1;
+ p2y = p->y;
+ if (p2x >= 0 && p2x < width && p2y >= 0 && p2y < height)
+ {
+ gegl_buffer_sample (buffer, p2x, p2y, NULL, &has_stroke, NULL,
+ GEGL_SAMPLER_NEAREST, GEGL_ABYSS_NONE);
+ if (has_stroke && ! visited[p2x + p2y * width])
+ {
+ Pixel *p2 = g_new (Pixel, 1);
+
+ p2->x = p2x;
+ p2->y = p2y;
+ g_queue_push_tail (q, p2);
+ visited[p2x +p2y * width] = TRUE;
+ }
+ }
+ p2x = p->x - 1;
+ p2y = p->y;
+ if (p2x >= 0 && p2x < width && p2y >= 0 && p2y < height)
+ {
+ gegl_buffer_sample (buffer, p2x, p2y, NULL, &has_stroke, NULL,
+ GEGL_SAMPLER_NEAREST, GEGL_ABYSS_NONE);
+ if (has_stroke && ! visited[p2x + p2y * width])
+ {
+ Pixel *p2 = g_new (Pixel, 1);
+
+ p2->x = p2x;
+ p2->y = p2y;
+ g_queue_push_tail (q, p2);
+ visited[p2x + p2y * width] = TRUE;
+ }
+ }
+ p2x = p->x;
+ p2y = p->y - 1;
+ if (p2x >= 0 && p2x < width && p2y >= 0 && p2y < height)
+ {
+ gegl_buffer_sample (buffer, p2x, p2y, NULL, &has_stroke, NULL,
+ GEGL_SAMPLER_NEAREST, GEGL_ABYSS_NONE);
+ if (has_stroke && ! visited[p2x + p2y * width])
+ {
+ Pixel *p2 = g_new (Pixel, 1);
+
+ p2->x = p2x;
+ p2->y = p2y;
+ g_queue_push_tail (q, p2);
+ visited[p2x + p2y * width] = TRUE;
+ }
+ }
+ p2x = p->x;
+ p2y = p->y + 1;
+ if (p2x >= 0 && p2x < width && p2y >= 0 && p2y < height)
+ {
+ gegl_buffer_sample (buffer, p2x, p2y, NULL, &has_stroke, NULL,
+ GEGL_SAMPLER_NEAREST, GEGL_ABYSS_NONE);
+ if (has_stroke && ! visited[p2x + p2y * width])
+ {
+ Pixel *p2 = g_new (Pixel, 1);
+
+ p2->x = p2x;
+ p2->y = p2y;
+ g_queue_push_tail (q, p2);
+ visited[p2x + p2y * width] = TRUE;
+ }
+ }
+ p2x = p->x + 1;
+ p2y = p->y + 1;
+ if (p2x >= 0 && p2x < width && p2y >= 0 && p2y < height)
+ {
+ gegl_buffer_sample (buffer, p2x, p2y, NULL, &has_stroke, NULL,
+ GEGL_SAMPLER_NEAREST, GEGL_ABYSS_NONE);
+ if (has_stroke && ! visited[p2x + p2y * width])
+ {
+ Pixel *p2 = g_new (Pixel, 1);
+
+ p2->x = p2x;
+ p2->y = p2y;
+ g_queue_push_tail (q, p2);
+ visited[p2x + p2y * width] = TRUE;
+ }
+ }
+ p2x = p->x - 1;
+ p2y = p->y - 1;
+ if (p2x >= 0 && p2x < width && p2y >= 0 && p2y < height)
+ {
+ gegl_buffer_sample (buffer, p2x, p2y, NULL, &has_stroke, NULL,
+ GEGL_SAMPLER_NEAREST, GEGL_ABYSS_NONE);
+ if (has_stroke && ! visited[p2x + p2y * width])
+ {
+ Pixel *p2 = g_new (Pixel, 1);
+
+ p2->x = p2x;
+ p2->y = p2y;
+ g_queue_push_tail (q, p2);
+ visited[p2x + p2y * width] = TRUE;
+ }
+ }
+ p2x = p->x - 1;
+ p2y = p->y + 1;
+ if (p2x >= 0 && p2x < width && p2y >= 0 && p2y < height)
+ {
+ gegl_buffer_sample (buffer, p2x, p2y, NULL, &has_stroke, NULL,
+ GEGL_SAMPLER_NEAREST, GEGL_ABYSS_NONE);
+ if (has_stroke && ! visited[p2x + p2y * width])
+ {
+ Pixel *p2 = g_new (Pixel, 1);
+
+ p2->x = p2x;
+ p2->y = p2y;
+ g_queue_push_tail (q, p2);
+ visited[p2x + p2y * width] = TRUE;
+ }
+ }
+ p2x = p->x + 1;
+ p2y = p->y - 1;
+ if (p2x >= 0 && p2x < width && p2y >= 0 && p2y < height)
+ {
+ gegl_buffer_sample (buffer, p2x, p2y, NULL, &has_stroke, NULL,
+ GEGL_SAMPLER_NEAREST, GEGL_ABYSS_NONE);
+ if (has_stroke && ! visited[p2x + p2y * width])
+ {
+ Pixel *p2 = g_new (Pixel, 1);
+
+ p2->x = p2x;
+ p2->y = p2y;
+ g_queue_push_tail (q, p2);
+ visited[p2x + p2y * width] = TRUE;
+ }
+ }
+
+ ++regionSize;
+ if (regionSize < minimum_area)
+ g_array_append_val (region, *p);
+ g_free (p);
+ }
+ if (regionSize < minimum_area)
+ {
+ Pixel *pixel = (Pixel *) region->data;
+ gint i = 0;
+
+ for (; i < region->len; i++)
+ {
+ guchar val = 0;
+ gegl_buffer_set (buffer, GEGL_RECTANGLE (pixel->x, pixel->y, 1, 1), 0,
+ NULL, &val, GEGL_AUTO_ROWSTRIDE);
+ pixel++;
+ }
+ }
+ g_array_remove_range (region, 0, region->len);
+ }
+ }
+
+ end:
+ g_array_free (region, TRUE);
+ g_queue_free_full (q, g_free);
+ g_free (visited);
+}
+
+static void
+gimp_lineart_compute_normals_curvatures (GeglBuffer *mask,
+ gfloat *normals,
+ gfloat *curvatures,
+ gfloat *smoothed_curvatures,
+ int normal_estimate_mask_size,
+ GimpAsync *async)
+{
+ gfloat *edgels_curvatures = NULL;
+ gfloat *smoothed_curvature;
+ GArray *es = NULL;
+ Edgel **e;
+ gint width = gegl_buffer_get_width (mask);
+
+ es = gimp_edgelset_new (mask, async);
+ if (gimp_async_is_stopped (async))
+ goto end;
+
+ e = (Edgel **) es->data;
+
+ gimp_edgelset_smooth_normals (es, normal_estimate_mask_size, async);
+ if (gimp_async_is_stopped (async))
+ goto end;
+
+ gimp_edgelset_compute_curvature (es, async);
+ if (gimp_async_is_stopped (async))
+ goto end;
+
+ while (*e)
+ {
+ const float curvature = ((*e)->curvature > 0.0f) ? (*e)->curvature : 0.0f;
+ const float w = MAX (1e-8f, curvature * curvature);
+
+ if (gimp_async_is_canceled (async))
+ {
+ gimp_async_abort (async);
+
+ goto end;
+ }
+
+ normals[((*e)->x + (*e)->y * width) * 2] += w * (*e)->x_normal;
+ normals[((*e)->x + (*e)->y * width) * 2 + 1] += w * (*e)->y_normal;
+ curvatures[(*e)->x + (*e)->y * width] = MAX (curvature,
+ curvatures[(*e)->x + (*e)->y * width]);
+ e++;
+ }
+ for (int y = 0; y < gegl_buffer_get_height (mask); ++y)
+ {
+ if (gimp_async_is_canceled (async))
+ {
+ gimp_async_abort (async);
+
+ goto end;
+ }
+
+ for (int x = 0; x < gegl_buffer_get_width (mask); ++x)
+ {
+ const float _angle = atan2f (normals[(x + y * width) * 2 + 1],
+ normals[(x + y * width) * 2]);
+ normals[(x + y * width) * 2] = cosf (_angle);
+ normals[(x + y * width) * 2 + 1] = sinf (_angle);
+ }
+ }
+
+ /* Smooth curvatures on edgels, then take maximum on each pixel. */
+ edgels_curvatures = gimp_lineart_get_smooth_curvatures (es, async);
+ if (gimp_async_is_stopped (async))
+ goto end;
+
+ smoothed_curvature = edgels_curvatures;
+
+ e = (Edgel **) es->data;
+ while (*e)
+ {
+ gfloat *pixel_curvature = &smoothed_curvatures[(*e)->x + (*e)->y * width];
+
+ if (*pixel_curvature < *smoothed_curvature)
+ *pixel_curvature = *smoothed_curvature;
+
+ ++smoothed_curvature;
+ e++;
+ }
+
+ end:
+ g_free (edgels_curvatures);
+
+ if (es)
+ g_array_free (es, TRUE);
+}
+
+static gfloat *
+gimp_lineart_get_smooth_curvatures (GArray *edgelset,
+ GimpAsync *async)
+{
+ Edgel **e;
+ gfloat *smoothed_curvatures = g_new0 (gfloat, edgelset->len);
+ gfloat weights[9];
+ gfloat smoothed_curvature;
+ gfloat weights_sum;
+ gint idx = 0;
+
+ weights[0] = 1.0f;
+ for (int i = 1; i <= 8; ++i)
+ weights[i] = expf (-(i * i) / 30.0f);
+
+ e = (Edgel **) edgelset->data;
+ while (*e)
+ {
+ Edgel *edgel_before = g_array_index (edgelset, Edgel*, (*e)->previous);
+ Edgel *edgel_after = g_array_index (edgelset, Edgel*, (*e)->next);
+ int n = 5;
+ int i = 1;
+
+ if (gimp_async_is_canceled (async))
+ {
+ gimp_async_abort (async);
+
+ g_free (smoothed_curvatures);
+
+ return NULL;
+ }
+
+ smoothed_curvature = (*e)->curvature;
+ weights_sum = weights[0];
+ while (n-- && (edgel_after != edgel_before))
+ {
+ smoothed_curvature += weights[i] * edgel_before->curvature;
+ smoothed_curvature += weights[i] * edgel_after->curvature;
+ edgel_before = g_array_index (edgelset, Edgel*, edgel_before->previous);
+ edgel_after = g_array_index (edgelset, Edgel*, edgel_after->next);
+ weights_sum += 2 * weights[i];
+ i++;
+ }
+ smoothed_curvature /= weights_sum;
+ smoothed_curvatures[idx++] = smoothed_curvature;
+
+ e++;
+ }
+
+ return smoothed_curvatures;
+}
+
+/**
+ * Keep one pixel per connected component of curvature extremums.
+ */
+static GArray *
+gimp_lineart_curvature_extremums (gfloat *curvatures,
+ gfloat *smoothed_curvatures,
+ gint width,
+ gint height,
+ GimpAsync *async)
+{
+ gboolean *visited = g_new0 (gboolean, width * height);
+ GQueue *q = g_queue_new ();
+ GArray *max_positions;
+
+ max_positions = g_array_new (FALSE, TRUE, sizeof (Pixel));
+
+ for (int y = 0; y < height; ++y)
+ {
+ if (gimp_async_is_canceled (async))
+ {
+ gimp_async_abort (async);
+
+ goto end;
+ }
+
+ for (int x = 0; x < width; ++x)
+ {
+ if ((curvatures[x + y * width] > 0.0) && ! visited[x + y * width])
+ {
+ Pixel *p = g_new (Pixel, 1);
+ Pixel max_smoothed_curvature_pixel;
+ Pixel max_raw_curvature_pixel;
+ gfloat max_smoothed_curvature;
+ gfloat max_raw_curvature;
+
+ max_smoothed_curvature_pixel = gimp_vector2_new (-1.0, -1.0);
+ max_smoothed_curvature = 0.0f;
+
+ max_raw_curvature_pixel = gimp_vector2_new (x, y);
+ max_raw_curvature = curvatures[x + y * width];
+
+ p->x = x;
+ p->y = y;
+ g_queue_push_tail (q, p);
+ visited[x + y * width] = TRUE;
+
+ while (! g_queue_is_empty (q))
+ {
+ gfloat sc;
+ gfloat c;
+ gint p2x;
+ gint p2y;
+
+ if (gimp_async_is_canceled (async))
+ {
+ gimp_async_abort (async);
+
+ goto end;
+ }
+
+ p = (Pixel *) g_queue_pop_head (q);
+ sc = smoothed_curvatures[(gint) p->x + (gint) p->y * width];
+ c = curvatures[(gint) p->x + (gint) p->y * width];
+
+ curvatures[(gint) p->x + (gint) p->y * width] = 0.0f;
+
+ p2x = (gint) p->x + 1;
+ p2y = (gint) p->y;
+ if (p2x >= 0 && p2x < width &&
+ p2y >= 0 && p2y < height &&
+ curvatures[p2x + p2y * width] > 0.0 &&
+ ! visited[p2x + p2y * width])
+ {
+ Pixel *p2 = g_new (Pixel, 1);
+
+ p2->x = p2x;
+ p2->y = p2y;
+ g_queue_push_tail (q, p2);
+ visited[p2x + p2y * width] = TRUE;
+ }
+
+ p2x = p->x - 1;
+ p2y = p->y;
+ if (p2x >= 0 && p2x < width &&
+ p2y >= 0 && p2y < height &&
+ curvatures[p2x + p2y * width] > 0.0 &&
+ ! visited[p2x + p2y * width])
+ {
+ Pixel *p2 = g_new (Pixel, 1);
+
+ p2->x = p2x;
+ p2->y = p2y;
+ g_queue_push_tail (q, p2);
+ visited[p2x + p2y * width] = TRUE;
+ }
+
+ p2x = p->x;
+ p2y = p->y - 1;
+ if (p2x >= 0 && p2x < width &&
+ p2y >= 0 && p2y < height &&
+ curvatures[p2x + p2y * width] > 0.0 &&
+ ! visited[p2x + p2y * width])
+ {
+ Pixel *p2 = g_new (Pixel, 1);
+
+ p2->x = p2x;
+ p2->y = p2y;
+ g_queue_push_tail (q, p2);
+ visited[p2x + p2y * width] = TRUE;
+ }
+
+ p2x = p->x;
+ p2y = p->y + 1;
+ if (p2x >= 0 && p2x < width &&
+ p2y >= 0 && p2y < height &&
+ curvatures[p2x + p2y * width] > 0.0 &&
+ ! visited[p2x + p2y * width])
+ {
+ Pixel *p2 = g_new (Pixel, 1);
+
+ p2->x = p2x;
+ p2->y = p2y;
+ g_queue_push_tail (q, p2);
+ visited[p2x + p2y * width] = TRUE;
+ }
+
+ p2x = p->x + 1;
+ p2y = p->y + 1;
+ if (p2x >= 0 && p2x < width &&
+ p2y >= 0 && p2y < height &&
+ curvatures[p2x + p2y * width] > 0.0 &&
+ ! visited[p2x + p2y * width])
+ {
+ Pixel *p2 = g_new (Pixel, 1);
+
+ p2->x = p2x;
+ p2->y = p2y;
+ g_queue_push_tail (q, p2);
+ visited[p2x + p2y * width] = TRUE;
+ }
+
+ p2x = p->x - 1;
+ p2y = p->y - 1;
+ if (p2x >= 0 && p2x < width &&
+ p2y >= 0 && p2y < height &&
+ curvatures[p2x + p2y * width] > 0.0 &&
+ ! visited[p2x + p2y * width])
+ {
+ Pixel *p2 = g_new (Pixel, 1);
+
+ p2->x = p2x;
+ p2->y = p2y;
+ g_queue_push_tail (q, p2);
+ visited[p2x + p2y * width] = TRUE;
+ }
+
+ p2x = p->x - 1;
+ p2y = p->y + 1;
+ if (p2x >= 0 && p2x < width &&
+ p2y >= 0 && p2y < height &&
+ curvatures[p2x + p2y * width] > 0.0 &&
+ ! visited[p2x + p2y * width])
+ {
+ Pixel *p2 = g_new (Pixel, 1);
+
+ p2->x = p2x;
+ p2->y = p2y;
+ g_queue_push_tail (q, p2);
+ visited[p2x + p2y * width] = TRUE;
+ }
+
+ p2x = p->x + 1;
+ p2y = p->y - 1;
+ if (p2x >= 0 && p2x < width &&
+ p2y >= 0 && p2y < height &&
+ curvatures[p2x + p2y * width] > 0.0 &&
+ ! visited[p2x + p2y * width])
+ {
+ Pixel *p2 = g_new (Pixel, 1);
+
+ p2->x = p2x;
+ p2->y = p2y;
+ g_queue_push_tail (q, p2);
+ visited[p2x + p2y * width] = TRUE;
+ }
+
+ if (sc > max_smoothed_curvature)
+ {
+ max_smoothed_curvature_pixel = *p;
+ max_smoothed_curvature = sc;
+ }
+ if (c > max_raw_curvature)
+ {
+ max_raw_curvature_pixel = *p;
+ max_raw_curvature = c;
+ }
+ g_free (p);
+ }
+ if (max_smoothed_curvature > 0.0f)
+ {
+ curvatures[(gint) max_smoothed_curvature_pixel.x + (gint) max_smoothed_curvature_pixel.y * width] = max_smoothed_curvature;
+ g_array_append_val (max_positions, max_smoothed_curvature_pixel);
+ }
+ else
+ {
+ curvatures[(gint) max_raw_curvature_pixel.x + (gint) max_raw_curvature_pixel.y * width] = max_raw_curvature;
+ g_array_append_val (max_positions, max_raw_curvature_pixel);
+ }
+ }
+ }
+ }
+
+ end:
+ g_queue_free_full (q, g_free);
+ g_free (visited);
+
+ if (gimp_async_is_stopped (async))
+ {
+ g_array_free (max_positions, TRUE);
+ max_positions = NULL;
+ }
+
+ return max_positions;
+}
+
+static gint
+gimp_spline_candidate_cmp (const SplineCandidate *a,
+ const SplineCandidate *b,
+ gpointer user_data)
+{
+ /* This comparison actually returns the opposite of common comparison
+ * functions on purpose, as we want the first element on the list to
+ * be the "bigger".
+ */
+ if (a->quality < b->quality)
+ return 1;
+ else if (a->quality > b->quality)
+ return -1;
+ else
+ return 0;
+}
+
+static GList *
+gimp_lineart_find_spline_candidates (GArray *max_positions,
+ gfloat *normals,
+ gint width,
+ gint distance_threshold,
+ gfloat max_angle_deg,
+ GimpAsync *async)
+{
+ GList *candidates = NULL;
+ const float CosMin = cosf (M_PI * (max_angle_deg / 180.0));
+ gint i;
+
+ for (i = 0; i < max_positions->len; i++)
+ {
+ Pixel p1 = g_array_index (max_positions, Pixel, i);
+ gint j;
+
+ if (gimp_async_is_canceled (async))
+ {
+ gimp_async_abort (async);
+
+ g_list_free_full (candidates, g_free);
+
+ return NULL;
+ }
+
+ for (j = i + 1; j < max_positions->len; j++)
+ {
+ Pixel p2 = g_array_index (max_positions, Pixel, j);
+ const float distance = gimp_vector2_length_val (gimp_vector2_sub_val (p1, p2));
+
+ if (distance <= distance_threshold)
+ {
+ GimpVector2 normalP1;
+ GimpVector2 normalP2;
+ GimpVector2 p1f;
+ GimpVector2 p2f;
+ GimpVector2 p1p2;
+ float cosN;
+ float qualityA;
+ float qualityB;
+ float qualityC;
+ float quality;
+
+ normalP1 = gimp_vector2_new (normals[((gint) p1.x + (gint) p1.y * width) * 2],
+ normals[((gint) p1.x + (gint) p1.y * width) * 2 + 1]);
+ normalP2 = gimp_vector2_new (normals[((gint) p2.x + (gint) p2.y * width) * 2],
+ normals[((gint) p2.x + (gint) p2.y * width) * 2 + 1]);
+ p1f = gimp_vector2_new (p1.x, p1.y);
+ p2f = gimp_vector2_new (p2.x, p2.y);
+ p1p2 = gimp_vector2_sub_val (p2f, p1f);
+
+ cosN = gimp_vector2_inner_product_val (normalP1, (gimp_vector2_neg_val (normalP2)));
+ qualityA = MAX (0.0f, 1 - distance / distance_threshold);
+ qualityB = MAX (0.0f,
+ (float) (gimp_vector2_inner_product_val (normalP1, p1p2) - gimp_vector2_inner_product_val (normalP2, p1p2)) /
+ distance);
+ qualityC = MAX (0.0f, cosN - CosMin);
+ quality = qualityA * qualityB * qualityC;
+ if (quality > 0)
+ {
+ SplineCandidate *candidate = g_new (SplineCandidate, 1);
+
+ candidate->p1 = p1;
+ candidate->p2 = p2;
+ candidate->quality = quality;
+
+ candidates = g_list_insert_sorted_with_data (candidates, candidate,
+ (GCompareDataFunc) gimp_spline_candidate_cmp,
+ NULL);
+ }
+ }
+ }
+ }
+ return candidates;
+}
+
+static GArray *
+gimp_lineart_discrete_spline (Pixel p0,
+ GimpVector2 n0,
+ Pixel p1,
+ GimpVector2 n1)
+{
+ GArray *points = g_array_new (FALSE, TRUE, sizeof (Pixel));
+ const double a0 = 2 * p0.x - 2 * p1.x + n0.x - n1.x;
+ const double b0 = -3 * p0.x + 3 * p1.x - 2 * n0.x + n1.x;
+ const double c0 = n0.x;
+ const double d0 = p0.x;
+ const double a1 = 2 * p0.y - 2 * p1.y + n0.y - n1.y;
+ const double b1 = -3 * p0.y + 3 * p1.y - 2 * n0.y + n1.y;
+ const double c1 = n0.y;
+ const double d1 = p0.y;
+
+ double t = 0.0;
+ const double dtMin = 1.0 / MAX (fabs (p0.x - p1.x), fabs (p0.y - p1.y));
+ Pixel point = gimp_vector2_new ((gint) round (d0), (gint) round (d1));
+
+ g_array_append_val (points, point);
+
+ while (t <= 1.0)
+ {
+ const double t2 = t * t;
+ const double t3 = t * t2;
+ double dx;
+ double dy;
+ Pixel p = gimp_vector2_new ((gint) round (a0 * t3 + b0 * t2 + c0 * t + d0),
+ (gint) round (a1 * t3 + b1 * t2 + c1 * t + d1));
+
+ /* create gimp_vector2_neq () ? */
+ if (g_array_index (points, Pixel, points->len - 1).x != p.x ||
+ g_array_index (points, Pixel, points->len - 1).y != p.y)
+ {
+ g_array_append_val (points, p);
+ }
+ dx = fabs (3 * a0 * t * t + 2 * b0 * t + c0) + 1e-8;
+ dy = fabs (3 * a1 * t * t + 2 * b1 * t + c1) + 1e-8;
+ t += MIN (dtMin, 0.75 / MAX (dx, dy));
+ }
+ if (g_array_index (points, Pixel, points->len - 1).x != p1.x ||
+ g_array_index (points, Pixel, points->len - 1).y != p1.y)
+ {
+ g_array_append_val (points, p1);
+ }
+ return points;
+}
+
+static gint
+gimp_number_of_transitions (GArray *pixels,
+ GeglBuffer *buffer)
+{
+ int result = 0;
+
+ if (pixels->len > 0)
+ {
+ Pixel it = g_array_index (pixels, Pixel, 0);
+ guchar value;
+ gboolean previous;
+ gint i;
+
+ gegl_buffer_sample (buffer, (gint) it.x, (gint) it.y, NULL, &value, NULL,
+ GEGL_SAMPLER_NEAREST, GEGL_ABYSS_NONE);
+ previous = (gboolean) value;
+
+ /* Starts at the second element. */
+ for (i = 1; i < pixels->len; i++)
+ {
+ it = g_array_index (pixels, Pixel, i);
+
+ gegl_buffer_sample (buffer, (gint) it.x, (gint) it.y, NULL, &value, NULL,
+ GEGL_SAMPLER_NEAREST, GEGL_ABYSS_NONE);
+ result += ((gboolean) value != previous);
+ previous = (gboolean) value;
+ }
+ }
+
+ return result;
+}
+
+/**
+ * gimp_line_art_allow_closure:
+ * @mask: the current state of line art closure.
+ * @pixels: the pixels of a candidate closure (spline or segment).
+ * @fill_pixels: #GList of unsignificant pixels to bucket fill.
+ * @significant_size: number of pixels for area to be considered
+ * "significant".
+ * @minimum_size: number of pixels for area to be allowed.
+ *
+ * Checks whether adding the set of points @pixels to @mask will create
+ * 4-connected background regions whose size (i.e. number of pixels)
+ * will be below @minimum_size. If it creates such small areas, the
+ * function will refuse this candidate spline/segment, with the
+ * exception of very small areas under @significant_size. These
+ * micro-area are considered "unsignificant" and accepted (because they
+ * can be created in some conditions, for instance when created curves
+ * cross or start from a same endpoint), and one pixel for each
+ * micro-area will be added to @fill_pixels to be later filled along
+ * with the candidate pixels.
+ *
+ * Returns: #TRUE if @pixels should be added to @mask, #FALSE otherwise.
+ */
+static gboolean
+gimp_line_art_allow_closure (GeglBuffer *mask,
+ GArray *pixels,
+ GList **fill_pixels,
+ int significant_size,
+ int minimum_size)
+{
+ /* A theorem from the paper is that a zone with more than
+ * `2 * (@minimum_size - 1)` edgels (border pixels) will have more
+ * than @minimum_size pixels.
+ * Since we are following the edges of the area, we can therefore stop
+ * earlier if we reach this number of edgels.
+ */
+ const glong max_edgel_count = 2 * minimum_size;
+
+ Pixel *p = (Pixel*) pixels->data;
+ GList *fp = NULL;
+ gint i;
+
+ /* Mark pixels */
+ for (i = 0; i < pixels->len; i++)
+ {
+ if (p->x >= 0 && p->x < gegl_buffer_get_width (mask) &&
+ p->y >= 0 && p->y < gegl_buffer_get_height (mask))
+ {
+ guchar val;
+
+ gegl_buffer_sample (mask, (gint) p->x, (gint) p->y, NULL, &val,
+ NULL, GEGL_SAMPLER_NEAREST, GEGL_ABYSS_NONE);
+ val = val ? 3 : 2;
+ gegl_buffer_set (mask, GEGL_RECTANGLE ((gint) p->x, (gint) p->y, 1, 1), 0,
+ NULL, &val, GEGL_AUTO_ROWSTRIDE);
+ }
+ p++;
+ }
+
+ for (i = 0; i < pixels->len; i++)
+ {
+ Pixel p = g_array_index (pixels, Pixel, i);
+
+ for (int direction = 0; direction < 4; ++direction)
+ {
+ if (p.x >= 0 && p.x < gegl_buffer_get_width (mask) &&
+ p.y >= 0 && p.y < gegl_buffer_get_height (mask) &&
+ border_in_direction (mask, p, direction))
+ {
+ Edgel e;
+ guchar val;
+ glong count;
+ glong area;
+
+ gegl_buffer_sample (mask, (gint) p.x, (gint) p.y, NULL, &val,
+ NULL, GEGL_SAMPLER_NEAREST, GEGL_ABYSS_NONE);
+ if ((gboolean) (val & (4 << direction)))
+ continue;
+
+ gimp_edgel_init (&e);
+ e.x = p.x;
+ e.y = p.y;
+ e.direction = direction;
+
+ count = gimp_edgel_track_mark (mask, e, max_edgel_count);
+ if ((count != -1) && (count <= max_edgel_count))
+ {
+ area = gimp_edgel_region_area (mask, e);
+
+ if (area >= significant_size && area < minimum_size)
+ {
+ gint j;
+
+ /* Remove marks */
+ for (j = 0; j < pixels->len; j++)
+ {
+ Pixel p2 = g_array_index (pixels, Pixel, j);
+
+ if (p2.x >= 0 && p2.x < gegl_buffer_get_width (mask) &&
+ p2.y >= 0 && p2.y < gegl_buffer_get_height (mask))
+ {
+ guchar val;
+
+ gegl_buffer_sample (mask, (gint) p2.x, (gint) p2.y, NULL, &val,
+ NULL, GEGL_SAMPLER_NEAREST, GEGL_ABYSS_NONE);
+ val &= 1;
+ gegl_buffer_set (mask, GEGL_RECTANGLE ((gint) p2.x, (gint) p2.y, 1, 1), 0,
+ NULL, &val, GEGL_AUTO_ROWSTRIDE);
+ }
+ }
+ g_list_free_full (fp, g_free);
+
+ return FALSE;
+ }
+ else if (area > 0 && area < significant_size)
+ {
+ Pixel *np = g_new (Pixel, 1);
+
+ np->x = direction == XPlusDirection ? p.x + 1 : (direction == XMinusDirection ? p.x - 1 : p.x);
+ np->y = direction == YPlusDirection ? p.y + 1 : (direction == YMinusDirection ? p.y - 1 : p.y);
+
+ if (np->x >= 0 && np->x < gegl_buffer_get_width (mask) &&
+ np->y >= 0 && np->y < gegl_buffer_get_height (mask))
+ fp = g_list_prepend (fp, np);
+ else
+ g_free (np);
+ }
+ }
+ }
+ }
+ }
+
+ *fill_pixels = g_list_concat (*fill_pixels, fp);
+ /* Remove marks */
+ for (i = 0; i < pixels->len; i++)
+ {
+ Pixel p = g_array_index (pixels, Pixel, i);
+
+ if (p.x >= 0 && p.x < gegl_buffer_get_width (mask) &&
+ p.y >= 0 && p.y < gegl_buffer_get_height (mask))
+ {
+ guchar val;
+
+ gegl_buffer_sample (mask, (gint) p.x, (gint) p.y, NULL, &val,
+ NULL, GEGL_SAMPLER_NEAREST, GEGL_ABYSS_NONE);
+ val &= 1;
+ gegl_buffer_set (mask, GEGL_RECTANGLE ((gint) p.x, (gint) p.y, 1, 1), 0,
+ NULL, &val, GEGL_AUTO_ROWSTRIDE);
+ }
+ }
+ return TRUE;
+}
+
+static GArray *
+gimp_lineart_line_segment_until_hit (const GeglBuffer *mask,
+ Pixel start,
+ GimpVector2 direction,
+ int size)
+{
+ GeglBuffer *buffer = (GeglBuffer *) mask;
+ gboolean out = FALSE;
+ GArray *points = g_array_new (FALSE, TRUE, sizeof (Pixel));
+ int tmax;
+ GimpVector2 p0 = gimp_vector2_new (start.x, start.y);
+
+ gimp_vector2_mul (&direction, (gdouble) size);
+ direction.x = round (direction.x);
+ direction.y = round (direction.y);
+
+ tmax = MAX (abs ((int) direction.x), abs ((int) direction.y));
+
+ for (int t = 0; t <= tmax; ++t)
+ {
+ GimpVector2 v = gimp_vector2_add_val (p0, gimp_vector2_mul_val (direction, (float)t / tmax));
+ Pixel p;
+
+ p.x = (gint) round (v.x);
+ p.y = (gint) round (v.y);
+ if (p.x >= 0 && p.x < gegl_buffer_get_width (buffer) &&
+ p.y >= 0 && p.y < gegl_buffer_get_height (buffer))
+ {
+ guchar val;
+ gegl_buffer_sample (buffer, p.x, p.y, NULL, &val,
+ NULL, GEGL_SAMPLER_NEAREST, GEGL_ABYSS_NONE);
+ if (out && val)
+ {
+ return points;
+ }
+ out = ! val;
+ }
+ else if (out)
+ {
+ return points;
+ }
+ else
+ {
+ g_array_free (points, TRUE);
+ return g_array_new (FALSE, TRUE, sizeof (Pixel));
+ }
+ g_array_append_val (points, p);
+ }
+
+ g_array_free (points, TRUE);
+ return g_array_new (FALSE, TRUE, sizeof (Pixel));
+}
+
+static gfloat *
+gimp_lineart_estimate_strokes_radii (GeglBuffer *mask,
+ GimpAsync *async)
+{
+ GeglBufferIterator *gi;
+ gfloat *dist;
+ gfloat *thickness;
+ GeglNode *graph;
+ GeglNode *input;
+ GeglNode *op;
+ gint width = gegl_buffer_get_width (mask);
+ gint height = gegl_buffer_get_height (mask);
+
+ /* Compute a distance map for the line art. */
+ dist = g_new (gfloat, width * height);
+
+ graph = gegl_node_new ();
+ input = gegl_node_new_child (graph,
+ "operation", "gegl:buffer-source",
+ "buffer", mask,
+ NULL);
+ op = gegl_node_new_child (graph,
+ "operation", "gegl:distance-transform",
+ "metric", GEGL_DISTANCE_METRIC_EUCLIDEAN,
+ "normalize", FALSE,
+ NULL);
+ gegl_node_connect_to (input, "output", op, "input");
+ gegl_node_blit (op, 1.0, gegl_buffer_get_extent (mask),
+ NULL, dist, GEGL_AUTO_ROWSTRIDE, GEGL_BLIT_DEFAULT);
+ g_object_unref (graph);
+
+ thickness = g_new0 (gfloat, width * height);
+ gi = gegl_buffer_iterator_new (mask, NULL, 0, NULL,
+ GEGL_ACCESS_READ, GEGL_ABYSS_NONE, 1);
+ while (gegl_buffer_iterator_next (gi))
+ {
+ guint8 *m = (guint8*) gi->items[0].data;
+ gint startx = gi->items[0].roi.x;
+ gint starty = gi->items[0].roi.y;
+ gint endy = starty + gi->items[0].roi.height;
+ gint endx = startx + gi->items[0].roi.width;
+ gint x;
+ gint y;
+
+ if (gimp_async_is_canceled (async))
+ {
+ gegl_buffer_iterator_stop (gi);
+
+ gimp_async_abort (async);
+
+ goto end;
+ }
+
+ for (y = starty; y < endy; y++)
+ for (x = startx; x < endx; x++)
+ {
+ if (*m && dist[x + y * width] == 1.0)
+ {
+ gint dx = x;
+ gint dy = y;
+ gfloat d = 1.0;
+ gfloat nd;
+ gboolean neighbour_thicker = TRUE;
+
+ while (neighbour_thicker)
+ {
+ gint px = dx - 1;
+ gint py = dy - 1;
+ gint nx = dx + 1;
+ gint ny = dy + 1;
+
+ neighbour_thicker = FALSE;
+ if (px >= 0)
+ {
+ if ((nd = dist[px + dy * width]) > d)
+ {
+ d = nd;
+ dx = px;
+ neighbour_thicker = TRUE;
+ continue;
+ }
+ if (py >= 0 && (nd = dist[px + py * width]) > d)
+ {
+ d = nd;
+ dx = px;
+ dy = py;
+ neighbour_thicker = TRUE;
+ continue;
+ }
+ if (ny < height && (nd = dist[px + ny * width]) > d)
+ {
+ d = nd;
+ dx = px;
+ dy = ny;
+ neighbour_thicker = TRUE;
+ continue;
+ }
+ }
+ if (nx < width)
+ {
+ if ((nd = dist[nx + dy * width]) > d)
+ {
+ d = nd;
+ dx = nx;
+ neighbour_thicker = TRUE;
+ continue;
+ }
+ if (py >= 0 && (nd = dist[nx + py * width]) > d)
+ {
+ d = nd;
+ dx = nx;
+ dy = py;
+ neighbour_thicker = TRUE;
+ continue;
+ }
+ if (ny < height && (nd = dist[nx + ny * width]) > d)
+ {
+ d = nd;
+ dx = nx;
+ dy = ny;
+ neighbour_thicker = TRUE;
+ continue;
+ }
+ }
+ if (py > 0 && (nd = dist[dx + py * width]) > d)
+ {
+ d = nd;
+ dy = py;
+ neighbour_thicker = TRUE;
+ continue;
+ }
+ if (ny < height && (nd = dist[dx + ny * width]) > d)
+ {
+ d = nd;
+ dy = ny;
+ neighbour_thicker = TRUE;
+ continue;
+ }
+ }
+ thickness[(gint) x + (gint) y * width] = d;
+ }
+ m++;
+ }
+ }
+
+ end:
+ g_free (dist);
+
+ if (gimp_async_is_stopped (async))
+ g_clear_pointer (&thickness, g_free);
+
+ return thickness;
+}
+
+static void
+gimp_line_art_simple_fill (GeglBuffer *buffer,
+ gint x,
+ gint y,
+ gint *counter)
+{
+ guchar val;
+
+ if (x < 0 || x >= gegl_buffer_get_width (buffer) ||
+ y < 0 || y >= gegl_buffer_get_height (buffer) ||
+ *counter <= 0)
+ return;
+
+ gegl_buffer_sample (buffer, x, y, NULL, &val,
+ NULL, GEGL_SAMPLER_NEAREST, GEGL_ABYSS_NONE);
+
+ if (! val)
+ {
+ val = 1;
+ gegl_buffer_set (buffer, GEGL_RECTANGLE (x, y, 1, 1), 0,
+ NULL, &val, GEGL_AUTO_ROWSTRIDE);
+ (*counter)--;
+ gimp_line_art_simple_fill (buffer, x + 1, y, counter);
+ gimp_line_art_simple_fill (buffer, x - 1, y, counter);
+ gimp_line_art_simple_fill (buffer, x, y + 1, counter);
+ gimp_line_art_simple_fill (buffer, x, y - 1, counter);
+ }
+}
+
+static guint
+visited_hash_fun (Pixel *key)
+{
+ /* Cantor pairing function. */
+ return (key->x + key->y) * (key->x + key->y + 1) / 2 + key->y;
+}
+
+static gboolean
+visited_equal_fun (Pixel *e1,
+ Pixel *e2)
+{
+ return (e1->x == e2->x && e1->y == e2->y);
+}
+
+static inline gboolean
+border_in_direction (GeglBuffer *mask,
+ Pixel p,
+ int direction)
+{
+ gint px = (gint) p.x + DeltaX[direction];
+ gint py = (gint) p.y + DeltaY[direction];
+
+ if (px >= 0 && px < gegl_buffer_get_width (mask) &&
+ py >= 0 && py < gegl_buffer_get_height (mask))
+ {
+ guchar val;
+
+ gegl_buffer_sample (mask, px, py, NULL, &val,
+ NULL, GEGL_SAMPLER_NEAREST, GEGL_ABYSS_NONE);
+ return ! ((gboolean) val);
+ }
+ return TRUE;
+}
+
+static inline GimpVector2
+pair2normal (Pixel p,
+ gfloat *normals,
+ gint width)
+{
+ return gimp_vector2_new (normals[((gint) p.x + (gint) p.y * width) * 2],
+ normals[((gint) p.x + (gint) p.y * width) * 2 + 1]);
+}
+/* Edgel functions */
+
+static Edgel *
+gimp_edgel_new (int x,
+ int y,
+ Direction direction)
+{
+ Edgel *edgel = g_new (Edgel, 1);
+
+ edgel->x = x;
+ edgel->y = y;
+ edgel->direction = direction;
+
+ gimp_edgel_init (edgel);
+
+ return edgel;
+}
+
+static void
+gimp_edgel_init (Edgel *edgel)
+{
+ edgel->x_normal = 0;
+ edgel->y_normal = 0;
+ edgel->curvature = 0;
+ edgel->next = edgel->previous = G_MAXUINT;
+}
+
+static void
+gimp_edgel_clear (Edgel **edgel)
+{
+ g_clear_pointer (edgel, g_free);
+}
+
+static int
+gimp_edgel_cmp (const Edgel* e1,
+ const Edgel* e2)
+{
+ gimp_assert (e1 && e2);
+
+ if ((e1->x == e2->x) && (e1->y == e2->y) &&
+ (e1->direction == e2->direction))
+ return 0;
+ else if ((e1->y < e2->y) || (e1->y == e2->y && e1->x < e2->x) ||
+ (e1->y == e2->y && e1->x == e2->x && e1->direction < e2->direction))
+ return -1;
+ else
+ return 1;
+}
+
+static guint
+edgel2index_hash_fun (Edgel *key)
+{
+ /* Cantor pairing function.
+ * Was not sure how to use the direction though. :-/
+ */
+ return (key->x + key->y) * (key->x + key->y + 1) / 2 + key->y * key->direction;
+}
+
+static gboolean
+edgel2index_equal_fun (Edgel *e1,
+ Edgel *e2)
+{
+ return (e1->x == e2->x && e1->y == e2->y &&
+ e1->direction == e2->direction);
+}
+
+/**
+ * @mask;
+ * @edgel:
+ * @size_limit:
+ *
+ * Track a border, marking inner pixels with a bit corresponding to the
+ * edgel traversed (4 << direction) for direction in {0,1,2,3}.
+ * Stop tracking after @size_limit edgels have been visited.
+ *
+ * Returns: Number of visited edgels, or -1 if an already visited edgel
+ * has been encountered.
+ */
+static glong
+gimp_edgel_track_mark (GeglBuffer *mask,
+ Edgel edgel,
+ long size_limit)
+{
+ Edgel start = edgel;
+ long count = 1;
+
+ do
+ {
+ guchar val;
+
+ gimp_edgelset_next8 (mask, &edgel, &edgel);
+ gegl_buffer_sample (mask, edgel.x, edgel.y, NULL, &val,
+ NULL, GEGL_SAMPLER_NEAREST, GEGL_ABYSS_NONE);
+ if (val & 2)
+ {
+ /* Only mark pixels of the spline/segment */
+ if (val & (4 << edgel.direction))
+ return -1;
+
+ /* Mark edgel in pixel (1 == In Mask, 2 == Spline/Segment) */
+ val |= (4 << edgel.direction);
+ gegl_buffer_set (mask, GEGL_RECTANGLE (edgel.x, edgel.y, 1, 1), 0,
+ NULL, &val, GEGL_AUTO_ROWSTRIDE);
+ }
+ if (gimp_edgel_cmp (&edgel, &start) != 0)
+ ++count;
+ }
+ while (gimp_edgel_cmp (&edgel, &start) != 0 && count <= size_limit);
+
+ return count;
+}
+
+/**
+ * gimp_edgel_region_area:
+ * @mask: current state of closed line art buffer.
+ * @start_edgel: edgel to follow.
+ *
+ * Follows a line border, starting from @start_edgel to compute the area
+ * enclosed by this border.
+ * Unfortunately this may return a negative area when the line does not
+ * close a zone. In this case, there is an uncertaincy on the size of
+ * the created zone, and we should consider it a big size.
+ *
+ * Returns: the area enclosed by the followed line, or a negative value
+ * if the zone is not closed (hence actual area unknown).
+ */
+static glong
+gimp_edgel_region_area (const GeglBuffer *mask,
+ Edgel start_edgel)
+{
+ Edgel edgel = start_edgel;
+ glong area = 0;
+
+ do
+ {
+ if (edgel.direction == XPlusDirection)
+ area -= edgel.x;
+ else if (edgel.direction == XMinusDirection)
+ area += edgel.x - 1;
+
+ gimp_edgelset_next8 (mask, &edgel, &edgel);
+ }
+ while (gimp_edgel_cmp (&edgel, &start_edgel) != 0);
+
+ return area;
+}
+
+/* Edgel sets */
+
+static GArray *
+gimp_edgelset_new (GeglBuffer *buffer,
+ GimpAsync *async)
+{
+ GeglBufferIterator *gi;
+ GArray *set;
+ GHashTable *edgel2index;
+ gint width = gegl_buffer_get_width (buffer);
+ gint height = gegl_buffer_get_height (buffer);
+
+ set = g_array_new (TRUE, TRUE, sizeof (Edgel *));
+ g_array_set_clear_func (set, (GDestroyNotify) gimp_edgel_clear);
+
+ if (width <= 1 || height <= 1)
+ return set;
+
+ edgel2index = g_hash_table_new ((GHashFunc) edgel2index_hash_fun,
+ (GEqualFunc) edgel2index_equal_fun);
+
+ gi = gegl_buffer_iterator_new (buffer, GEGL_RECTANGLE (0, 0, width, height),
+ 0, NULL, GEGL_ACCESS_READ, GEGL_ABYSS_NONE, 5);
+ gegl_buffer_iterator_add (gi, buffer, GEGL_RECTANGLE (0, -1, width, height),
+ 0, NULL, GEGL_ACCESS_READ, GEGL_ABYSS_NONE);
+ gegl_buffer_iterator_add (gi, buffer, GEGL_RECTANGLE (0, 1, width, height),
+ 0, NULL, GEGL_ACCESS_READ, GEGL_ABYSS_NONE);
+ gegl_buffer_iterator_add (gi, buffer, GEGL_RECTANGLE (-1, 0, width, height),
+ 0, NULL, GEGL_ACCESS_READ, GEGL_ABYSS_NONE);
+ gegl_buffer_iterator_add (gi, buffer, GEGL_RECTANGLE (1, 0, width, height),
+ 0, NULL, GEGL_ACCESS_READ, GEGL_ABYSS_NONE);
+ while (gegl_buffer_iterator_next (gi))
+ {
+ guint8 *p = (guint8*) gi->items[0].data;
+ guint8 *prevy = (guint8*) gi->items[1].data;
+ guint8 *nexty = (guint8*) gi->items[2].data;
+ guint8 *prevx = (guint8*) gi->items[3].data;
+ guint8 *nextx = (guint8*) gi->items[4].data;
+ gint startx = gi->items[0].roi.x;
+ gint starty = gi->items[0].roi.y;
+ gint endy = starty + gi->items[0].roi.height;
+ gint endx = startx + gi->items[0].roi.width;
+ gint x;
+ gint y;
+
+ if (gimp_async_is_canceled (async))
+ {
+ gegl_buffer_iterator_stop (gi);
+
+ gimp_async_abort (async);
+
+ goto end;
+ }
+
+ for (y = starty; y < endy; y++)
+ for (x = startx; x < endx; x++)
+ {
+ if (*(p++))
+ {
+ if (! *prevy)
+ gimp_edgelset_add (set, x, y, YMinusDirection, edgel2index);
+ if (! *nexty)
+ gimp_edgelset_add (set, x, y, YPlusDirection, edgel2index);
+ if (! *prevx)
+ gimp_edgelset_add (set, x, y, XMinusDirection, edgel2index);
+ if (! *nextx)
+ gimp_edgelset_add (set, x, y, XPlusDirection, edgel2index);
+ }
+ prevy++;
+ nexty++;
+ prevx++;
+ nextx++;
+ }
+ }
+
+ gimp_edgelset_build_graph (set, buffer, edgel2index, async);
+ if (gimp_async_is_stopped (async))
+ goto end;
+
+ gimp_edgelset_init_normals (set);
+
+ end:
+ g_hash_table_destroy (edgel2index);
+
+ if (gimp_async_is_stopped (async))
+ {
+ g_array_free (set, TRUE);
+ set = NULL;
+ }
+
+ return set;
+}
+
+static void
+gimp_edgelset_add (GArray *set,
+ int x,
+ int y,
+ Direction direction,
+ GHashTable *edgel2index)
+{
+ Edgel *edgel = gimp_edgel_new (x, y, direction);
+ unsigned long position = set->len;
+
+ g_array_append_val (set, edgel);
+ g_hash_table_insert (edgel2index, edgel, GUINT_TO_POINTER (position));
+}
+
+static void
+gimp_edgelset_init_normals (GArray *set)
+{
+ Edgel **e = (Edgel**) set->data;
+
+ while (*e)
+ {
+ GimpVector2 n = Direction2Normal[(*e)->direction];
+
+ (*e)->x_normal = n.x;
+ (*e)->y_normal = n.y;
+ e++;
+ }
+}
+
+static void
+gimp_edgelset_smooth_normals (GArray *set,
+ int mask_size,
+ GimpAsync *async)
+{
+ const gfloat sigma = mask_size * 0.775;
+ const gfloat den = 2 * sigma * sigma;
+ gfloat weights[65];
+ GimpVector2 smoothed_normal;
+ gint i;
+
+ gimp_assert (mask_size <= 65);
+
+ weights[0] = 1.0f;
+ for (int i = 1; i <= mask_size; ++i)
+ weights[i] = expf (-(i * i) / den);
+
+ for (i = 0; i < set->len; i++)
+ {
+ Edgel *it = g_array_index (set, Edgel*, i);
+ Edgel *edgel_before = g_array_index (set, Edgel*, it->previous);
+ Edgel *edgel_after = g_array_index (set, Edgel*, it->next);
+ int n = mask_size;
+ int i = 1;
+
+ if (gimp_async_is_canceled (async))
+ {
+ gimp_async_abort (async);
+
+ return;
+ }
+
+ smoothed_normal = Direction2Normal[it->direction];
+ while (n-- && (edgel_after != edgel_before))
+ {
+ smoothed_normal = gimp_vector2_add_val (smoothed_normal,
+ gimp_vector2_mul_val (Direction2Normal[edgel_before->direction], weights[i]));
+ smoothed_normal = gimp_vector2_add_val (smoothed_normal,
+ gimp_vector2_mul_val (Direction2Normal[edgel_after->direction], weights[i]));
+ edgel_before = g_array_index (set, Edgel *, edgel_before->previous);
+ edgel_after = g_array_index (set, Edgel *, edgel_after->next);
+ ++i;
+ }
+ gimp_vector2_normalize (&smoothed_normal);
+ it->x_normal = smoothed_normal.x;
+ it->y_normal = smoothed_normal.y;
+ }
+}
+
+static void
+gimp_edgelset_compute_curvature (GArray *set,
+ GimpAsync *async)
+{
+ gint i;
+
+ for (i = 0; i < set->len; i++)
+ {
+ Edgel *it = g_array_index (set, Edgel*, i);
+ Edgel *previous = g_array_index (set, Edgel *, it->previous);
+ Edgel *next = g_array_index (set, Edgel *, it->next);
+ GimpVector2 n_prev = gimp_vector2_new (previous->x_normal, previous->y_normal);
+ GimpVector2 n_next = gimp_vector2_new (next->x_normal, next->y_normal);
+ GimpVector2 diff = gimp_vector2_mul_val (gimp_vector2_sub_val (n_next, n_prev),
+ 0.5);
+ const float c = gimp_vector2_length_val (diff);
+ const float crossp = n_prev.x * n_next.y - n_prev.y * n_next.x;
+
+ it->curvature = (crossp > 0.0f) ? c : -c;
+
+ if (gimp_async_is_canceled (async))
+ {
+ gimp_async_abort (async);
+
+ return;
+ }
+ }
+}
+
+static void
+gimp_edgelset_build_graph (GArray *set,
+ GeglBuffer *buffer,
+ GHashTable *edgel2index,
+ GimpAsync *async)
+{
+ Edgel edgel;
+ gint i;
+
+ for (i = 0; i < set->len; i++)
+ {
+ Edgel *neighbor;
+ Edgel *it = g_array_index (set, Edgel *, i);
+ guint neighbor_pos;
+
+ if (gimp_async_is_canceled (async))
+ {
+ gimp_async_abort (async);
+
+ return;
+ }
+
+ gimp_edgelset_next8 (buffer, it, &edgel);
+
+ gimp_assert (g_hash_table_contains (edgel2index, &edgel));
+ neighbor_pos = GPOINTER_TO_UINT (g_hash_table_lookup (edgel2index, &edgel));
+ it->next = neighbor_pos;
+ neighbor = g_array_index (set, Edgel *, neighbor_pos);
+ neighbor->previous = i;
+ }
+}
+
+static void
+gimp_edgelset_next8 (const GeglBuffer *buffer,
+ Edgel *it,
+ Edgel *n)
+{
+ guint8 pixels[9];
+
+ n->x = it->x;
+ n->y = it->y;
+ n->direction = it->direction;
+
+ gegl_buffer_get ((GeglBuffer *) buffer,
+ GEGL_RECTANGLE (n->x - 1, n->y - 1, 3, 3),
+ 1.0, NULL, pixels, GEGL_AUTO_ROWSTRIDE,
+ GEGL_ABYSS_NONE);
+ switch (n->direction)
+ {
+ case XPlusDirection:
+ if (pixels[8])
+ {
+ ++(n->y);
+ ++(n->x);
+ n->direction = YMinusDirection;
+ }
+ else if (pixels[7])
+ {
+ ++(n->y);
+ }
+ else
+ {
+ n->direction = YPlusDirection;
+ }
+ break;
+ case YMinusDirection:
+ if (pixels[2])
+ {
+ ++(n->x);
+ --(n->y);
+ n->direction = XMinusDirection;
+ }
+ else if (pixels[5])
+ {
+ ++(n->x);
+ }
+ else
+ {
+ n->direction = XPlusDirection;
+ }
+ break;
+ case XMinusDirection:
+ if (pixels[0])
+ {
+ --(n->x);
+ --(n->y);
+ n->direction = YPlusDirection;
+ }
+ else if (pixels[1])
+ {
+ --(n->y);
+ }
+ else
+ {
+ n->direction = YMinusDirection;
+ }
+ break;
+ case YPlusDirection:
+ if (pixels[6])
+ {
+ --(n->x);
+ ++(n->y);
+ n->direction = XPlusDirection;
+ }
+ else if (pixels[3])
+ {
+ --(n->x);
+ }
+ else
+ {
+ n->direction = XMinusDirection;
+ }
+ break;
+ default:
+ g_return_if_reached ();
+ break;
+ }
+}
diff --git a/app/core/gimplineart.h b/app/core/gimplineart.h
new file mode 100644
index 0000000..cdec1ee
--- /dev/null
+++ b/app/core/gimplineart.h
@@ -0,0 +1,73 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * Copyright (C) 2017 Sébastien Fourey & David Tchumperlé
+ * Copyright (C) 2018 Jehan
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_LINEART__
+#define __GIMP_LINEART__
+
+
+#include "gimpobject.h"
+
+#define GIMP_TYPE_LINE_ART (gimp_line_art_get_type ())
+#define GIMP_LINE_ART(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_LINE_ART, GimpLineArt))
+#define GIMP_LINE_ART_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_LINE_ART, GimpLineArtClass))
+#define GIMP_IS_LINE_ART(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_LINE_ART))
+#define GIMP_IS_LINE_ART_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_LINE_ART))
+#define GIMP_LINE_ART_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_LINE_ART, GimpLineArtClass))
+
+
+typedef struct _GimpLineArtClass GimpLineArtClass;
+typedef struct _GimpLineArtPrivate GimpLineArtPrivate;
+
+struct _GimpLineArt
+{
+ GimpObject parent_instance;
+
+ GimpLineArtPrivate *priv;
+};
+
+struct _GimpLineArtClass
+{
+ GimpObjectClass parent_class;
+
+ /* Signals */
+
+ void (* computing_start) (GimpLineArt *line_art);
+ void (* computing_end) (GimpLineArt *line_art);
+};
+
+
+GType gimp_line_art_get_type (void) G_GNUC_CONST;
+
+GimpLineArt * gimp_line_art_new (void);
+
+void gimp_line_art_bind_gap_length (GimpLineArt *line_art,
+ gboolean bound);
+
+void gimp_line_art_set_input (GimpLineArt *line_art,
+ GimpPickable *pickable);
+GimpPickable * gimp_line_art_get_input (GimpLineArt *line_art);
+void gimp_line_art_freeze (GimpLineArt *line_art);
+void gimp_line_art_thaw (GimpLineArt *line_art);
+gboolean gimp_line_art_is_frozen (GimpLineArt *line_art);
+
+GeglBuffer * gimp_line_art_get (GimpLineArt *line_art,
+ gfloat **distmap);
+
+#endif /* __GIMP_LINEART__ */
diff --git a/app/core/gimplist.c b/app/core/gimplist.c
new file mode 100644
index 0000000..0fbca1f
--- /dev/null
+++ b/app/core/gimplist.c
@@ -0,0 +1,691 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-1997 Spencer Kimball and Peter Mattis
+ *
+ * gimplist.c
+ * Copyright (C) 2001-2016 Michael Natterer <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <string.h> /* strcmp */
+
+#include <glib-object.h>
+
+#include "core-types.h"
+
+#include "gimp-memsize.h"
+#include "gimplist.h"
+
+
+enum
+{
+ PROP_0,
+ PROP_UNIQUE_NAMES,
+ PROP_SORT_FUNC,
+ PROP_APPEND
+};
+
+
+static void gimp_list_finalize (GObject *object);
+static void gimp_list_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_list_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static gint64 gimp_list_get_memsize (GimpObject *object,
+ gint64 *gui_size);
+
+static void gimp_list_add (GimpContainer *container,
+ GimpObject *object);
+static void gimp_list_remove (GimpContainer *container,
+ GimpObject *object);
+static void gimp_list_reorder (GimpContainer *container,
+ GimpObject *object,
+ gint new_index);
+static void gimp_list_clear (GimpContainer *container);
+static gboolean gimp_list_have (GimpContainer *container,
+ GimpObject *object);
+static void gimp_list_foreach (GimpContainer *container,
+ GFunc func,
+ gpointer user_data);
+static GimpObject * gimp_list_search (GimpContainer *container,
+ GimpContainerSearchFunc func,
+ gpointer user_data);
+static gboolean gimp_list_get_unique_names (GimpContainer *container);
+static GimpObject * gimp_list_get_child_by_name (GimpContainer *container,
+ const gchar *name);
+static GimpObject * gimp_list_get_child_by_index (GimpContainer *container,
+ gint index);
+static gint gimp_list_get_child_index (GimpContainer *container,
+ GimpObject *object);
+
+static void gimp_list_uniquefy_name (GimpList *gimp_list,
+ GimpObject *object);
+static void gimp_list_object_renamed (GimpObject *object,
+ GimpList *list);
+
+
+G_DEFINE_TYPE (GimpList, gimp_list, GIMP_TYPE_CONTAINER)
+
+#define parent_class gimp_list_parent_class
+
+
+static void
+gimp_list_class_init (GimpListClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpObjectClass *gimp_object_class = GIMP_OBJECT_CLASS (klass);
+ GimpContainerClass *container_class = GIMP_CONTAINER_CLASS (klass);
+
+ object_class->finalize = gimp_list_finalize;
+ object_class->set_property = gimp_list_set_property;
+ object_class->get_property = gimp_list_get_property;
+
+ gimp_object_class->get_memsize = gimp_list_get_memsize;
+
+ container_class->add = gimp_list_add;
+ container_class->remove = gimp_list_remove;
+ container_class->reorder = gimp_list_reorder;
+ container_class->clear = gimp_list_clear;
+ container_class->have = gimp_list_have;
+ container_class->foreach = gimp_list_foreach;
+ container_class->search = gimp_list_search;
+ container_class->get_unique_names = gimp_list_get_unique_names;
+ container_class->get_child_by_name = gimp_list_get_child_by_name;
+ container_class->get_child_by_index = gimp_list_get_child_by_index;
+ container_class->get_child_index = gimp_list_get_child_index;
+
+ g_object_class_install_property (object_class, PROP_UNIQUE_NAMES,
+ g_param_spec_boolean ("unique-names",
+ NULL, NULL,
+ FALSE,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+
+ g_object_class_install_property (object_class, PROP_SORT_FUNC,
+ g_param_spec_pointer ("sort-func",
+ NULL, NULL,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_APPEND,
+ g_param_spec_boolean ("append",
+ NULL, NULL,
+ FALSE,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+}
+
+static void
+gimp_list_init (GimpList *list)
+{
+ list->queue = g_queue_new ();
+ list->unique_names = FALSE;
+ list->sort_func = NULL;
+ list->append = FALSE;
+}
+
+static void
+gimp_list_finalize (GObject *object)
+{
+ GimpList *list = GIMP_LIST (object);
+
+ if (list->queue)
+ {
+ g_queue_free (list->queue);
+ list->queue = NULL;
+ }
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gimp_list_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpList *list = GIMP_LIST (object);
+
+ switch (property_id)
+ {
+ case PROP_UNIQUE_NAMES:
+ list->unique_names = g_value_get_boolean (value);
+ break;
+ case PROP_SORT_FUNC:
+ gimp_list_set_sort_func (list, g_value_get_pointer (value));
+ break;
+ case PROP_APPEND:
+ list->append = g_value_get_boolean (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_list_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpList *list = GIMP_LIST (object);
+
+ switch (property_id)
+ {
+ case PROP_UNIQUE_NAMES:
+ g_value_set_boolean (value, list->unique_names);
+ break;
+ case PROP_SORT_FUNC:
+ g_value_set_pointer (value, list->sort_func);
+ break;
+ case PROP_APPEND:
+ g_value_set_boolean (value, list->append);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static gint64
+gimp_list_get_memsize (GimpObject *object,
+ gint64 *gui_size)
+{
+ GimpList *list = GIMP_LIST (object);
+ gint64 memsize = 0;
+
+ if (gimp_container_get_policy (GIMP_CONTAINER (list)) ==
+ GIMP_CONTAINER_POLICY_STRONG)
+ {
+ memsize += gimp_g_queue_get_memsize_foreach (list->queue,
+ (GimpMemsizeFunc) gimp_object_get_memsize,
+ gui_size);
+ }
+ else
+ {
+ memsize += gimp_g_queue_get_memsize (list->queue, 0);
+ }
+
+ return memsize + GIMP_OBJECT_CLASS (parent_class)->get_memsize (object,
+ gui_size);
+}
+
+static gint
+gimp_list_sort_func (gconstpointer a,
+ gconstpointer b,
+ gpointer user_data)
+{
+ GCompareFunc func = user_data;
+
+ return func (a, b);
+}
+
+static void
+gimp_list_add (GimpContainer *container,
+ GimpObject *object)
+{
+ GimpList *list = GIMP_LIST (container);
+
+ if (list->unique_names)
+ gimp_list_uniquefy_name (list, object);
+
+ if (list->unique_names || list->sort_func)
+ g_signal_connect (object, "name-changed",
+ G_CALLBACK (gimp_list_object_renamed),
+ list);
+
+ if (list->sort_func)
+ {
+ g_queue_insert_sorted (list->queue, object, gimp_list_sort_func,
+ list->sort_func);
+ }
+ else if (list->append)
+ {
+ g_queue_push_tail (list->queue, object);
+ }
+ else
+ {
+ g_queue_push_head (list->queue, object);
+ }
+
+ GIMP_CONTAINER_CLASS (parent_class)->add (container, object);
+}
+
+static void
+gimp_list_remove (GimpContainer *container,
+ GimpObject *object)
+{
+ GimpList *list = GIMP_LIST (container);
+
+ if (list->unique_names || list->sort_func)
+ g_signal_handlers_disconnect_by_func (object,
+ gimp_list_object_renamed,
+ list);
+
+ g_queue_remove (list->queue, object);
+
+ GIMP_CONTAINER_CLASS (parent_class)->remove (container, object);
+}
+
+static void
+gimp_list_reorder (GimpContainer *container,
+ GimpObject *object,
+ gint new_index)
+{
+ GimpList *list = GIMP_LIST (container);
+
+ g_queue_remove (list->queue, object);
+
+ if (new_index == gimp_container_get_n_children (container) - 1)
+ g_queue_push_tail (list->queue, object);
+ else
+ g_queue_push_nth (list->queue, object, new_index);
+}
+
+static void
+gimp_list_clear (GimpContainer *container)
+{
+ GimpList *list = GIMP_LIST (container);
+
+ while (g_queue_peek_head (list->queue))
+ gimp_container_remove (container, g_queue_peek_head (list->queue));
+}
+
+static gboolean
+gimp_list_have (GimpContainer *container,
+ GimpObject *object)
+{
+ GimpList *list = GIMP_LIST (container);
+
+ return g_queue_find (list->queue, object) ? TRUE : FALSE;
+}
+
+static void
+gimp_list_foreach (GimpContainer *container,
+ GFunc func,
+ gpointer user_data)
+{
+ GimpList *list = GIMP_LIST (container);
+
+ g_queue_foreach (list->queue, func, user_data);
+}
+
+static GimpObject *
+gimp_list_search (GimpContainer *container,
+ GimpContainerSearchFunc func,
+ gpointer user_data)
+{
+ GimpList *list = GIMP_LIST (container);
+ GList *iter;
+
+ iter = list->queue->head;
+
+ while (iter)
+ {
+ GimpObject *object = iter->data;
+
+ iter = g_list_next (iter);
+
+ if (func (object, user_data))
+ return object;
+ }
+
+ return NULL;
+}
+
+static gboolean
+gimp_list_get_unique_names (GimpContainer *container)
+{
+ GimpList *list = GIMP_LIST (container);
+
+ return list->unique_names;
+}
+
+static GimpObject *
+gimp_list_get_child_by_name (GimpContainer *container,
+ const gchar *name)
+{
+ GimpList *list = GIMP_LIST (container);
+ GList *glist;
+
+ for (glist = list->queue->head; glist; glist = g_list_next (glist))
+ {
+ GimpObject *object = glist->data;
+
+ if (! strcmp (gimp_object_get_name (object), name))
+ return object;
+ }
+
+ return NULL;
+}
+
+static GimpObject *
+gimp_list_get_child_by_index (GimpContainer *container,
+ gint index)
+{
+ GimpList *list = GIMP_LIST (container);
+
+ return g_queue_peek_nth (list->queue, index);
+}
+
+static gint
+gimp_list_get_child_index (GimpContainer *container,
+ GimpObject *object)
+{
+ GimpList *list = GIMP_LIST (container);
+
+ return g_queue_index (list->queue, (gpointer) object);
+}
+
+/**
+ * gimp_list_new:
+ * @children_type: the #GType of objects the list is going to hold
+ * @unique_names: if the list should ensure that all its children
+ * have unique names.
+ *
+ * Creates a new #GimpList object. Since #GimpList is a #GimpContainer
+ * implementation, it holds GimpObjects. Thus @children_type must be
+ * GIMP_TYPE_OBJECT or a type derived from it.
+ *
+ * The returned list has the #GIMP_CONTAINER_POLICY_STRONG.
+ *
+ * Return value: a new #GimpList object
+ **/
+GimpContainer *
+gimp_list_new (GType children_type,
+ gboolean unique_names)
+{
+ GimpList *list;
+
+ g_return_val_if_fail (g_type_is_a (children_type, GIMP_TYPE_OBJECT), NULL);
+
+ list = g_object_new (GIMP_TYPE_LIST,
+ "children-type", children_type,
+ "policy", GIMP_CONTAINER_POLICY_STRONG,
+ "unique-names", unique_names ? TRUE : FALSE,
+ NULL);
+
+ /* for debugging purposes only */
+ gimp_object_set_static_name (GIMP_OBJECT (list), g_type_name (children_type));
+
+ return GIMP_CONTAINER (list);
+}
+
+/**
+ * gimp_list_new_weak:
+ * @children_type: the #GType of objects the list is going to hold
+ * @unique_names: if the list should ensure that all its children
+ * have unique names.
+ *
+ * Creates a new #GimpList object. Since #GimpList is a #GimpContainer
+ * implementation, it holds GimpObjects. Thus @children_type must be
+ * GIMP_TYPE_OBJECT or a type derived from it.
+ *
+ * The returned list has the #GIMP_CONTAINER_POLICY_WEAK.
+ *
+ * Return value: a new #GimpList object
+ **/
+GimpContainer *
+gimp_list_new_weak (GType children_type,
+ gboolean unique_names)
+{
+ GimpList *list;
+
+ g_return_val_if_fail (g_type_is_a (children_type, GIMP_TYPE_OBJECT), NULL);
+
+ list = g_object_new (GIMP_TYPE_LIST,
+ "children-type", children_type,
+ "policy", GIMP_CONTAINER_POLICY_WEAK,
+ "unique-names", unique_names ? TRUE : FALSE,
+ NULL);
+
+ /* for debugging purposes only */
+ gimp_object_set_static_name (GIMP_OBJECT (list), g_type_name (children_type));
+
+ return GIMP_CONTAINER (list);
+}
+
+/**
+ * gimp_list_reverse:
+ * @list: a #GimpList
+ *
+ * Reverses the order of elements in a #GimpList.
+ **/
+void
+gimp_list_reverse (GimpList *list)
+{
+ g_return_if_fail (GIMP_IS_LIST (list));
+
+ if (gimp_container_get_n_children (GIMP_CONTAINER (list)) > 1)
+ {
+ gimp_container_freeze (GIMP_CONTAINER (list));
+ g_queue_reverse (list->queue);
+ gimp_container_thaw (GIMP_CONTAINER (list));
+ }
+}
+
+/**
+ * gimp_list_set_sort_func:
+ * @list: a #GimpList
+ * @sort_func: a #GCompareFunc
+ *
+ * Sorts the elements of @list using gimp_list_sort() and remembers the
+ * passed @sort_func in order to keep the list ordered across inserting
+ * or renaming children.
+ **/
+void
+gimp_list_set_sort_func (GimpList *list,
+ GCompareFunc sort_func)
+{
+ g_return_if_fail (GIMP_IS_LIST (list));
+
+ if (sort_func != list->sort_func)
+ {
+ if (sort_func)
+ gimp_list_sort (list, sort_func);
+
+ list->sort_func = sort_func;
+ g_object_notify (G_OBJECT (list), "sort-func");
+ }
+}
+
+/**
+ * gimp_list_get_sort_func:
+ * @list: a #GimpList
+ *
+ * Returns the @list's sort function, see gimp_list_set_sort_func().
+ *
+ * Return Value: The @list's sort function.
+ **/
+GCompareFunc
+gimp_list_get_sort_func (GimpList*list)
+{
+ g_return_val_if_fail (GIMP_IS_LIST (list), NULL);
+
+ return list->sort_func;
+}
+
+/**
+ * gimp_list_sort:
+ * @list: a #GimpList
+ * @sort_func: a #GCompareFunc
+ *
+ * Sorts the elements of a #GimpList according to the given @sort_func.
+ * See g_list_sort() for a detailed description of this function.
+ **/
+void
+gimp_list_sort (GimpList *list,
+ GCompareFunc sort_func)
+{
+ g_return_if_fail (GIMP_IS_LIST (list));
+ g_return_if_fail (sort_func != NULL);
+
+ if (gimp_container_get_n_children (GIMP_CONTAINER (list)) > 1)
+ {
+ gimp_container_freeze (GIMP_CONTAINER (list));
+ g_queue_sort (list->queue, gimp_list_sort_func, sort_func);
+ gimp_container_thaw (GIMP_CONTAINER (list));
+ }
+}
+
+/**
+ * gimp_list_sort_by_name:
+ * @list: a #GimpList
+ *
+ * Sorts the #GimpObject elements of a #GimpList by their names.
+ **/
+void
+gimp_list_sort_by_name (GimpList *list)
+{
+ g_return_if_fail (GIMP_IS_LIST (list));
+
+ gimp_list_sort (list, (GCompareFunc) gimp_object_name_collate);
+}
+
+
+/* private functions */
+
+static void
+gimp_list_uniquefy_name (GimpList *gimp_list,
+ GimpObject *object)
+{
+ gchar *name = (gchar *) gimp_object_get_name (object);
+ GList *list;
+
+ if (! name)
+ return;
+
+ for (list = gimp_list->queue->head; list; list = g_list_next (list))
+ {
+ GimpObject *object2 = list->data;
+ const gchar *name2 = gimp_object_get_name (object2);
+
+ if (object != object2 &&
+ name2 &&
+ ! strcmp (name, name2))
+ break;
+ }
+
+ if (list)
+ {
+ gchar *ext;
+ gchar *new_name = NULL;
+ gint unique_ext = 0;
+
+ name = g_strdup (name);
+
+ ext = strrchr (name, '#');
+
+ if (ext)
+ {
+ gchar ext_str[8];
+
+ unique_ext = atoi (ext + 1);
+
+ g_snprintf (ext_str, sizeof (ext_str), "%d", unique_ext);
+
+ /* check if the extension really is of the form "#<n>" */
+ if (! strcmp (ext_str, ext + 1))
+ {
+ if (ext > name && *(ext - 1) == ' ')
+ ext--;
+
+ *ext = '\0';
+ }
+ else
+ {
+ unique_ext = 0;
+ }
+ }
+
+ do
+ {
+ unique_ext++;
+
+ g_free (new_name);
+
+ new_name = g_strdup_printf ("%s #%d", name, unique_ext);
+
+ for (list = gimp_list->queue->head; list; list = g_list_next (list))
+ {
+ GimpObject *object2 = list->data;
+ const gchar *name2 = gimp_object_get_name (object2);
+
+ if (object != object2 &&
+ name2 &&
+ ! strcmp (new_name, name2))
+ break;
+ }
+ }
+ while (list);
+
+ g_free (name);
+
+ gimp_object_take_name (object, new_name);
+ }
+}
+
+static void
+gimp_list_object_renamed (GimpObject *object,
+ GimpList *list)
+{
+ if (list->unique_names)
+ {
+ g_signal_handlers_block_by_func (object,
+ gimp_list_object_renamed,
+ list);
+
+ gimp_list_uniquefy_name (list, object);
+
+ g_signal_handlers_unblock_by_func (object,
+ gimp_list_object_renamed,
+ list);
+ }
+
+ if (list->sort_func)
+ {
+ GList *glist;
+ gint old_index;
+ gint new_index = 0;
+
+ old_index = g_queue_index (list->queue, object);
+
+ for (glist = list->queue->head; glist; glist = g_list_next (glist))
+ {
+ GimpObject *object2 = GIMP_OBJECT (glist->data);
+
+ if (object == object2)
+ continue;
+
+ if (list->sort_func (object, object2) > 0)
+ new_index++;
+ else
+ break;
+ }
+
+ if (new_index != old_index)
+ gimp_container_reorder (GIMP_CONTAINER (list), object, new_index);
+ }
+}
diff --git a/app/core/gimplist.h b/app/core/gimplist.h
new file mode 100644
index 0000000..194493e
--- /dev/null
+++ b/app/core/gimplist.h
@@ -0,0 +1,70 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-1997 Spencer Kimball and Peter Mattis
+ *
+ * gimplist.h
+ * Copyright (C) 2001-2016 Michael Natterer <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_LIST_H__
+#define __GIMP_LIST_H__
+
+
+#include "gimpcontainer.h"
+
+
+#define GIMP_TYPE_LIST (gimp_list_get_type ())
+#define GIMP_LIST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_LIST, GimpList))
+#define GIMP_LIST_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_LIST, GimpListClass))
+#define GIMP_IS_LIST(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_LIST))
+#define GIMP_IS_LIST_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_LIST))
+#define GIMP_LIST_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_LIST, GimpListClass))
+
+
+typedef struct _GimpListClass GimpListClass;
+
+struct _GimpList
+{
+ GimpContainer parent_instance;
+
+ GQueue *queue;
+ gboolean unique_names;
+ GCompareFunc sort_func;
+ gboolean append;
+};
+
+struct _GimpListClass
+{
+ GimpContainerClass parent_class;
+};
+
+
+GType gimp_list_get_type (void) G_GNUC_CONST;
+
+GimpContainer * gimp_list_new (GType children_type,
+ gboolean unique_names);
+GimpContainer * gimp_list_new_weak (GType children_type,
+ gboolean unique_names);
+
+void gimp_list_reverse (GimpList *list);
+void gimp_list_set_sort_func (GimpList *list,
+ GCompareFunc sort_func);
+GCompareFunc gimp_list_get_sort_func (GimpList *list);
+void gimp_list_sort (GimpList *list,
+ GCompareFunc sort_func);
+void gimp_list_sort_by_name (GimpList *list);
+
+
+#endif /* __GIMP_LIST_H__ */
diff --git a/app/core/gimpmarshal.c b/app/core/gimpmarshal.c
new file mode 100644
index 0000000..e4efb99
--- /dev/null
+++ b/app/core/gimpmarshal.c
@@ -0,0 +1,1901 @@
+/* This file is generated by glib-genmarshal, do not modify it. This code is licensed under the same license as the containing project. Note that it links to GLib, so must comply with the LGPL linking clauses. */
+#include <glib-object.h>
+
+#ifdef G_ENABLE_DEBUG
+#define g_marshal_value_peek_boolean(v) g_value_get_boolean (v)
+#define g_marshal_value_peek_char(v) g_value_get_schar (v)
+#define g_marshal_value_peek_uchar(v) g_value_get_uchar (v)
+#define g_marshal_value_peek_int(v) g_value_get_int (v)
+#define g_marshal_value_peek_uint(v) g_value_get_uint (v)
+#define g_marshal_value_peek_long(v) g_value_get_long (v)
+#define g_marshal_value_peek_ulong(v) g_value_get_ulong (v)
+#define g_marshal_value_peek_int64(v) g_value_get_int64 (v)
+#define g_marshal_value_peek_uint64(v) g_value_get_uint64 (v)
+#define g_marshal_value_peek_enum(v) g_value_get_enum (v)
+#define g_marshal_value_peek_flags(v) g_value_get_flags (v)
+#define g_marshal_value_peek_float(v) g_value_get_float (v)
+#define g_marshal_value_peek_double(v) g_value_get_double (v)
+#define g_marshal_value_peek_string(v) (char*) g_value_get_string (v)
+#define g_marshal_value_peek_param(v) g_value_get_param (v)
+#define g_marshal_value_peek_boxed(v) g_value_get_boxed (v)
+#define g_marshal_value_peek_pointer(v) g_value_get_pointer (v)
+#define g_marshal_value_peek_object(v) g_value_get_object (v)
+#define g_marshal_value_peek_variant(v) g_value_get_variant (v)
+#else /* !G_ENABLE_DEBUG */
+/* WARNING: This code accesses GValues directly, which is UNSUPPORTED API.
+ * Do not access GValues directly in your code. Instead, use the
+ * g_value_get_*() functions
+ */
+#define g_marshal_value_peek_boolean(v) (v)->data[0].v_int
+#define g_marshal_value_peek_char(v) (v)->data[0].v_int
+#define g_marshal_value_peek_uchar(v) (v)->data[0].v_uint
+#define g_marshal_value_peek_int(v) (v)->data[0].v_int
+#define g_marshal_value_peek_uint(v) (v)->data[0].v_uint
+#define g_marshal_value_peek_long(v) (v)->data[0].v_long
+#define g_marshal_value_peek_ulong(v) (v)->data[0].v_ulong
+#define g_marshal_value_peek_int64(v) (v)->data[0].v_int64
+#define g_marshal_value_peek_uint64(v) (v)->data[0].v_uint64
+#define g_marshal_value_peek_enum(v) (v)->data[0].v_long
+#define g_marshal_value_peek_flags(v) (v)->data[0].v_ulong
+#define g_marshal_value_peek_float(v) (v)->data[0].v_float
+#define g_marshal_value_peek_double(v) (v)->data[0].v_double
+#define g_marshal_value_peek_string(v) (v)->data[0].v_pointer
+#define g_marshal_value_peek_param(v) (v)->data[0].v_pointer
+#define g_marshal_value_peek_boxed(v) (v)->data[0].v_pointer
+#define g_marshal_value_peek_pointer(v) (v)->data[0].v_pointer
+#define g_marshal_value_peek_object(v) (v)->data[0].v_pointer
+#define g_marshal_value_peek_variant(v) (v)->data[0].v_pointer
+#endif /* !G_ENABLE_DEBUG */
+
+/* BOOLEAN: BOOLEAN (../../../app/core/gimpmarshal.list:25) */
+/* Prototype for -Wmissing-prototypes */
+G_BEGIN_DECLS
+extern
+void gimp_marshal_BOOLEAN__BOOLEAN (GClosure *closure,
+ GValue *return_value,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint,
+ gpointer marshal_data);
+G_END_DECLS
+void
+gimp_marshal_BOOLEAN__BOOLEAN (GClosure *closure,
+ GValue *return_value,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint G_GNUC_UNUSED,
+ gpointer marshal_data)
+{
+ typedef gboolean (*GMarshalFunc_BOOLEAN__BOOLEAN) (gpointer data1,
+ gboolean arg1,
+ gpointer data2);
+ GCClosure *cc = (GCClosure *) closure;
+ gpointer data1, data2;
+ GMarshalFunc_BOOLEAN__BOOLEAN callback;
+ gboolean v_return;
+
+ g_return_if_fail (return_value != NULL);
+ g_return_if_fail (n_param_values == 2);
+
+ if (G_CCLOSURE_SWAP_DATA (closure))
+ {
+ data1 = closure->data;
+ data2 = g_value_peek_pointer (param_values + 0);
+ }
+ else
+ {
+ data1 = g_value_peek_pointer (param_values + 0);
+ data2 = closure->data;
+ }
+ callback = (GMarshalFunc_BOOLEAN__BOOLEAN) (marshal_data ? marshal_data : cc->callback);
+
+ v_return = callback (data1,
+ g_marshal_value_peek_boolean (param_values + 1),
+ data2);
+
+ g_value_set_boolean (return_value, v_return);
+}
+
+/* BOOLEAN: DOUBLE (../../../app/core/gimpmarshal.list:26) */
+/* Prototype for -Wmissing-prototypes */
+G_BEGIN_DECLS
+extern
+void gimp_marshal_BOOLEAN__DOUBLE (GClosure *closure,
+ GValue *return_value,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint,
+ gpointer marshal_data);
+G_END_DECLS
+void
+gimp_marshal_BOOLEAN__DOUBLE (GClosure *closure,
+ GValue *return_value,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint G_GNUC_UNUSED,
+ gpointer marshal_data)
+{
+ typedef gboolean (*GMarshalFunc_BOOLEAN__DOUBLE) (gpointer data1,
+ gdouble arg1,
+ gpointer data2);
+ GCClosure *cc = (GCClosure *) closure;
+ gpointer data1, data2;
+ GMarshalFunc_BOOLEAN__DOUBLE callback;
+ gboolean v_return;
+
+ g_return_if_fail (return_value != NULL);
+ g_return_if_fail (n_param_values == 2);
+
+ if (G_CCLOSURE_SWAP_DATA (closure))
+ {
+ data1 = closure->data;
+ data2 = g_value_peek_pointer (param_values + 0);
+ }
+ else
+ {
+ data1 = g_value_peek_pointer (param_values + 0);
+ data2 = closure->data;
+ }
+ callback = (GMarshalFunc_BOOLEAN__DOUBLE) (marshal_data ? marshal_data : cc->callback);
+
+ v_return = callback (data1,
+ g_marshal_value_peek_double (param_values + 1),
+ data2);
+
+ g_value_set_boolean (return_value, v_return);
+}
+
+/* BOOLEAN: ENUM, INT (../../../app/core/gimpmarshal.list:27) */
+/* Prototype for -Wmissing-prototypes */
+G_BEGIN_DECLS
+extern
+void gimp_marshal_BOOLEAN__ENUM_INT (GClosure *closure,
+ GValue *return_value,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint,
+ gpointer marshal_data);
+G_END_DECLS
+void
+gimp_marshal_BOOLEAN__ENUM_INT (GClosure *closure,
+ GValue *return_value,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint G_GNUC_UNUSED,
+ gpointer marshal_data)
+{
+ typedef gboolean (*GMarshalFunc_BOOLEAN__ENUM_INT) (gpointer data1,
+ gint arg1,
+ gint arg2,
+ gpointer data2);
+ GCClosure *cc = (GCClosure *) closure;
+ gpointer data1, data2;
+ GMarshalFunc_BOOLEAN__ENUM_INT callback;
+ gboolean v_return;
+
+ g_return_if_fail (return_value != NULL);
+ g_return_if_fail (n_param_values == 3);
+
+ if (G_CCLOSURE_SWAP_DATA (closure))
+ {
+ data1 = closure->data;
+ data2 = g_value_peek_pointer (param_values + 0);
+ }
+ else
+ {
+ data1 = g_value_peek_pointer (param_values + 0);
+ data2 = closure->data;
+ }
+ callback = (GMarshalFunc_BOOLEAN__ENUM_INT) (marshal_data ? marshal_data : cc->callback);
+
+ v_return = callback (data1,
+ g_marshal_value_peek_enum (param_values + 1),
+ g_marshal_value_peek_int (param_values + 2),
+ data2);
+
+ g_value_set_boolean (return_value, v_return);
+}
+
+/* BOOLEAN: INT, UINT, ENUM (../../../app/core/gimpmarshal.list:28) */
+/* Prototype for -Wmissing-prototypes */
+G_BEGIN_DECLS
+extern
+void gimp_marshal_BOOLEAN__INT_UINT_ENUM (GClosure *closure,
+ GValue *return_value,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint,
+ gpointer marshal_data);
+G_END_DECLS
+void
+gimp_marshal_BOOLEAN__INT_UINT_ENUM (GClosure *closure,
+ GValue *return_value,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint G_GNUC_UNUSED,
+ gpointer marshal_data)
+{
+ typedef gboolean (*GMarshalFunc_BOOLEAN__INT_UINT_ENUM) (gpointer data1,
+ gint arg1,
+ guint arg2,
+ gint arg3,
+ gpointer data2);
+ GCClosure *cc = (GCClosure *) closure;
+ gpointer data1, data2;
+ GMarshalFunc_BOOLEAN__INT_UINT_ENUM callback;
+ gboolean v_return;
+
+ g_return_if_fail (return_value != NULL);
+ g_return_if_fail (n_param_values == 4);
+
+ if (G_CCLOSURE_SWAP_DATA (closure))
+ {
+ data1 = closure->data;
+ data2 = g_value_peek_pointer (param_values + 0);
+ }
+ else
+ {
+ data1 = g_value_peek_pointer (param_values + 0);
+ data2 = closure->data;
+ }
+ callback = (GMarshalFunc_BOOLEAN__INT_UINT_ENUM) (marshal_data ? marshal_data : cc->callback);
+
+ v_return = callback (data1,
+ g_marshal_value_peek_int (param_values + 1),
+ g_marshal_value_peek_uint (param_values + 2),
+ g_marshal_value_peek_enum (param_values + 3),
+ data2);
+
+ g_value_set_boolean (return_value, v_return);
+}
+
+/* BOOLEAN: OBJECT (../../../app/core/gimpmarshal.list:29) */
+/* Prototype for -Wmissing-prototypes */
+G_BEGIN_DECLS
+extern
+void gimp_marshal_BOOLEAN__OBJECT (GClosure *closure,
+ GValue *return_value,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint,
+ gpointer marshal_data);
+G_END_DECLS
+void
+gimp_marshal_BOOLEAN__OBJECT (GClosure *closure,
+ GValue *return_value,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint G_GNUC_UNUSED,
+ gpointer marshal_data)
+{
+ typedef gboolean (*GMarshalFunc_BOOLEAN__OBJECT) (gpointer data1,
+ gpointer arg1,
+ gpointer data2);
+ GCClosure *cc = (GCClosure *) closure;
+ gpointer data1, data2;
+ GMarshalFunc_BOOLEAN__OBJECT callback;
+ gboolean v_return;
+
+ g_return_if_fail (return_value != NULL);
+ g_return_if_fail (n_param_values == 2);
+
+ if (G_CCLOSURE_SWAP_DATA (closure))
+ {
+ data1 = closure->data;
+ data2 = g_value_peek_pointer (param_values + 0);
+ }
+ else
+ {
+ data1 = g_value_peek_pointer (param_values + 0);
+ data2 = closure->data;
+ }
+ callback = (GMarshalFunc_BOOLEAN__OBJECT) (marshal_data ? marshal_data : cc->callback);
+
+ v_return = callback (data1,
+ g_marshal_value_peek_object (param_values + 1),
+ data2);
+
+ g_value_set_boolean (return_value, v_return);
+}
+
+/* BOOLEAN: OBJECT, POINTER (../../../app/core/gimpmarshal.list:30) */
+/* Prototype for -Wmissing-prototypes */
+G_BEGIN_DECLS
+extern
+void gimp_marshal_BOOLEAN__OBJECT_POINTER (GClosure *closure,
+ GValue *return_value,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint,
+ gpointer marshal_data);
+G_END_DECLS
+void
+gimp_marshal_BOOLEAN__OBJECT_POINTER (GClosure *closure,
+ GValue *return_value,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint G_GNUC_UNUSED,
+ gpointer marshal_data)
+{
+ typedef gboolean (*GMarshalFunc_BOOLEAN__OBJECT_POINTER) (gpointer data1,
+ gpointer arg1,
+ gpointer arg2,
+ gpointer data2);
+ GCClosure *cc = (GCClosure *) closure;
+ gpointer data1, data2;
+ GMarshalFunc_BOOLEAN__OBJECT_POINTER callback;
+ gboolean v_return;
+
+ g_return_if_fail (return_value != NULL);
+ g_return_if_fail (n_param_values == 3);
+
+ if (G_CCLOSURE_SWAP_DATA (closure))
+ {
+ data1 = closure->data;
+ data2 = g_value_peek_pointer (param_values + 0);
+ }
+ else
+ {
+ data1 = g_value_peek_pointer (param_values + 0);
+ data2 = closure->data;
+ }
+ callback = (GMarshalFunc_BOOLEAN__OBJECT_POINTER) (marshal_data ? marshal_data : cc->callback);
+
+ v_return = callback (data1,
+ g_marshal_value_peek_object (param_values + 1),
+ g_marshal_value_peek_pointer (param_values + 2),
+ data2);
+
+ g_value_set_boolean (return_value, v_return);
+}
+
+/* BOOLEAN: OBJECT, POINTER, STRING (../../../app/core/gimpmarshal.list:31) */
+/* Prototype for -Wmissing-prototypes */
+G_BEGIN_DECLS
+extern
+void gimp_marshal_BOOLEAN__OBJECT_POINTER_STRING (GClosure *closure,
+ GValue *return_value,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint,
+ gpointer marshal_data);
+G_END_DECLS
+void
+gimp_marshal_BOOLEAN__OBJECT_POINTER_STRING (GClosure *closure,
+ GValue *return_value,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint G_GNUC_UNUSED,
+ gpointer marshal_data)
+{
+ typedef gboolean (*GMarshalFunc_BOOLEAN__OBJECT_POINTER_STRING) (gpointer data1,
+ gpointer arg1,
+ gpointer arg2,
+ gpointer arg3,
+ gpointer data2);
+ GCClosure *cc = (GCClosure *) closure;
+ gpointer data1, data2;
+ GMarshalFunc_BOOLEAN__OBJECT_POINTER_STRING callback;
+ gboolean v_return;
+
+ g_return_if_fail (return_value != NULL);
+ g_return_if_fail (n_param_values == 4);
+
+ if (G_CCLOSURE_SWAP_DATA (closure))
+ {
+ data1 = closure->data;
+ data2 = g_value_peek_pointer (param_values + 0);
+ }
+ else
+ {
+ data1 = g_value_peek_pointer (param_values + 0);
+ data2 = closure->data;
+ }
+ callback = (GMarshalFunc_BOOLEAN__OBJECT_POINTER_STRING) (marshal_data ? marshal_data : cc->callback);
+
+ v_return = callback (data1,
+ g_marshal_value_peek_object (param_values + 1),
+ g_marshal_value_peek_pointer (param_values + 2),
+ g_marshal_value_peek_string (param_values + 3),
+ data2);
+
+ g_value_set_boolean (return_value, v_return);
+}
+
+/* BOOLEAN: STRING (../../../app/core/gimpmarshal.list:32) */
+/* Prototype for -Wmissing-prototypes */
+G_BEGIN_DECLS
+extern
+void gimp_marshal_BOOLEAN__STRING (GClosure *closure,
+ GValue *return_value,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint,
+ gpointer marshal_data);
+G_END_DECLS
+void
+gimp_marshal_BOOLEAN__STRING (GClosure *closure,
+ GValue *return_value,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint G_GNUC_UNUSED,
+ gpointer marshal_data)
+{
+ typedef gboolean (*GMarshalFunc_BOOLEAN__STRING) (gpointer data1,
+ gpointer arg1,
+ gpointer data2);
+ GCClosure *cc = (GCClosure *) closure;
+ gpointer data1, data2;
+ GMarshalFunc_BOOLEAN__STRING callback;
+ gboolean v_return;
+
+ g_return_if_fail (return_value != NULL);
+ g_return_if_fail (n_param_values == 2);
+
+ if (G_CCLOSURE_SWAP_DATA (closure))
+ {
+ data1 = closure->data;
+ data2 = g_value_peek_pointer (param_values + 0);
+ }
+ else
+ {
+ data1 = g_value_peek_pointer (param_values + 0);
+ data2 = closure->data;
+ }
+ callback = (GMarshalFunc_BOOLEAN__STRING) (marshal_data ? marshal_data : cc->callback);
+
+ v_return = callback (data1,
+ g_marshal_value_peek_string (param_values + 1),
+ data2);
+
+ g_value_set_boolean (return_value, v_return);
+}
+
+/* BOOLEAN: STRING, FLAGS (../../../app/core/gimpmarshal.list:33) */
+/* Prototype for -Wmissing-prototypes */
+G_BEGIN_DECLS
+extern
+void gimp_marshal_BOOLEAN__STRING_FLAGS (GClosure *closure,
+ GValue *return_value,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint,
+ gpointer marshal_data);
+G_END_DECLS
+void
+gimp_marshal_BOOLEAN__STRING_FLAGS (GClosure *closure,
+ GValue *return_value,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint G_GNUC_UNUSED,
+ gpointer marshal_data)
+{
+ typedef gboolean (*GMarshalFunc_BOOLEAN__STRING_FLAGS) (gpointer data1,
+ gpointer arg1,
+ guint arg2,
+ gpointer data2);
+ GCClosure *cc = (GCClosure *) closure;
+ gpointer data1, data2;
+ GMarshalFunc_BOOLEAN__STRING_FLAGS callback;
+ gboolean v_return;
+
+ g_return_if_fail (return_value != NULL);
+ g_return_if_fail (n_param_values == 3);
+
+ if (G_CCLOSURE_SWAP_DATA (closure))
+ {
+ data1 = closure->data;
+ data2 = g_value_peek_pointer (param_values + 0);
+ }
+ else
+ {
+ data1 = g_value_peek_pointer (param_values + 0);
+ data2 = closure->data;
+ }
+ callback = (GMarshalFunc_BOOLEAN__STRING_FLAGS) (marshal_data ? marshal_data : cc->callback);
+
+ v_return = callback (data1,
+ g_marshal_value_peek_string (param_values + 1),
+ g_marshal_value_peek_flags (param_values + 2),
+ data2);
+
+ g_value_set_boolean (return_value, v_return);
+}
+
+/* INT: DOUBLE (../../../app/core/gimpmarshal.list:35) */
+/* Prototype for -Wmissing-prototypes */
+G_BEGIN_DECLS
+extern
+void gimp_marshal_INT__DOUBLE (GClosure *closure,
+ GValue *return_value,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint,
+ gpointer marshal_data);
+G_END_DECLS
+void
+gimp_marshal_INT__DOUBLE (GClosure *closure,
+ GValue *return_value,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint G_GNUC_UNUSED,
+ gpointer marshal_data)
+{
+ typedef gint (*GMarshalFunc_INT__DOUBLE) (gpointer data1,
+ gdouble arg1,
+ gpointer data2);
+ GCClosure *cc = (GCClosure *) closure;
+ gpointer data1, data2;
+ GMarshalFunc_INT__DOUBLE callback;
+ gint v_return;
+
+ g_return_if_fail (return_value != NULL);
+ g_return_if_fail (n_param_values == 2);
+
+ if (G_CCLOSURE_SWAP_DATA (closure))
+ {
+ data1 = closure->data;
+ data2 = g_value_peek_pointer (param_values + 0);
+ }
+ else
+ {
+ data1 = g_value_peek_pointer (param_values + 0);
+ data2 = closure->data;
+ }
+ callback = (GMarshalFunc_INT__DOUBLE) (marshal_data ? marshal_data : cc->callback);
+
+ v_return = callback (data1,
+ g_marshal_value_peek_double (param_values + 1),
+ data2);
+
+ g_value_set_int (return_value, v_return);
+}
+
+/* VOID: BOOLEAN (../../../app/core/gimpmarshal.list:37) */
+#define gimp_marshal_VOID__BOOLEAN g_cclosure_marshal_VOID__BOOLEAN
+
+/* VOID: BOOLEAN, INT, INT, INT, INT (../../../app/core/gimpmarshal.list:38) */
+/* Prototype for -Wmissing-prototypes */
+G_BEGIN_DECLS
+extern
+void gimp_marshal_VOID__BOOLEAN_INT_INT_INT_INT (GClosure *closure,
+ GValue *return_value,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint,
+ gpointer marshal_data);
+G_END_DECLS
+void
+gimp_marshal_VOID__BOOLEAN_INT_INT_INT_INT (GClosure *closure,
+ GValue *return_value G_GNUC_UNUSED,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint G_GNUC_UNUSED,
+ gpointer marshal_data)
+{
+ typedef void (*GMarshalFunc_VOID__BOOLEAN_INT_INT_INT_INT) (gpointer data1,
+ gboolean arg1,
+ gint arg2,
+ gint arg3,
+ gint arg4,
+ gint arg5,
+ gpointer data2);
+ GCClosure *cc = (GCClosure *) closure;
+ gpointer data1, data2;
+ GMarshalFunc_VOID__BOOLEAN_INT_INT_INT_INT callback;
+
+ g_return_if_fail (n_param_values == 6);
+
+ if (G_CCLOSURE_SWAP_DATA (closure))
+ {
+ data1 = closure->data;
+ data2 = g_value_peek_pointer (param_values + 0);
+ }
+ else
+ {
+ data1 = g_value_peek_pointer (param_values + 0);
+ data2 = closure->data;
+ }
+ callback = (GMarshalFunc_VOID__BOOLEAN_INT_INT_INT_INT) (marshal_data ? marshal_data : cc->callback);
+
+ callback (data1,
+ g_marshal_value_peek_boolean (param_values + 1),
+ g_marshal_value_peek_int (param_values + 2),
+ g_marshal_value_peek_int (param_values + 3),
+ g_marshal_value_peek_int (param_values + 4),
+ g_marshal_value_peek_int (param_values + 5),
+ data2);
+}
+
+/* VOID: BOXED (../../../app/core/gimpmarshal.list:39) */
+#define gimp_marshal_VOID__BOXED g_cclosure_marshal_VOID__BOXED
+
+/* VOID: BOXED, ENUM (../../../app/core/gimpmarshal.list:40) */
+/* Prototype for -Wmissing-prototypes */
+G_BEGIN_DECLS
+extern
+void gimp_marshal_VOID__BOXED_ENUM (GClosure *closure,
+ GValue *return_value,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint,
+ gpointer marshal_data);
+G_END_DECLS
+void
+gimp_marshal_VOID__BOXED_ENUM (GClosure *closure,
+ GValue *return_value G_GNUC_UNUSED,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint G_GNUC_UNUSED,
+ gpointer marshal_data)
+{
+ typedef void (*GMarshalFunc_VOID__BOXED_ENUM) (gpointer data1,
+ gpointer arg1,
+ gint arg2,
+ gpointer data2);
+ GCClosure *cc = (GCClosure *) closure;
+ gpointer data1, data2;
+ GMarshalFunc_VOID__BOXED_ENUM callback;
+
+ g_return_if_fail (n_param_values == 3);
+
+ if (G_CCLOSURE_SWAP_DATA (closure))
+ {
+ data1 = closure->data;
+ data2 = g_value_peek_pointer (param_values + 0);
+ }
+ else
+ {
+ data1 = g_value_peek_pointer (param_values + 0);
+ data2 = closure->data;
+ }
+ callback = (GMarshalFunc_VOID__BOXED_ENUM) (marshal_data ? marshal_data : cc->callback);
+
+ callback (data1,
+ g_marshal_value_peek_boxed (param_values + 1),
+ g_marshal_value_peek_enum (param_values + 2),
+ data2);
+}
+
+/* VOID: DOUBLE (../../../app/core/gimpmarshal.list:41) */
+#define gimp_marshal_VOID__DOUBLE g_cclosure_marshal_VOID__DOUBLE
+
+/* VOID: DOUBLE, DOUBLE (../../../app/core/gimpmarshal.list:42) */
+/* Prototype for -Wmissing-prototypes */
+G_BEGIN_DECLS
+extern
+void gimp_marshal_VOID__DOUBLE_DOUBLE (GClosure *closure,
+ GValue *return_value,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint,
+ gpointer marshal_data);
+G_END_DECLS
+void
+gimp_marshal_VOID__DOUBLE_DOUBLE (GClosure *closure,
+ GValue *return_value G_GNUC_UNUSED,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint G_GNUC_UNUSED,
+ gpointer marshal_data)
+{
+ typedef void (*GMarshalFunc_VOID__DOUBLE_DOUBLE) (gpointer data1,
+ gdouble arg1,
+ gdouble arg2,
+ gpointer data2);
+ GCClosure *cc = (GCClosure *) closure;
+ gpointer data1, data2;
+ GMarshalFunc_VOID__DOUBLE_DOUBLE callback;
+
+ g_return_if_fail (n_param_values == 3);
+
+ if (G_CCLOSURE_SWAP_DATA (closure))
+ {
+ data1 = closure->data;
+ data2 = g_value_peek_pointer (param_values + 0);
+ }
+ else
+ {
+ data1 = g_value_peek_pointer (param_values + 0);
+ data2 = closure->data;
+ }
+ callback = (GMarshalFunc_VOID__DOUBLE_DOUBLE) (marshal_data ? marshal_data : cc->callback);
+
+ callback (data1,
+ g_marshal_value_peek_double (param_values + 1),
+ g_marshal_value_peek_double (param_values + 2),
+ data2);
+}
+
+/* VOID: DOUBLE, DOUBLE, DOUBLE, DOUBLE (../../../app/core/gimpmarshal.list:43) */
+/* Prototype for -Wmissing-prototypes */
+G_BEGIN_DECLS
+extern
+void gimp_marshal_VOID__DOUBLE_DOUBLE_DOUBLE_DOUBLE (GClosure *closure,
+ GValue *return_value,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint,
+ gpointer marshal_data);
+G_END_DECLS
+void
+gimp_marshal_VOID__DOUBLE_DOUBLE_DOUBLE_DOUBLE (GClosure *closure,
+ GValue *return_value G_GNUC_UNUSED,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint G_GNUC_UNUSED,
+ gpointer marshal_data)
+{
+ typedef void (*GMarshalFunc_VOID__DOUBLE_DOUBLE_DOUBLE_DOUBLE) (gpointer data1,
+ gdouble arg1,
+ gdouble arg2,
+ gdouble arg3,
+ gdouble arg4,
+ gpointer data2);
+ GCClosure *cc = (GCClosure *) closure;
+ gpointer data1, data2;
+ GMarshalFunc_VOID__DOUBLE_DOUBLE_DOUBLE_DOUBLE callback;
+
+ g_return_if_fail (n_param_values == 5);
+
+ if (G_CCLOSURE_SWAP_DATA (closure))
+ {
+ data1 = closure->data;
+ data2 = g_value_peek_pointer (param_values + 0);
+ }
+ else
+ {
+ data1 = g_value_peek_pointer (param_values + 0);
+ data2 = closure->data;
+ }
+ callback = (GMarshalFunc_VOID__DOUBLE_DOUBLE_DOUBLE_DOUBLE) (marshal_data ? marshal_data : cc->callback);
+
+ callback (data1,
+ g_marshal_value_peek_double (param_values + 1),
+ g_marshal_value_peek_double (param_values + 2),
+ g_marshal_value_peek_double (param_values + 3),
+ g_marshal_value_peek_double (param_values + 4),
+ data2);
+}
+
+/* VOID: ENUM (../../../app/core/gimpmarshal.list:44) */
+#define gimp_marshal_VOID__ENUM g_cclosure_marshal_VOID__ENUM
+
+/* VOID: ENUM, INT (../../../app/core/gimpmarshal.list:45) */
+/* Prototype for -Wmissing-prototypes */
+G_BEGIN_DECLS
+extern
+void gimp_marshal_VOID__ENUM_INT (GClosure *closure,
+ GValue *return_value,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint,
+ gpointer marshal_data);
+G_END_DECLS
+void
+gimp_marshal_VOID__ENUM_INT (GClosure *closure,
+ GValue *return_value G_GNUC_UNUSED,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint G_GNUC_UNUSED,
+ gpointer marshal_data)
+{
+ typedef void (*GMarshalFunc_VOID__ENUM_INT) (gpointer data1,
+ gint arg1,
+ gint arg2,
+ gpointer data2);
+ GCClosure *cc = (GCClosure *) closure;
+ gpointer data1, data2;
+ GMarshalFunc_VOID__ENUM_INT callback;
+
+ g_return_if_fail (n_param_values == 3);
+
+ if (G_CCLOSURE_SWAP_DATA (closure))
+ {
+ data1 = closure->data;
+ data2 = g_value_peek_pointer (param_values + 0);
+ }
+ else
+ {
+ data1 = g_value_peek_pointer (param_values + 0);
+ data2 = closure->data;
+ }
+ callback = (GMarshalFunc_VOID__ENUM_INT) (marshal_data ? marshal_data : cc->callback);
+
+ callback (data1,
+ g_marshal_value_peek_enum (param_values + 1),
+ g_marshal_value_peek_int (param_values + 2),
+ data2);
+}
+
+/* VOID: ENUM, INT, BOOLEAN (../../../app/core/gimpmarshal.list:46) */
+/* Prototype for -Wmissing-prototypes */
+G_BEGIN_DECLS
+extern
+void gimp_marshal_VOID__ENUM_INT_BOOLEAN (GClosure *closure,
+ GValue *return_value,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint,
+ gpointer marshal_data);
+G_END_DECLS
+void
+gimp_marshal_VOID__ENUM_INT_BOOLEAN (GClosure *closure,
+ GValue *return_value G_GNUC_UNUSED,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint G_GNUC_UNUSED,
+ gpointer marshal_data)
+{
+ typedef void (*GMarshalFunc_VOID__ENUM_INT_BOOLEAN) (gpointer data1,
+ gint arg1,
+ gint arg2,
+ gboolean arg3,
+ gpointer data2);
+ GCClosure *cc = (GCClosure *) closure;
+ gpointer data1, data2;
+ GMarshalFunc_VOID__ENUM_INT_BOOLEAN callback;
+
+ g_return_if_fail (n_param_values == 4);
+
+ if (G_CCLOSURE_SWAP_DATA (closure))
+ {
+ data1 = closure->data;
+ data2 = g_value_peek_pointer (param_values + 0);
+ }
+ else
+ {
+ data1 = g_value_peek_pointer (param_values + 0);
+ data2 = closure->data;
+ }
+ callback = (GMarshalFunc_VOID__ENUM_INT_BOOLEAN) (marshal_data ? marshal_data : cc->callback);
+
+ callback (data1,
+ g_marshal_value_peek_enum (param_values + 1),
+ g_marshal_value_peek_int (param_values + 2),
+ g_marshal_value_peek_boolean (param_values + 3),
+ data2);
+}
+
+/* VOID: ENUM, OBJECT (../../../app/core/gimpmarshal.list:47) */
+/* Prototype for -Wmissing-prototypes */
+G_BEGIN_DECLS
+extern
+void gimp_marshal_VOID__ENUM_OBJECT (GClosure *closure,
+ GValue *return_value,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint,
+ gpointer marshal_data);
+G_END_DECLS
+void
+gimp_marshal_VOID__ENUM_OBJECT (GClosure *closure,
+ GValue *return_value G_GNUC_UNUSED,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint G_GNUC_UNUSED,
+ gpointer marshal_data)
+{
+ typedef void (*GMarshalFunc_VOID__ENUM_OBJECT) (gpointer data1,
+ gint arg1,
+ gpointer arg2,
+ gpointer data2);
+ GCClosure *cc = (GCClosure *) closure;
+ gpointer data1, data2;
+ GMarshalFunc_VOID__ENUM_OBJECT callback;
+
+ g_return_if_fail (n_param_values == 3);
+
+ if (G_CCLOSURE_SWAP_DATA (closure))
+ {
+ data1 = closure->data;
+ data2 = g_value_peek_pointer (param_values + 0);
+ }
+ else
+ {
+ data1 = g_value_peek_pointer (param_values + 0);
+ data2 = closure->data;
+ }
+ callback = (GMarshalFunc_VOID__ENUM_OBJECT) (marshal_data ? marshal_data : cc->callback);
+
+ callback (data1,
+ g_marshal_value_peek_enum (param_values + 1),
+ g_marshal_value_peek_object (param_values + 2),
+ data2);
+}
+
+/* VOID: ENUM, POINTER (../../../app/core/gimpmarshal.list:48) */
+/* Prototype for -Wmissing-prototypes */
+G_BEGIN_DECLS
+extern
+void gimp_marshal_VOID__ENUM_POINTER (GClosure *closure,
+ GValue *return_value,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint,
+ gpointer marshal_data);
+G_END_DECLS
+void
+gimp_marshal_VOID__ENUM_POINTER (GClosure *closure,
+ GValue *return_value G_GNUC_UNUSED,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint G_GNUC_UNUSED,
+ gpointer marshal_data)
+{
+ typedef void (*GMarshalFunc_VOID__ENUM_POINTER) (gpointer data1,
+ gint arg1,
+ gpointer arg2,
+ gpointer data2);
+ GCClosure *cc = (GCClosure *) closure;
+ gpointer data1, data2;
+ GMarshalFunc_VOID__ENUM_POINTER callback;
+
+ g_return_if_fail (n_param_values == 3);
+
+ if (G_CCLOSURE_SWAP_DATA (closure))
+ {
+ data1 = closure->data;
+ data2 = g_value_peek_pointer (param_values + 0);
+ }
+ else
+ {
+ data1 = g_value_peek_pointer (param_values + 0);
+ data2 = closure->data;
+ }
+ callback = (GMarshalFunc_VOID__ENUM_POINTER) (marshal_data ? marshal_data : cc->callback);
+
+ callback (data1,
+ g_marshal_value_peek_enum (param_values + 1),
+ g_marshal_value_peek_pointer (param_values + 2),
+ data2);
+}
+
+/* VOID: FLAGS (../../../app/core/gimpmarshal.list:49) */
+#define gimp_marshal_VOID__FLAGS g_cclosure_marshal_VOID__FLAGS
+
+/* VOID: INT (../../../app/core/gimpmarshal.list:50) */
+#define gimp_marshal_VOID__INT g_cclosure_marshal_VOID__INT
+
+/* VOID: INT, BOOLEAN (../../../app/core/gimpmarshal.list:51) */
+/* Prototype for -Wmissing-prototypes */
+G_BEGIN_DECLS
+extern
+void gimp_marshal_VOID__INT_BOOLEAN (GClosure *closure,
+ GValue *return_value,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint,
+ gpointer marshal_data);
+G_END_DECLS
+void
+gimp_marshal_VOID__INT_BOOLEAN (GClosure *closure,
+ GValue *return_value G_GNUC_UNUSED,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint G_GNUC_UNUSED,
+ gpointer marshal_data)
+{
+ typedef void (*GMarshalFunc_VOID__INT_BOOLEAN) (gpointer data1,
+ gint arg1,
+ gboolean arg2,
+ gpointer data2);
+ GCClosure *cc = (GCClosure *) closure;
+ gpointer data1, data2;
+ GMarshalFunc_VOID__INT_BOOLEAN callback;
+
+ g_return_if_fail (n_param_values == 3);
+
+ if (G_CCLOSURE_SWAP_DATA (closure))
+ {
+ data1 = closure->data;
+ data2 = g_value_peek_pointer (param_values + 0);
+ }
+ else
+ {
+ data1 = g_value_peek_pointer (param_values + 0);
+ data2 = closure->data;
+ }
+ callback = (GMarshalFunc_VOID__INT_BOOLEAN) (marshal_data ? marshal_data : cc->callback);
+
+ callback (data1,
+ g_marshal_value_peek_int (param_values + 1),
+ g_marshal_value_peek_boolean (param_values + 2),
+ data2);
+}
+
+/* VOID: INT, INT (../../../app/core/gimpmarshal.list:52) */
+/* Prototype for -Wmissing-prototypes */
+G_BEGIN_DECLS
+extern
+void gimp_marshal_VOID__INT_INT (GClosure *closure,
+ GValue *return_value,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint,
+ gpointer marshal_data);
+G_END_DECLS
+void
+gimp_marshal_VOID__INT_INT (GClosure *closure,
+ GValue *return_value G_GNUC_UNUSED,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint G_GNUC_UNUSED,
+ gpointer marshal_data)
+{
+ typedef void (*GMarshalFunc_VOID__INT_INT) (gpointer data1,
+ gint arg1,
+ gint arg2,
+ gpointer data2);
+ GCClosure *cc = (GCClosure *) closure;
+ gpointer data1, data2;
+ GMarshalFunc_VOID__INT_INT callback;
+
+ g_return_if_fail (n_param_values == 3);
+
+ if (G_CCLOSURE_SWAP_DATA (closure))
+ {
+ data1 = closure->data;
+ data2 = g_value_peek_pointer (param_values + 0);
+ }
+ else
+ {
+ data1 = g_value_peek_pointer (param_values + 0);
+ data2 = closure->data;
+ }
+ callback = (GMarshalFunc_VOID__INT_INT) (marshal_data ? marshal_data : cc->callback);
+
+ callback (data1,
+ g_marshal_value_peek_int (param_values + 1),
+ g_marshal_value_peek_int (param_values + 2),
+ data2);
+}
+
+/* VOID: INT, INT, INT, INT (../../../app/core/gimpmarshal.list:53) */
+/* Prototype for -Wmissing-prototypes */
+G_BEGIN_DECLS
+extern
+void gimp_marshal_VOID__INT_INT_INT_INT (GClosure *closure,
+ GValue *return_value,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint,
+ gpointer marshal_data);
+G_END_DECLS
+void
+gimp_marshal_VOID__INT_INT_INT_INT (GClosure *closure,
+ GValue *return_value G_GNUC_UNUSED,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint G_GNUC_UNUSED,
+ gpointer marshal_data)
+{
+ typedef void (*GMarshalFunc_VOID__INT_INT_INT_INT) (gpointer data1,
+ gint arg1,
+ gint arg2,
+ gint arg3,
+ gint arg4,
+ gpointer data2);
+ GCClosure *cc = (GCClosure *) closure;
+ gpointer data1, data2;
+ GMarshalFunc_VOID__INT_INT_INT_INT callback;
+
+ g_return_if_fail (n_param_values == 5);
+
+ if (G_CCLOSURE_SWAP_DATA (closure))
+ {
+ data1 = closure->data;
+ data2 = g_value_peek_pointer (param_values + 0);
+ }
+ else
+ {
+ data1 = g_value_peek_pointer (param_values + 0);
+ data2 = closure->data;
+ }
+ callback = (GMarshalFunc_VOID__INT_INT_INT_INT) (marshal_data ? marshal_data : cc->callback);
+
+ callback (data1,
+ g_marshal_value_peek_int (param_values + 1),
+ g_marshal_value_peek_int (param_values + 2),
+ g_marshal_value_peek_int (param_values + 3),
+ g_marshal_value_peek_int (param_values + 4),
+ data2);
+}
+
+/* VOID: INT, INT, BOOLEAN, BOOLEAN (../../../app/core/gimpmarshal.list:54) */
+/* Prototype for -Wmissing-prototypes */
+G_BEGIN_DECLS
+extern
+void gimp_marshal_VOID__INT_INT_BOOLEAN_BOOLEAN (GClosure *closure,
+ GValue *return_value,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint,
+ gpointer marshal_data);
+G_END_DECLS
+void
+gimp_marshal_VOID__INT_INT_BOOLEAN_BOOLEAN (GClosure *closure,
+ GValue *return_value G_GNUC_UNUSED,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint G_GNUC_UNUSED,
+ gpointer marshal_data)
+{
+ typedef void (*GMarshalFunc_VOID__INT_INT_BOOLEAN_BOOLEAN) (gpointer data1,
+ gint arg1,
+ gint arg2,
+ gboolean arg3,
+ gboolean arg4,
+ gpointer data2);
+ GCClosure *cc = (GCClosure *) closure;
+ gpointer data1, data2;
+ GMarshalFunc_VOID__INT_INT_BOOLEAN_BOOLEAN callback;
+
+ g_return_if_fail (n_param_values == 5);
+
+ if (G_CCLOSURE_SWAP_DATA (closure))
+ {
+ data1 = closure->data;
+ data2 = g_value_peek_pointer (param_values + 0);
+ }
+ else
+ {
+ data1 = g_value_peek_pointer (param_values + 0);
+ data2 = closure->data;
+ }
+ callback = (GMarshalFunc_VOID__INT_INT_BOOLEAN_BOOLEAN) (marshal_data ? marshal_data : cc->callback);
+
+ callback (data1,
+ g_marshal_value_peek_int (param_values + 1),
+ g_marshal_value_peek_int (param_values + 2),
+ g_marshal_value_peek_boolean (param_values + 3),
+ g_marshal_value_peek_boolean (param_values + 4),
+ data2);
+}
+
+/* VOID: INT, OBJECT (../../../app/core/gimpmarshal.list:55) */
+/* Prototype for -Wmissing-prototypes */
+G_BEGIN_DECLS
+extern
+void gimp_marshal_VOID__INT_OBJECT (GClosure *closure,
+ GValue *return_value,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint,
+ gpointer marshal_data);
+G_END_DECLS
+void
+gimp_marshal_VOID__INT_OBJECT (GClosure *closure,
+ GValue *return_value G_GNUC_UNUSED,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint G_GNUC_UNUSED,
+ gpointer marshal_data)
+{
+ typedef void (*GMarshalFunc_VOID__INT_OBJECT) (gpointer data1,
+ gint arg1,
+ gpointer arg2,
+ gpointer data2);
+ GCClosure *cc = (GCClosure *) closure;
+ gpointer data1, data2;
+ GMarshalFunc_VOID__INT_OBJECT callback;
+
+ g_return_if_fail (n_param_values == 3);
+
+ if (G_CCLOSURE_SWAP_DATA (closure))
+ {
+ data1 = closure->data;
+ data2 = g_value_peek_pointer (param_values + 0);
+ }
+ else
+ {
+ data1 = g_value_peek_pointer (param_values + 0);
+ data2 = closure->data;
+ }
+ callback = (GMarshalFunc_VOID__INT_OBJECT) (marshal_data ? marshal_data : cc->callback);
+
+ callback (data1,
+ g_marshal_value_peek_int (param_values + 1),
+ g_marshal_value_peek_object (param_values + 2),
+ data2);
+}
+
+/* VOID: OBJECT (../../../app/core/gimpmarshal.list:56) */
+#define gimp_marshal_VOID__OBJECT g_cclosure_marshal_VOID__OBJECT
+
+/* VOID: OBJECT, BOOLEAN (../../../app/core/gimpmarshal.list:57) */
+/* Prototype for -Wmissing-prototypes */
+G_BEGIN_DECLS
+extern
+void gimp_marshal_VOID__OBJECT_BOOLEAN (GClosure *closure,
+ GValue *return_value,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint,
+ gpointer marshal_data);
+G_END_DECLS
+void
+gimp_marshal_VOID__OBJECT_BOOLEAN (GClosure *closure,
+ GValue *return_value G_GNUC_UNUSED,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint G_GNUC_UNUSED,
+ gpointer marshal_data)
+{
+ typedef void (*GMarshalFunc_VOID__OBJECT_BOOLEAN) (gpointer data1,
+ gpointer arg1,
+ gboolean arg2,
+ gpointer data2);
+ GCClosure *cc = (GCClosure *) closure;
+ gpointer data1, data2;
+ GMarshalFunc_VOID__OBJECT_BOOLEAN callback;
+
+ g_return_if_fail (n_param_values == 3);
+
+ if (G_CCLOSURE_SWAP_DATA (closure))
+ {
+ data1 = closure->data;
+ data2 = g_value_peek_pointer (param_values + 0);
+ }
+ else
+ {
+ data1 = g_value_peek_pointer (param_values + 0);
+ data2 = closure->data;
+ }
+ callback = (GMarshalFunc_VOID__OBJECT_BOOLEAN) (marshal_data ? marshal_data : cc->callback);
+
+ callback (data1,
+ g_marshal_value_peek_object (param_values + 1),
+ g_marshal_value_peek_boolean (param_values + 2),
+ data2);
+}
+
+/* VOID: OBJECT, INT (../../../app/core/gimpmarshal.list:58) */
+/* Prototype for -Wmissing-prototypes */
+G_BEGIN_DECLS
+extern
+void gimp_marshal_VOID__OBJECT_INT (GClosure *closure,
+ GValue *return_value,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint,
+ gpointer marshal_data);
+G_END_DECLS
+void
+gimp_marshal_VOID__OBJECT_INT (GClosure *closure,
+ GValue *return_value G_GNUC_UNUSED,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint G_GNUC_UNUSED,
+ gpointer marshal_data)
+{
+ typedef void (*GMarshalFunc_VOID__OBJECT_INT) (gpointer data1,
+ gpointer arg1,
+ gint arg2,
+ gpointer data2);
+ GCClosure *cc = (GCClosure *) closure;
+ gpointer data1, data2;
+ GMarshalFunc_VOID__OBJECT_INT callback;
+
+ g_return_if_fail (n_param_values == 3);
+
+ if (G_CCLOSURE_SWAP_DATA (closure))
+ {
+ data1 = closure->data;
+ data2 = g_value_peek_pointer (param_values + 0);
+ }
+ else
+ {
+ data1 = g_value_peek_pointer (param_values + 0);
+ data2 = closure->data;
+ }
+ callback = (GMarshalFunc_VOID__OBJECT_INT) (marshal_data ? marshal_data : cc->callback);
+
+ callback (data1,
+ g_marshal_value_peek_object (param_values + 1),
+ g_marshal_value_peek_int (param_values + 2),
+ data2);
+}
+
+/* VOID: OBJECT, OBJECT (../../../app/core/gimpmarshal.list:59) */
+/* Prototype for -Wmissing-prototypes */
+G_BEGIN_DECLS
+extern
+void gimp_marshal_VOID__OBJECT_OBJECT (GClosure *closure,
+ GValue *return_value,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint,
+ gpointer marshal_data);
+G_END_DECLS
+void
+gimp_marshal_VOID__OBJECT_OBJECT (GClosure *closure,
+ GValue *return_value G_GNUC_UNUSED,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint G_GNUC_UNUSED,
+ gpointer marshal_data)
+{
+ typedef void (*GMarshalFunc_VOID__OBJECT_OBJECT) (gpointer data1,
+ gpointer arg1,
+ gpointer arg2,
+ gpointer data2);
+ GCClosure *cc = (GCClosure *) closure;
+ gpointer data1, data2;
+ GMarshalFunc_VOID__OBJECT_OBJECT callback;
+
+ g_return_if_fail (n_param_values == 3);
+
+ if (G_CCLOSURE_SWAP_DATA (closure))
+ {
+ data1 = closure->data;
+ data2 = g_value_peek_pointer (param_values + 0);
+ }
+ else
+ {
+ data1 = g_value_peek_pointer (param_values + 0);
+ data2 = closure->data;
+ }
+ callback = (GMarshalFunc_VOID__OBJECT_OBJECT) (marshal_data ? marshal_data : cc->callback);
+
+ callback (data1,
+ g_marshal_value_peek_object (param_values + 1),
+ g_marshal_value_peek_object (param_values + 2),
+ data2);
+}
+
+/* VOID: OBJECT, POINTER (../../../app/core/gimpmarshal.list:60) */
+/* Prototype for -Wmissing-prototypes */
+G_BEGIN_DECLS
+extern
+void gimp_marshal_VOID__OBJECT_POINTER (GClosure *closure,
+ GValue *return_value,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint,
+ gpointer marshal_data);
+G_END_DECLS
+void
+gimp_marshal_VOID__OBJECT_POINTER (GClosure *closure,
+ GValue *return_value G_GNUC_UNUSED,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint G_GNUC_UNUSED,
+ gpointer marshal_data)
+{
+ typedef void (*GMarshalFunc_VOID__OBJECT_POINTER) (gpointer data1,
+ gpointer arg1,
+ gpointer arg2,
+ gpointer data2);
+ GCClosure *cc = (GCClosure *) closure;
+ gpointer data1, data2;
+ GMarshalFunc_VOID__OBJECT_POINTER callback;
+
+ g_return_if_fail (n_param_values == 3);
+
+ if (G_CCLOSURE_SWAP_DATA (closure))
+ {
+ data1 = closure->data;
+ data2 = g_value_peek_pointer (param_values + 0);
+ }
+ else
+ {
+ data1 = g_value_peek_pointer (param_values + 0);
+ data2 = closure->data;
+ }
+ callback = (GMarshalFunc_VOID__OBJECT_POINTER) (marshal_data ? marshal_data : cc->callback);
+
+ callback (data1,
+ g_marshal_value_peek_object (param_values + 1),
+ g_marshal_value_peek_pointer (param_values + 2),
+ data2);
+}
+
+/* VOID: OBJECT, STRING, STRING (../../../app/core/gimpmarshal.list:61) */
+/* Prototype for -Wmissing-prototypes */
+G_BEGIN_DECLS
+extern
+void gimp_marshal_VOID__OBJECT_STRING_STRING (GClosure *closure,
+ GValue *return_value,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint,
+ gpointer marshal_data);
+G_END_DECLS
+void
+gimp_marshal_VOID__OBJECT_STRING_STRING (GClosure *closure,
+ GValue *return_value G_GNUC_UNUSED,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint G_GNUC_UNUSED,
+ gpointer marshal_data)
+{
+ typedef void (*GMarshalFunc_VOID__OBJECT_STRING_STRING) (gpointer data1,
+ gpointer arg1,
+ gpointer arg2,
+ gpointer arg3,
+ gpointer data2);
+ GCClosure *cc = (GCClosure *) closure;
+ gpointer data1, data2;
+ GMarshalFunc_VOID__OBJECT_STRING_STRING callback;
+
+ g_return_if_fail (n_param_values == 4);
+
+ if (G_CCLOSURE_SWAP_DATA (closure))
+ {
+ data1 = closure->data;
+ data2 = g_value_peek_pointer (param_values + 0);
+ }
+ else
+ {
+ data1 = g_value_peek_pointer (param_values + 0);
+ data2 = closure->data;
+ }
+ callback = (GMarshalFunc_VOID__OBJECT_STRING_STRING) (marshal_data ? marshal_data : cc->callback);
+
+ callback (data1,
+ g_marshal_value_peek_object (param_values + 1),
+ g_marshal_value_peek_string (param_values + 2),
+ g_marshal_value_peek_string (param_values + 3),
+ data2);
+}
+
+/* VOID: POINTER (../../../app/core/gimpmarshal.list:62) */
+#define gimp_marshal_VOID__POINTER g_cclosure_marshal_VOID__POINTER
+
+/* VOID: POINTER, BOXED (../../../app/core/gimpmarshal.list:63) */
+/* Prototype for -Wmissing-prototypes */
+G_BEGIN_DECLS
+extern
+void gimp_marshal_VOID__POINTER_BOXED (GClosure *closure,
+ GValue *return_value,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint,
+ gpointer marshal_data);
+G_END_DECLS
+void
+gimp_marshal_VOID__POINTER_BOXED (GClosure *closure,
+ GValue *return_value G_GNUC_UNUSED,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint G_GNUC_UNUSED,
+ gpointer marshal_data)
+{
+ typedef void (*GMarshalFunc_VOID__POINTER_BOXED) (gpointer data1,
+ gpointer arg1,
+ gpointer arg2,
+ gpointer data2);
+ GCClosure *cc = (GCClosure *) closure;
+ gpointer data1, data2;
+ GMarshalFunc_VOID__POINTER_BOXED callback;
+
+ g_return_if_fail (n_param_values == 3);
+
+ if (G_CCLOSURE_SWAP_DATA (closure))
+ {
+ data1 = closure->data;
+ data2 = g_value_peek_pointer (param_values + 0);
+ }
+ else
+ {
+ data1 = g_value_peek_pointer (param_values + 0);
+ data2 = closure->data;
+ }
+ callback = (GMarshalFunc_VOID__POINTER_BOXED) (marshal_data ? marshal_data : cc->callback);
+
+ callback (data1,
+ g_marshal_value_peek_pointer (param_values + 1),
+ g_marshal_value_peek_boxed (param_values + 2),
+ data2);
+}
+
+/* VOID: POINTER, ENUM (../../../app/core/gimpmarshal.list:64) */
+/* Prototype for -Wmissing-prototypes */
+G_BEGIN_DECLS
+extern
+void gimp_marshal_VOID__POINTER_ENUM (GClosure *closure,
+ GValue *return_value,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint,
+ gpointer marshal_data);
+G_END_DECLS
+void
+gimp_marshal_VOID__POINTER_ENUM (GClosure *closure,
+ GValue *return_value G_GNUC_UNUSED,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint G_GNUC_UNUSED,
+ gpointer marshal_data)
+{
+ typedef void (*GMarshalFunc_VOID__POINTER_ENUM) (gpointer data1,
+ gpointer arg1,
+ gint arg2,
+ gpointer data2);
+ GCClosure *cc = (GCClosure *) closure;
+ gpointer data1, data2;
+ GMarshalFunc_VOID__POINTER_ENUM callback;
+
+ g_return_if_fail (n_param_values == 3);
+
+ if (G_CCLOSURE_SWAP_DATA (closure))
+ {
+ data1 = closure->data;
+ data2 = g_value_peek_pointer (param_values + 0);
+ }
+ else
+ {
+ data1 = g_value_peek_pointer (param_values + 0);
+ data2 = closure->data;
+ }
+ callback = (GMarshalFunc_VOID__POINTER_ENUM) (marshal_data ? marshal_data : cc->callback);
+
+ callback (data1,
+ g_marshal_value_peek_pointer (param_values + 1),
+ g_marshal_value_peek_enum (param_values + 2),
+ data2);
+}
+
+/* VOID: POINTER, FLAGS, BOOLEAN (../../../app/core/gimpmarshal.list:65) */
+/* Prototype for -Wmissing-prototypes */
+G_BEGIN_DECLS
+extern
+void gimp_marshal_VOID__POINTER_FLAGS_BOOLEAN (GClosure *closure,
+ GValue *return_value,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint,
+ gpointer marshal_data);
+G_END_DECLS
+void
+gimp_marshal_VOID__POINTER_FLAGS_BOOLEAN (GClosure *closure,
+ GValue *return_value G_GNUC_UNUSED,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint G_GNUC_UNUSED,
+ gpointer marshal_data)
+{
+ typedef void (*GMarshalFunc_VOID__POINTER_FLAGS_BOOLEAN) (gpointer data1,
+ gpointer arg1,
+ guint arg2,
+ gboolean arg3,
+ gpointer data2);
+ GCClosure *cc = (GCClosure *) closure;
+ gpointer data1, data2;
+ GMarshalFunc_VOID__POINTER_FLAGS_BOOLEAN callback;
+
+ g_return_if_fail (n_param_values == 4);
+
+ if (G_CCLOSURE_SWAP_DATA (closure))
+ {
+ data1 = closure->data;
+ data2 = g_value_peek_pointer (param_values + 0);
+ }
+ else
+ {
+ data1 = g_value_peek_pointer (param_values + 0);
+ data2 = closure->data;
+ }
+ callback = (GMarshalFunc_VOID__POINTER_FLAGS_BOOLEAN) (marshal_data ? marshal_data : cc->callback);
+
+ callback (data1,
+ g_marshal_value_peek_pointer (param_values + 1),
+ g_marshal_value_peek_flags (param_values + 2),
+ g_marshal_value_peek_boolean (param_values + 3),
+ data2);
+}
+
+/* VOID: POINTER, OBJECT, ENUM, POINTER, POINTER, BOXED (../../../app/core/gimpmarshal.list:66) */
+/* Prototype for -Wmissing-prototypes */
+G_BEGIN_DECLS
+extern
+void gimp_marshal_VOID__POINTER_OBJECT_ENUM_POINTER_POINTER_BOXED (GClosure *closure,
+ GValue *return_value,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint,
+ gpointer marshal_data);
+G_END_DECLS
+void
+gimp_marshal_VOID__POINTER_OBJECT_ENUM_POINTER_POINTER_BOXED (GClosure *closure,
+ GValue *return_value G_GNUC_UNUSED,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint G_GNUC_UNUSED,
+ gpointer marshal_data)
+{
+ typedef void (*GMarshalFunc_VOID__POINTER_OBJECT_ENUM_POINTER_POINTER_BOXED) (gpointer data1,
+ gpointer arg1,
+ gpointer arg2,
+ gint arg3,
+ gpointer arg4,
+ gpointer arg5,
+ gpointer arg6,
+ gpointer data2);
+ GCClosure *cc = (GCClosure *) closure;
+ gpointer data1, data2;
+ GMarshalFunc_VOID__POINTER_OBJECT_ENUM_POINTER_POINTER_BOXED callback;
+
+ g_return_if_fail (n_param_values == 7);
+
+ if (G_CCLOSURE_SWAP_DATA (closure))
+ {
+ data1 = closure->data;
+ data2 = g_value_peek_pointer (param_values + 0);
+ }
+ else
+ {
+ data1 = g_value_peek_pointer (param_values + 0);
+ data2 = closure->data;
+ }
+ callback = (GMarshalFunc_VOID__POINTER_OBJECT_ENUM_POINTER_POINTER_BOXED) (marshal_data ? marshal_data : cc->callback);
+
+ callback (data1,
+ g_marshal_value_peek_pointer (param_values + 1),
+ g_marshal_value_peek_object (param_values + 2),
+ g_marshal_value_peek_enum (param_values + 3),
+ g_marshal_value_peek_pointer (param_values + 4),
+ g_marshal_value_peek_pointer (param_values + 5),
+ g_marshal_value_peek_boxed (param_values + 6),
+ data2);
+}
+
+/* VOID: POINTER, UINT, FLAGS (../../../app/core/gimpmarshal.list:67) */
+/* Prototype for -Wmissing-prototypes */
+G_BEGIN_DECLS
+extern
+void gimp_marshal_VOID__POINTER_UINT_FLAGS (GClosure *closure,
+ GValue *return_value,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint,
+ gpointer marshal_data);
+G_END_DECLS
+void
+gimp_marshal_VOID__POINTER_UINT_FLAGS (GClosure *closure,
+ GValue *return_value G_GNUC_UNUSED,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint G_GNUC_UNUSED,
+ gpointer marshal_data)
+{
+ typedef void (*GMarshalFunc_VOID__POINTER_UINT_FLAGS) (gpointer data1,
+ gpointer arg1,
+ guint arg2,
+ guint arg3,
+ gpointer data2);
+ GCClosure *cc = (GCClosure *) closure;
+ gpointer data1, data2;
+ GMarshalFunc_VOID__POINTER_UINT_FLAGS callback;
+
+ g_return_if_fail (n_param_values == 4);
+
+ if (G_CCLOSURE_SWAP_DATA (closure))
+ {
+ data1 = closure->data;
+ data2 = g_value_peek_pointer (param_values + 0);
+ }
+ else
+ {
+ data1 = g_value_peek_pointer (param_values + 0);
+ data2 = closure->data;
+ }
+ callback = (GMarshalFunc_VOID__POINTER_UINT_FLAGS) (marshal_data ? marshal_data : cc->callback);
+
+ callback (data1,
+ g_marshal_value_peek_pointer (param_values + 1),
+ g_marshal_value_peek_uint (param_values + 2),
+ g_marshal_value_peek_flags (param_values + 3),
+ data2);
+}
+
+/* VOID: STRING (../../../app/core/gimpmarshal.list:68) */
+#define gimp_marshal_VOID__STRING g_cclosure_marshal_VOID__STRING
+
+/* VOID: STRING, BOOLEAN, UINT, FLAGS (../../../app/core/gimpmarshal.list:69) */
+/* Prototype for -Wmissing-prototypes */
+G_BEGIN_DECLS
+extern
+void gimp_marshal_VOID__STRING_BOOLEAN_UINT_FLAGS (GClosure *closure,
+ GValue *return_value,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint,
+ gpointer marshal_data);
+G_END_DECLS
+void
+gimp_marshal_VOID__STRING_BOOLEAN_UINT_FLAGS (GClosure *closure,
+ GValue *return_value G_GNUC_UNUSED,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint G_GNUC_UNUSED,
+ gpointer marshal_data)
+{
+ typedef void (*GMarshalFunc_VOID__STRING_BOOLEAN_UINT_FLAGS) (gpointer data1,
+ gpointer arg1,
+ gboolean arg2,
+ guint arg3,
+ guint arg4,
+ gpointer data2);
+ GCClosure *cc = (GCClosure *) closure;
+ gpointer data1, data2;
+ GMarshalFunc_VOID__STRING_BOOLEAN_UINT_FLAGS callback;
+
+ g_return_if_fail (n_param_values == 5);
+
+ if (G_CCLOSURE_SWAP_DATA (closure))
+ {
+ data1 = closure->data;
+ data2 = g_value_peek_pointer (param_values + 0);
+ }
+ else
+ {
+ data1 = g_value_peek_pointer (param_values + 0);
+ data2 = closure->data;
+ }
+ callback = (GMarshalFunc_VOID__STRING_BOOLEAN_UINT_FLAGS) (marshal_data ? marshal_data : cc->callback);
+
+ callback (data1,
+ g_marshal_value_peek_string (param_values + 1),
+ g_marshal_value_peek_boolean (param_values + 2),
+ g_marshal_value_peek_uint (param_values + 3),
+ g_marshal_value_peek_flags (param_values + 4),
+ data2);
+}
+
+/* VOID: STRING, DOUBLE, STRING, DOUBLE, STRING (../../../app/core/gimpmarshal.list:70) */
+/* Prototype for -Wmissing-prototypes */
+G_BEGIN_DECLS
+extern
+void gimp_marshal_VOID__STRING_DOUBLE_STRING_DOUBLE_STRING (GClosure *closure,
+ GValue *return_value,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint,
+ gpointer marshal_data);
+G_END_DECLS
+void
+gimp_marshal_VOID__STRING_DOUBLE_STRING_DOUBLE_STRING (GClosure *closure,
+ GValue *return_value G_GNUC_UNUSED,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint G_GNUC_UNUSED,
+ gpointer marshal_data)
+{
+ typedef void (*GMarshalFunc_VOID__STRING_DOUBLE_STRING_DOUBLE_STRING) (gpointer data1,
+ gpointer arg1,
+ gdouble arg2,
+ gpointer arg3,
+ gdouble arg4,
+ gpointer arg5,
+ gpointer data2);
+ GCClosure *cc = (GCClosure *) closure;
+ gpointer data1, data2;
+ GMarshalFunc_VOID__STRING_DOUBLE_STRING_DOUBLE_STRING callback;
+
+ g_return_if_fail (n_param_values == 6);
+
+ if (G_CCLOSURE_SWAP_DATA (closure))
+ {
+ data1 = closure->data;
+ data2 = g_value_peek_pointer (param_values + 0);
+ }
+ else
+ {
+ data1 = g_value_peek_pointer (param_values + 0);
+ data2 = closure->data;
+ }
+ callback = (GMarshalFunc_VOID__STRING_DOUBLE_STRING_DOUBLE_STRING) (marshal_data ? marshal_data : cc->callback);
+
+ callback (data1,
+ g_marshal_value_peek_string (param_values + 1),
+ g_marshal_value_peek_double (param_values + 2),
+ g_marshal_value_peek_string (param_values + 3),
+ g_marshal_value_peek_double (param_values + 4),
+ g_marshal_value_peek_string (param_values + 5),
+ data2);
+}
+
+/* VOID: STRING, FLAGS (../../../app/core/gimpmarshal.list:71) */
+/* Prototype for -Wmissing-prototypes */
+G_BEGIN_DECLS
+extern
+void gimp_marshal_VOID__STRING_FLAGS (GClosure *closure,
+ GValue *return_value,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint,
+ gpointer marshal_data);
+G_END_DECLS
+void
+gimp_marshal_VOID__STRING_FLAGS (GClosure *closure,
+ GValue *return_value G_GNUC_UNUSED,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint G_GNUC_UNUSED,
+ gpointer marshal_data)
+{
+ typedef void (*GMarshalFunc_VOID__STRING_FLAGS) (gpointer data1,
+ gpointer arg1,
+ guint arg2,
+ gpointer data2);
+ GCClosure *cc = (GCClosure *) closure;
+ gpointer data1, data2;
+ GMarshalFunc_VOID__STRING_FLAGS callback;
+
+ g_return_if_fail (n_param_values == 3);
+
+ if (G_CCLOSURE_SWAP_DATA (closure))
+ {
+ data1 = closure->data;
+ data2 = g_value_peek_pointer (param_values + 0);
+ }
+ else
+ {
+ data1 = g_value_peek_pointer (param_values + 0);
+ data2 = closure->data;
+ }
+ callback = (GMarshalFunc_VOID__STRING_FLAGS) (marshal_data ? marshal_data : cc->callback);
+
+ callback (data1,
+ g_marshal_value_peek_string (param_values + 1),
+ g_marshal_value_peek_flags (param_values + 2),
+ data2);
+}
+
+/* VOID: STRING, STRING, STRING (../../../app/core/gimpmarshal.list:72) */
+/* Prototype for -Wmissing-prototypes */
+G_BEGIN_DECLS
+extern
+void gimp_marshal_VOID__STRING_STRING_STRING (GClosure *closure,
+ GValue *return_value,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint,
+ gpointer marshal_data);
+G_END_DECLS
+void
+gimp_marshal_VOID__STRING_STRING_STRING (GClosure *closure,
+ GValue *return_value G_GNUC_UNUSED,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint G_GNUC_UNUSED,
+ gpointer marshal_data)
+{
+ typedef void (*GMarshalFunc_VOID__STRING_STRING_STRING) (gpointer data1,
+ gpointer arg1,
+ gpointer arg2,
+ gpointer arg3,
+ gpointer data2);
+ GCClosure *cc = (GCClosure *) closure;
+ gpointer data1, data2;
+ GMarshalFunc_VOID__STRING_STRING_STRING callback;
+
+ g_return_if_fail (n_param_values == 4);
+
+ if (G_CCLOSURE_SWAP_DATA (closure))
+ {
+ data1 = closure->data;
+ data2 = g_value_peek_pointer (param_values + 0);
+ }
+ else
+ {
+ data1 = g_value_peek_pointer (param_values + 0);
+ data2 = closure->data;
+ }
+ callback = (GMarshalFunc_VOID__STRING_STRING_STRING) (marshal_data ? marshal_data : cc->callback);
+
+ callback (data1,
+ g_marshal_value_peek_string (param_values + 1),
+ g_marshal_value_peek_string (param_values + 2),
+ g_marshal_value_peek_string (param_values + 3),
+ data2);
+}
+
+/* VOID: VARIANT (../../../app/core/gimpmarshal.list:73) */
+#define gimp_marshal_VOID__VARIANT g_cclosure_marshal_VOID__VARIANT
+
+/* VOID: VOID (../../../app/core/gimpmarshal.list:74) */
+#define gimp_marshal_VOID__VOID g_cclosure_marshal_VOID__VOID
+
diff --git a/app/core/gimpmarshal.h b/app/core/gimpmarshal.h
new file mode 100644
index 0000000..e8247a2
--- /dev/null
+++ b/app/core/gimpmarshal.h
@@ -0,0 +1,378 @@
+/* This file is generated by glib-genmarshal, do not modify it. This code is licensed under the same license as the containing project. Note that it links to GLib, so must comply with the LGPL linking clauses. */
+#ifndef __GIMP_MARSHAL_MARSHAL_H__
+#define __GIMP_MARSHAL_MARSHAL_H__
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+/* BOOLEAN: BOOLEAN (../../../app/core/gimpmarshal.list:25) */
+extern
+void gimp_marshal_BOOLEAN__BOOLEAN (GClosure *closure,
+ GValue *return_value,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint,
+ gpointer marshal_data);
+
+/* BOOLEAN: DOUBLE (../../../app/core/gimpmarshal.list:26) */
+extern
+void gimp_marshal_BOOLEAN__DOUBLE (GClosure *closure,
+ GValue *return_value,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint,
+ gpointer marshal_data);
+
+/* BOOLEAN: ENUM, INT (../../../app/core/gimpmarshal.list:27) */
+extern
+void gimp_marshal_BOOLEAN__ENUM_INT (GClosure *closure,
+ GValue *return_value,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint,
+ gpointer marshal_data);
+
+/* BOOLEAN: INT, UINT, ENUM (../../../app/core/gimpmarshal.list:28) */
+extern
+void gimp_marshal_BOOLEAN__INT_UINT_ENUM (GClosure *closure,
+ GValue *return_value,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint,
+ gpointer marshal_data);
+
+/* BOOLEAN: OBJECT (../../../app/core/gimpmarshal.list:29) */
+extern
+void gimp_marshal_BOOLEAN__OBJECT (GClosure *closure,
+ GValue *return_value,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint,
+ gpointer marshal_data);
+
+/* BOOLEAN: OBJECT, POINTER (../../../app/core/gimpmarshal.list:30) */
+extern
+void gimp_marshal_BOOLEAN__OBJECT_POINTER (GClosure *closure,
+ GValue *return_value,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint,
+ gpointer marshal_data);
+
+/* BOOLEAN: OBJECT, POINTER, STRING (../../../app/core/gimpmarshal.list:31) */
+extern
+void gimp_marshal_BOOLEAN__OBJECT_POINTER_STRING (GClosure *closure,
+ GValue *return_value,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint,
+ gpointer marshal_data);
+
+/* BOOLEAN: STRING (../../../app/core/gimpmarshal.list:32) */
+extern
+void gimp_marshal_BOOLEAN__STRING (GClosure *closure,
+ GValue *return_value,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint,
+ gpointer marshal_data);
+
+/* BOOLEAN: STRING, FLAGS (../../../app/core/gimpmarshal.list:33) */
+extern
+void gimp_marshal_BOOLEAN__STRING_FLAGS (GClosure *closure,
+ GValue *return_value,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint,
+ gpointer marshal_data);
+
+/* INT: DOUBLE (../../../app/core/gimpmarshal.list:35) */
+extern
+void gimp_marshal_INT__DOUBLE (GClosure *closure,
+ GValue *return_value,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint,
+ gpointer marshal_data);
+
+/* VOID: BOOLEAN (../../../app/core/gimpmarshal.list:37) */
+#define gimp_marshal_VOID__BOOLEAN g_cclosure_marshal_VOID__BOOLEAN
+
+/* VOID: BOOLEAN, INT, INT, INT, INT (../../../app/core/gimpmarshal.list:38) */
+extern
+void gimp_marshal_VOID__BOOLEAN_INT_INT_INT_INT (GClosure *closure,
+ GValue *return_value,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint,
+ gpointer marshal_data);
+
+/* VOID: BOXED (../../../app/core/gimpmarshal.list:39) */
+#define gimp_marshal_VOID__BOXED g_cclosure_marshal_VOID__BOXED
+
+/* VOID: BOXED, ENUM (../../../app/core/gimpmarshal.list:40) */
+extern
+void gimp_marshal_VOID__BOXED_ENUM (GClosure *closure,
+ GValue *return_value,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint,
+ gpointer marshal_data);
+
+/* VOID: DOUBLE (../../../app/core/gimpmarshal.list:41) */
+#define gimp_marshal_VOID__DOUBLE g_cclosure_marshal_VOID__DOUBLE
+
+/* VOID: DOUBLE, DOUBLE (../../../app/core/gimpmarshal.list:42) */
+extern
+void gimp_marshal_VOID__DOUBLE_DOUBLE (GClosure *closure,
+ GValue *return_value,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint,
+ gpointer marshal_data);
+
+/* VOID: DOUBLE, DOUBLE, DOUBLE, DOUBLE (../../../app/core/gimpmarshal.list:43) */
+extern
+void gimp_marshal_VOID__DOUBLE_DOUBLE_DOUBLE_DOUBLE (GClosure *closure,
+ GValue *return_value,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint,
+ gpointer marshal_data);
+
+/* VOID: ENUM (../../../app/core/gimpmarshal.list:44) */
+#define gimp_marshal_VOID__ENUM g_cclosure_marshal_VOID__ENUM
+
+/* VOID: ENUM, INT (../../../app/core/gimpmarshal.list:45) */
+extern
+void gimp_marshal_VOID__ENUM_INT (GClosure *closure,
+ GValue *return_value,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint,
+ gpointer marshal_data);
+
+/* VOID: ENUM, INT, BOOLEAN (../../../app/core/gimpmarshal.list:46) */
+extern
+void gimp_marshal_VOID__ENUM_INT_BOOLEAN (GClosure *closure,
+ GValue *return_value,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint,
+ gpointer marshal_data);
+
+/* VOID: ENUM, OBJECT (../../../app/core/gimpmarshal.list:47) */
+extern
+void gimp_marshal_VOID__ENUM_OBJECT (GClosure *closure,
+ GValue *return_value,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint,
+ gpointer marshal_data);
+
+/* VOID: ENUM, POINTER (../../../app/core/gimpmarshal.list:48) */
+extern
+void gimp_marshal_VOID__ENUM_POINTER (GClosure *closure,
+ GValue *return_value,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint,
+ gpointer marshal_data);
+
+/* VOID: FLAGS (../../../app/core/gimpmarshal.list:49) */
+#define gimp_marshal_VOID__FLAGS g_cclosure_marshal_VOID__FLAGS
+
+/* VOID: INT (../../../app/core/gimpmarshal.list:50) */
+#define gimp_marshal_VOID__INT g_cclosure_marshal_VOID__INT
+
+/* VOID: INT, BOOLEAN (../../../app/core/gimpmarshal.list:51) */
+extern
+void gimp_marshal_VOID__INT_BOOLEAN (GClosure *closure,
+ GValue *return_value,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint,
+ gpointer marshal_data);
+
+/* VOID: INT, INT (../../../app/core/gimpmarshal.list:52) */
+extern
+void gimp_marshal_VOID__INT_INT (GClosure *closure,
+ GValue *return_value,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint,
+ gpointer marshal_data);
+
+/* VOID: INT, INT, INT, INT (../../../app/core/gimpmarshal.list:53) */
+extern
+void gimp_marshal_VOID__INT_INT_INT_INT (GClosure *closure,
+ GValue *return_value,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint,
+ gpointer marshal_data);
+
+/* VOID: INT, INT, BOOLEAN, BOOLEAN (../../../app/core/gimpmarshal.list:54) */
+extern
+void gimp_marshal_VOID__INT_INT_BOOLEAN_BOOLEAN (GClosure *closure,
+ GValue *return_value,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint,
+ gpointer marshal_data);
+
+/* VOID: INT, OBJECT (../../../app/core/gimpmarshal.list:55) */
+extern
+void gimp_marshal_VOID__INT_OBJECT (GClosure *closure,
+ GValue *return_value,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint,
+ gpointer marshal_data);
+
+/* VOID: OBJECT (../../../app/core/gimpmarshal.list:56) */
+#define gimp_marshal_VOID__OBJECT g_cclosure_marshal_VOID__OBJECT
+
+/* VOID: OBJECT, BOOLEAN (../../../app/core/gimpmarshal.list:57) */
+extern
+void gimp_marshal_VOID__OBJECT_BOOLEAN (GClosure *closure,
+ GValue *return_value,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint,
+ gpointer marshal_data);
+
+/* VOID: OBJECT, INT (../../../app/core/gimpmarshal.list:58) */
+extern
+void gimp_marshal_VOID__OBJECT_INT (GClosure *closure,
+ GValue *return_value,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint,
+ gpointer marshal_data);
+
+/* VOID: OBJECT, OBJECT (../../../app/core/gimpmarshal.list:59) */
+extern
+void gimp_marshal_VOID__OBJECT_OBJECT (GClosure *closure,
+ GValue *return_value,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint,
+ gpointer marshal_data);
+
+/* VOID: OBJECT, POINTER (../../../app/core/gimpmarshal.list:60) */
+extern
+void gimp_marshal_VOID__OBJECT_POINTER (GClosure *closure,
+ GValue *return_value,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint,
+ gpointer marshal_data);
+
+/* VOID: OBJECT, STRING, STRING (../../../app/core/gimpmarshal.list:61) */
+extern
+void gimp_marshal_VOID__OBJECT_STRING_STRING (GClosure *closure,
+ GValue *return_value,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint,
+ gpointer marshal_data);
+
+/* VOID: POINTER (../../../app/core/gimpmarshal.list:62) */
+#define gimp_marshal_VOID__POINTER g_cclosure_marshal_VOID__POINTER
+
+/* VOID: POINTER, BOXED (../../../app/core/gimpmarshal.list:63) */
+extern
+void gimp_marshal_VOID__POINTER_BOXED (GClosure *closure,
+ GValue *return_value,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint,
+ gpointer marshal_data);
+
+/* VOID: POINTER, ENUM (../../../app/core/gimpmarshal.list:64) */
+extern
+void gimp_marshal_VOID__POINTER_ENUM (GClosure *closure,
+ GValue *return_value,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint,
+ gpointer marshal_data);
+
+/* VOID: POINTER, FLAGS, BOOLEAN (../../../app/core/gimpmarshal.list:65) */
+extern
+void gimp_marshal_VOID__POINTER_FLAGS_BOOLEAN (GClosure *closure,
+ GValue *return_value,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint,
+ gpointer marshal_data);
+
+/* VOID: POINTER, OBJECT, ENUM, POINTER, POINTER, BOXED (../../../app/core/gimpmarshal.list:66) */
+extern
+void gimp_marshal_VOID__POINTER_OBJECT_ENUM_POINTER_POINTER_BOXED (GClosure *closure,
+ GValue *return_value,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint,
+ gpointer marshal_data);
+
+/* VOID: POINTER, UINT, FLAGS (../../../app/core/gimpmarshal.list:67) */
+extern
+void gimp_marshal_VOID__POINTER_UINT_FLAGS (GClosure *closure,
+ GValue *return_value,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint,
+ gpointer marshal_data);
+
+/* VOID: STRING (../../../app/core/gimpmarshal.list:68) */
+#define gimp_marshal_VOID__STRING g_cclosure_marshal_VOID__STRING
+
+/* VOID: STRING, BOOLEAN, UINT, FLAGS (../../../app/core/gimpmarshal.list:69) */
+extern
+void gimp_marshal_VOID__STRING_BOOLEAN_UINT_FLAGS (GClosure *closure,
+ GValue *return_value,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint,
+ gpointer marshal_data);
+
+/* VOID: STRING, DOUBLE, STRING, DOUBLE, STRING (../../../app/core/gimpmarshal.list:70) */
+extern
+void gimp_marshal_VOID__STRING_DOUBLE_STRING_DOUBLE_STRING (GClosure *closure,
+ GValue *return_value,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint,
+ gpointer marshal_data);
+
+/* VOID: STRING, FLAGS (../../../app/core/gimpmarshal.list:71) */
+extern
+void gimp_marshal_VOID__STRING_FLAGS (GClosure *closure,
+ GValue *return_value,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint,
+ gpointer marshal_data);
+
+/* VOID: STRING, STRING, STRING (../../../app/core/gimpmarshal.list:72) */
+extern
+void gimp_marshal_VOID__STRING_STRING_STRING (GClosure *closure,
+ GValue *return_value,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint,
+ gpointer marshal_data);
+
+/* VOID: VARIANT (../../../app/core/gimpmarshal.list:73) */
+#define gimp_marshal_VOID__VARIANT g_cclosure_marshal_VOID__VARIANT
+
+/* VOID: VOID (../../../app/core/gimpmarshal.list:74) */
+#define gimp_marshal_VOID__VOID g_cclosure_marshal_VOID__VOID
+
+
+G_END_DECLS
+
+#endif /* __GIMP_MARSHAL_MARSHAL_H__ */
diff --git a/app/core/gimpmarshal.list b/app/core/gimpmarshal.list
new file mode 100644
index 0000000..f09116e
--- /dev/null
+++ b/app/core/gimpmarshal.list
@@ -0,0 +1,74 @@
+# see glib-genmarshal(1) for a detailed description of the file format,
+# possible parameter types are:
+# VOID indicates no return type, or no extra
+# parameters. if VOID is used as the parameter
+# list, no additional parameters may be present.
+# BOOLEAN for boolean types (gboolean)
+# CHAR for signed char types (gchar)
+# UCHAR for unsigned char types (guchar)
+# INT for signed integer types (gint)
+# UINT for unsigned integer types (guint)
+# LONG for signed long integer types (glong)
+# ULONG for unsigned long integer types (gulong)
+# ENUM for enumeration types (gint)
+# FLAGS for flag enumeration types (guint)
+# FLOAT for single-precision float types (gfloat)
+# DOUBLE for double-precision float types (gdouble)
+# STRING for string types (gchar*)
+# BOXED for boxed (anonymous but reference counted) types (GBoxed*)
+# POINTER for anonymous pointer types (gpointer)
+# PARAM for GParamSpec or derived types (GParamSpec*)
+# OBJECT for GObject or derived types (GObject*)
+# NONE deprecated alias for VOID
+# BOOL deprecated alias for BOOLEAN
+
+BOOLEAN: BOOLEAN
+BOOLEAN: DOUBLE
+BOOLEAN: ENUM, INT
+BOOLEAN: INT, UINT, ENUM
+BOOLEAN: OBJECT
+BOOLEAN: OBJECT, POINTER
+BOOLEAN: OBJECT, POINTER, STRING
+BOOLEAN: STRING
+BOOLEAN: STRING, FLAGS
+
+INT: DOUBLE
+
+VOID: BOOLEAN
+VOID: BOOLEAN, INT, INT, INT, INT
+VOID: BOXED
+VOID: BOXED, ENUM
+VOID: DOUBLE
+VOID: DOUBLE, DOUBLE
+VOID: DOUBLE, DOUBLE, DOUBLE, DOUBLE
+VOID: ENUM
+VOID: ENUM, INT
+VOID: ENUM, INT, BOOLEAN
+VOID: ENUM, OBJECT
+VOID: ENUM, POINTER
+VOID: FLAGS
+VOID: INT
+VOID: INT, BOOLEAN
+VOID: INT, INT
+VOID: INT, INT, INT, INT
+VOID: INT, INT, BOOLEAN, BOOLEAN
+VOID: INT, OBJECT
+VOID: OBJECT
+VOID: OBJECT, BOOLEAN
+VOID: OBJECT, INT
+VOID: OBJECT, OBJECT
+VOID: OBJECT, POINTER
+VOID: OBJECT, STRING, STRING
+VOID: POINTER
+VOID: POINTER, BOXED
+VOID: POINTER, ENUM
+VOID: POINTER, FLAGS, BOOLEAN
+VOID: POINTER, OBJECT, ENUM, POINTER, POINTER, BOXED
+VOID: POINTER, UINT, FLAGS
+VOID: STRING
+VOID: STRING, BOOLEAN, UINT, FLAGS
+VOID: STRING, DOUBLE, STRING, DOUBLE, STRING
+VOID: STRING, FLAGS
+VOID: STRING, STRING, STRING
+VOID: VARIANT
+VOID: VOID
diff --git a/app/core/gimpmaskundo.c b/app/core/gimpmaskundo.c
new file mode 100644
index 0000000..d388ee5
--- /dev/null
+++ b/app/core/gimpmaskundo.c
@@ -0,0 +1,292 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "core-types.h"
+
+#include "gegl/gimp-gegl-loops.h"
+
+#include "gimp-memsize.h"
+#include "gimpchannel.h"
+#include "gimpmaskundo.h"
+
+
+enum
+{
+ PROP_0,
+ PROP_CONVERT_FORMAT
+};
+
+
+static void gimp_mask_undo_constructed (GObject *object);
+static void gimp_mask_undo_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_mask_undo_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static gint64 gimp_mask_undo_get_memsize (GimpObject *object,
+ gint64 *gui_size);
+
+static void gimp_mask_undo_pop (GimpUndo *undo,
+ GimpUndoMode undo_mode,
+ GimpUndoAccumulator *accum);
+static void gimp_mask_undo_free (GimpUndo *undo,
+ GimpUndoMode undo_mode);
+
+
+G_DEFINE_TYPE (GimpMaskUndo, gimp_mask_undo, GIMP_TYPE_ITEM_UNDO)
+
+#define parent_class gimp_mask_undo_parent_class
+
+
+static void
+gimp_mask_undo_class_init (GimpMaskUndoClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpObjectClass *gimp_object_class = GIMP_OBJECT_CLASS (klass);
+ GimpUndoClass *undo_class = GIMP_UNDO_CLASS (klass);
+
+ object_class->constructed = gimp_mask_undo_constructed;
+ object_class->set_property = gimp_mask_undo_set_property;
+ object_class->get_property = gimp_mask_undo_get_property;
+
+ gimp_object_class->get_memsize = gimp_mask_undo_get_memsize;
+
+ undo_class->pop = gimp_mask_undo_pop;
+ undo_class->free = gimp_mask_undo_free;
+
+ g_object_class_install_property (object_class, PROP_CONVERT_FORMAT,
+ g_param_spec_boolean ("convert-format",
+ NULL, NULL,
+ FALSE,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+}
+
+static void
+gimp_mask_undo_init (GimpMaskUndo *undo)
+{
+}
+
+static void
+gimp_mask_undo_constructed (GObject *object)
+{
+ GimpMaskUndo *mask_undo = GIMP_MASK_UNDO (object);
+ GimpItem *item;
+ GimpDrawable *drawable;
+
+ G_OBJECT_CLASS (parent_class)->constructed (object);
+
+ gimp_assert (GIMP_IS_CHANNEL (GIMP_ITEM_UNDO (object)->item));
+
+ item = GIMP_ITEM_UNDO (object)->item;
+ drawable = GIMP_DRAWABLE (item);
+
+ mask_undo->format = gimp_drawable_get_format (drawable);
+
+ if (gimp_item_bounds (item,
+ &mask_undo->bounds.x,
+ &mask_undo->bounds.y,
+ &mask_undo->bounds.width,
+ &mask_undo->bounds.height))
+ {
+ GeglBuffer *buffer = gimp_drawable_get_buffer (drawable);
+ GeglRectangle rect;
+
+ gegl_rectangle_align_to_buffer (&rect, &mask_undo->bounds, buffer,
+ GEGL_RECTANGLE_ALIGNMENT_SUPERSET);
+
+ mask_undo->buffer = gegl_buffer_new (GEGL_RECTANGLE (0, 0,
+ rect.width,
+ rect.height),
+ mask_undo->format);
+
+ gimp_gegl_buffer_copy (buffer, &rect, GEGL_ABYSS_NONE,
+ mask_undo->buffer, GEGL_RECTANGLE (0, 0, 0, 0));
+
+ mask_undo->x = rect.x;
+ mask_undo->y = rect.y;
+ }
+}
+
+static void
+gimp_mask_undo_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpMaskUndo *mask_undo = GIMP_MASK_UNDO (object);
+
+ switch (property_id)
+ {
+ case PROP_CONVERT_FORMAT:
+ mask_undo->convert_format = g_value_get_boolean (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_mask_undo_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpMaskUndo *mask_undo = GIMP_MASK_UNDO (object);
+
+ switch (property_id)
+ {
+ case PROP_CONVERT_FORMAT:
+ g_value_set_boolean (value, mask_undo->convert_format);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static gint64
+gimp_mask_undo_get_memsize (GimpObject *object,
+ gint64 *gui_size)
+{
+ GimpMaskUndo *mask_undo = GIMP_MASK_UNDO (object);
+ gint64 memsize = 0;
+
+ memsize += gimp_gegl_buffer_get_memsize (mask_undo->buffer);
+
+ return memsize + GIMP_OBJECT_CLASS (parent_class)->get_memsize (object,
+ gui_size);
+}
+
+static void
+gimp_mask_undo_pop (GimpUndo *undo,
+ GimpUndoMode undo_mode,
+ GimpUndoAccumulator *accum)
+{
+ GimpMaskUndo *mask_undo = GIMP_MASK_UNDO (undo);
+ GimpItem *item = GIMP_ITEM_UNDO (undo)->item;
+ GimpDrawable *drawable = GIMP_DRAWABLE (item);
+ GimpChannel *channel = GIMP_CHANNEL (item);
+ GeglBuffer *new_buffer = NULL;
+ GeglRectangle bounds = {};
+ GeglRectangle rect = {};
+ const Babl *format;
+
+ GIMP_UNDO_CLASS (parent_class)->pop (undo, undo_mode, accum);
+
+ format = gimp_drawable_get_format (drawable);
+
+ if (gimp_item_bounds (item,
+ &bounds.x,
+ &bounds.y,
+ &bounds.width,
+ &bounds.height))
+ {
+ GeglBuffer *buffer = gimp_drawable_get_buffer (drawable);
+
+ gegl_rectangle_align_to_buffer (&rect, &bounds, buffer,
+ GEGL_RECTANGLE_ALIGNMENT_SUPERSET);
+
+ new_buffer = gegl_buffer_new (GEGL_RECTANGLE (0, 0,
+ rect.width, rect.height),
+ format);
+
+ gimp_gegl_buffer_copy (buffer, &rect, GEGL_ABYSS_NONE,
+ new_buffer, GEGL_RECTANGLE (0, 0, 0, 0));
+
+ gegl_buffer_clear (buffer, &rect);
+ }
+
+ if (mask_undo->convert_format)
+ {
+ GeglBuffer *buffer;
+ gint width = gimp_item_get_width (item);
+ gint height = gimp_item_get_height (item);
+
+ buffer = gegl_buffer_new (GEGL_RECTANGLE (0, 0, width, height),
+ mask_undo->format);
+
+ gimp_drawable_set_buffer (drawable, FALSE, NULL, buffer);
+ g_object_unref (buffer);
+ }
+
+ if (mask_undo->buffer)
+ {
+ gimp_gegl_buffer_copy (mask_undo->buffer,
+ NULL,
+ GEGL_ABYSS_NONE,
+ gimp_drawable_get_buffer (drawable),
+ GEGL_RECTANGLE (mask_undo->x, mask_undo->y, 0, 0));
+
+ g_object_unref (mask_undo->buffer);
+ }
+
+ /* invalidate the current bounds and boundary of the mask */
+ gimp_drawable_invalidate_boundary (drawable);
+
+ if (mask_undo->buffer)
+ {
+ channel->empty = FALSE;
+ channel->x1 = mask_undo->bounds.x;
+ channel->y1 = mask_undo->bounds.y;
+ channel->x2 = mask_undo->bounds.x + mask_undo->bounds.width;
+ channel->y2 = mask_undo->bounds.y + mask_undo->bounds.height;
+ }
+ else
+ {
+ channel->empty = TRUE;
+ channel->x1 = 0;
+ channel->y1 = 0;
+ channel->x2 = gimp_item_get_width (item);
+ channel->y2 = gimp_item_get_height (item);
+ }
+
+ /* we know the bounds */
+ channel->bounds_known = TRUE;
+
+ /* set the new mask undo parameters */
+ mask_undo->format = format;
+ mask_undo->buffer = new_buffer;
+ mask_undo->bounds = bounds;
+ mask_undo->x = rect.x;
+ mask_undo->y = rect.y;
+
+ gimp_drawable_update (drawable, 0, 0, -1, -1);
+}
+
+static void
+gimp_mask_undo_free (GimpUndo *undo,
+ GimpUndoMode undo_mode)
+{
+ GimpMaskUndo *mask_undo = GIMP_MASK_UNDO (undo);
+
+ g_clear_object (&mask_undo->buffer);
+
+ GIMP_UNDO_CLASS (parent_class)->free (undo, undo_mode);
+}
diff --git a/app/core/gimpmaskundo.h b/app/core/gimpmaskundo.h
new file mode 100644
index 0000000..53b92e8
--- /dev/null
+++ b/app/core/gimpmaskundo.h
@@ -0,0 +1,58 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_MASK_UNDO_H__
+#define __GIMP_MASK_UNDO_H__
+
+
+#include "gimpitemundo.h"
+
+
+#define GIMP_TYPE_MASK_UNDO (gimp_mask_undo_get_type ())
+#define GIMP_MASK_UNDO(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_MASK_UNDO, GimpMaskUndo))
+#define GIMP_MASK_UNDO_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_MASK_UNDO, GimpMaskUndoClass))
+#define GIMP_IS_MASK_UNDO(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_MASK_UNDO))
+#define GIMP_IS_MASK_UNDO_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_MASK_UNDO))
+#define GIMP_MASK_UNDO_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_MASK_UNDO, GimpMaskUndoClass))
+
+
+typedef struct _GimpMaskUndo GimpMaskUndo;
+typedef struct _GimpMaskUndoClass GimpMaskUndoClass;
+
+struct _GimpMaskUndo
+{
+ GimpItemUndo parent_instance;
+
+ gboolean convert_format;
+
+ const Babl *format;
+ GeglBuffer *buffer;
+ GeglRectangle bounds;
+ gint x;
+ gint y;
+};
+
+struct _GimpMaskUndoClass
+{
+ GimpItemUndoClass parent_class;
+};
+
+
+GType gimp_mask_undo_get_type (void) G_GNUC_CONST;
+
+
+#endif /* __GIMP_MASK_UNDO_H__ */
diff --git a/app/core/gimpmybrush-load.c b/app/core/gimpmybrush-load.c
new file mode 100644
index 0000000..1dbb9f5
--- /dev/null
+++ b/app/core/gimpmybrush-load.c
@@ -0,0 +1,153 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpmybrush-load.c
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include <mypaint-brush.h>
+
+#include "libgimpbase/gimpbase.h"
+
+#include "core-types.h"
+
+#include "gimpmybrush.h"
+#include "gimpmybrush-load.h"
+#include "gimpmybrush-private.h"
+
+#include "gimp-intl.h"
+
+
+/* public functions */
+
+GList *
+gimp_mybrush_load (GimpContext *context,
+ GFile *file,
+ GInputStream *input,
+ GError **error)
+{
+ GimpMybrush *brush = NULL;
+ MyPaintBrush *mypaint_brush;
+ GdkPixbuf *pixbuf;
+ GFileInfo *info;
+ guint64 size;
+ guchar *buffer;
+ gchar *path;
+ gchar *basename;
+ gchar *preview_filename;
+ gchar *p;
+
+ g_return_val_if_fail (G_IS_FILE (file), NULL);
+ g_return_val_if_fail (G_IS_INPUT_STREAM (input), NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ info = g_file_query_info (file,
+ G_FILE_ATTRIBUTE_STANDARD_SIZE,
+ G_FILE_QUERY_INFO_NONE,
+ NULL, error);
+ if (! info)
+ return NULL;
+
+ size = g_file_info_get_attribute_uint64 (info,
+ G_FILE_ATTRIBUTE_STANDARD_SIZE);
+ g_object_unref (info);
+
+ if (size > 32768)
+ {
+ g_set_error (error, GIMP_DATA_ERROR, GIMP_DATA_ERROR_READ,
+ _("MyPaint brush file is unreasonably large, skipping."));
+ return NULL;
+ }
+
+ buffer = g_new0 (guchar, size + 1);
+
+ if (! g_input_stream_read_all (input, buffer, size, NULL, NULL, error))
+ {
+ g_free (buffer);
+ return NULL;
+ }
+
+ mypaint_brush = mypaint_brush_new ();
+ mypaint_brush_from_defaults (mypaint_brush);
+
+ if (! mypaint_brush_from_string (mypaint_brush, (const gchar *) buffer))
+ {
+ g_set_error (error, GIMP_DATA_ERROR, GIMP_DATA_ERROR_READ,
+ _("Failed to deserialize MyPaint brush."));
+ mypaint_brush_unref (mypaint_brush);
+ g_free (buffer);
+ return NULL;
+ }
+
+ path = g_file_get_path (file);
+ basename = g_strndup (path, strlen (path) - 4);
+ g_free (path);
+
+ preview_filename = g_strconcat (basename, "_prev.png", NULL);
+ g_free (basename);
+
+ pixbuf = gdk_pixbuf_new_from_file_at_size (preview_filename,
+ 48, 48, error);
+ g_free (preview_filename);
+
+ basename = g_path_get_basename (gimp_file_get_utf8_name (file));
+
+ basename[strlen (basename) - 4] = '\0';
+ for (p = basename; *p; p++)
+ if (*p == '_' || *p == '-')
+ *p = ' ';
+
+ brush = g_object_new (GIMP_TYPE_MYBRUSH,
+ "name", basename,
+ "mime-type", "image/x-gimp-myb",
+ "icon-pixbuf", pixbuf,
+ NULL);
+
+ g_free (basename);
+
+ if (pixbuf)
+ g_object_unref (pixbuf);
+
+ brush->priv->brush_json = (gchar *) buffer;
+
+ brush->priv->radius =
+ mypaint_brush_get_base_value (mypaint_brush,
+ MYPAINT_BRUSH_SETTING_RADIUS_LOGARITHMIC);
+
+ brush->priv->opaque =
+ mypaint_brush_get_base_value (mypaint_brush,
+ MYPAINT_BRUSH_SETTING_OPAQUE);
+
+ brush->priv->hardness =
+ mypaint_brush_get_base_value (mypaint_brush,
+ MYPAINT_BRUSH_SETTING_HARDNESS);
+
+ brush->priv->eraser =
+ mypaint_brush_get_base_value (mypaint_brush,
+ MYPAINT_BRUSH_SETTING_ERASER) > 0.5f;
+
+ brush->priv->offset_by_random =
+ mypaint_brush_get_base_value (mypaint_brush,
+ MYPAINT_BRUSH_SETTING_OFFSET_BY_RANDOM);
+
+ mypaint_brush_unref (mypaint_brush);
+
+ return g_list_prepend (NULL, brush);
+}
diff --git a/app/core/gimpmybrush-load.h b/app/core/gimpmybrush-load.h
new file mode 100644
index 0000000..4afe203
--- /dev/null
+++ b/app/core/gimpmybrush-load.h
@@ -0,0 +1,33 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpmybrush-load.h
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_MYBRUSH_LOAD_H__
+#define __GIMP_MYBRUSH_LOAD_H__
+
+
+#define GIMP_MYBRUSH_FILE_EXTENSION ".myb"
+
+
+GList * gimp_mybrush_load (GimpContext *context,
+ GFile *file,
+ GInputStream *input,
+ GError **error);
+
+
+#endif /* __GIMP_MYBRUSH_LOAD_H__ */
diff --git a/app/core/gimpmybrush-private.h b/app/core/gimpmybrush-private.h
new file mode 100644
index 0000000..7966c0b
--- /dev/null
+++ b/app/core/gimpmybrush-private.h
@@ -0,0 +1,35 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpmybrush-private.h
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_MYBRUSH_PRIVATE_H__
+#define __GIMP_MYBRUSH_PRIVATE_H__
+
+
+struct _GimpMybrushPrivate
+{
+ gchar *brush_json;
+ gdouble radius;
+ gdouble opaque;
+ gdouble hardness;
+ gdouble offset_by_random;
+ gboolean eraser;
+};
+
+
+#endif /* __GIMP_MYBRUSH_PRIVATE_H__ */
diff --git a/app/core/gimpmybrush.c b/app/core/gimpmybrush.c
new file mode 100644
index 0000000..a26c86b
--- /dev/null
+++ b/app/core/gimpmybrush.c
@@ -0,0 +1,281 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpmybrush.c
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <cairo.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "core-types.h"
+
+#include "gimp-memsize.h"
+#include "gimpmybrush.h"
+#include "gimpmybrush-load.h"
+#include "gimpmybrush-private.h"
+#include "gimptagged.h"
+
+#include "gimp-intl.h"
+
+
+static void gimp_mybrush_tagged_iface_init (GimpTaggedInterface *iface);
+
+static void gimp_mybrush_finalize (GObject *object);
+static void gimp_mybrush_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_mybrush_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static gint64 gimp_mybrush_get_memsize (GimpObject *object,
+ gint64 *gui_size);
+
+static gchar * gimp_mybrush_get_description (GimpViewable *viewable,
+ gchar **tooltip);
+
+static void gimp_mybrush_dirty (GimpData *data);
+static const gchar * gimp_mybrush_get_extension (GimpData *data);
+
+static gchar * gimp_mybrush_get_checksum (GimpTagged *tagged);
+
+
+G_DEFINE_TYPE_WITH_CODE (GimpMybrush, gimp_mybrush, GIMP_TYPE_DATA,
+ G_ADD_PRIVATE (GimpMybrush)
+ G_IMPLEMENT_INTERFACE (GIMP_TYPE_TAGGED,
+ gimp_mybrush_tagged_iface_init))
+
+#define parent_class gimp_mybrush_parent_class
+
+
+static void
+gimp_mybrush_class_init (GimpMybrushClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpObjectClass *gimp_object_class = GIMP_OBJECT_CLASS (klass);
+ GimpViewableClass *viewable_class = GIMP_VIEWABLE_CLASS (klass);
+ GimpDataClass *data_class = GIMP_DATA_CLASS (klass);
+
+ object_class->finalize = gimp_mybrush_finalize;
+ object_class->get_property = gimp_mybrush_get_property;
+ object_class->set_property = gimp_mybrush_set_property;
+
+ gimp_object_class->get_memsize = gimp_mybrush_get_memsize;
+
+ viewable_class->default_icon_name = "gimp-tool-mypaint-brush";
+ viewable_class->get_description = gimp_mybrush_get_description;
+
+ data_class->dirty = gimp_mybrush_dirty;
+ data_class->get_extension = gimp_mybrush_get_extension;
+}
+
+static void
+gimp_mybrush_tagged_iface_init (GimpTaggedInterface *iface)
+{
+ iface->get_checksum = gimp_mybrush_get_checksum;
+}
+
+static void
+gimp_mybrush_init (GimpMybrush *brush)
+{
+ brush->priv = gimp_mybrush_get_instance_private (brush);
+
+ brush->priv->radius = 1.0;
+ brush->priv->opaque = 1.0;
+ brush->priv->hardness = 1.0;
+ brush->priv->eraser = FALSE;
+}
+
+static void
+gimp_mybrush_finalize (GObject *object)
+{
+ GimpMybrush *brush = GIMP_MYBRUSH (object);
+
+ g_clear_pointer (&brush->priv->brush_json, g_free);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gimp_mybrush_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id)
+ {
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_mybrush_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id)
+ {
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static gint64
+gimp_mybrush_get_memsize (GimpObject *object,
+ gint64 *gui_size)
+{
+ GimpMybrush *brush = GIMP_MYBRUSH (object);
+ gint64 memsize = 0;
+
+ memsize += gimp_string_get_memsize (brush->priv->brush_json);
+
+ return memsize + GIMP_OBJECT_CLASS (parent_class)->get_memsize (object,
+ gui_size);
+}
+
+static gchar *
+gimp_mybrush_get_description (GimpViewable *viewable,
+ gchar **tooltip)
+{
+ GimpMybrush *brush = GIMP_MYBRUSH (viewable);
+
+ return g_strdup_printf ("%s",
+ gimp_object_get_name (brush));
+}
+
+static void
+gimp_mybrush_dirty (GimpData *data)
+{
+ GIMP_DATA_CLASS (parent_class)->dirty (data);
+}
+
+static const gchar *
+gimp_mybrush_get_extension (GimpData *data)
+{
+ return GIMP_MYBRUSH_FILE_EXTENSION;
+}
+
+static gchar *
+gimp_mybrush_get_checksum (GimpTagged *tagged)
+{
+ GimpMybrush *brush = GIMP_MYBRUSH (tagged);
+ gchar *checksum_string = NULL;
+
+ if (brush->priv->brush_json)
+ {
+ GChecksum *checksum = g_checksum_new (G_CHECKSUM_MD5);
+
+ g_checksum_update (checksum,
+ (const guchar *) brush->priv->brush_json,
+ strlen (brush->priv->brush_json));
+
+ checksum_string = g_strdup (g_checksum_get_string (checksum));
+
+ g_checksum_free (checksum);
+ }
+
+ return checksum_string;
+}
+
+/* public functions */
+
+GimpData *
+gimp_mybrush_new (GimpContext *context,
+ const gchar *name)
+{
+ g_return_val_if_fail (name != NULL, NULL);
+
+ return g_object_new (GIMP_TYPE_MYBRUSH,
+ "name", name,
+ "mime-type", "image/x-gimp-myb",
+ NULL);
+}
+
+GimpData *
+gimp_mybrush_get_standard (GimpContext *context)
+{
+ static GimpData *standard_mybrush = NULL;
+
+ if (! standard_mybrush)
+ {
+ standard_mybrush = gimp_mybrush_new (context, "Standard");
+
+ gimp_data_clean (standard_mybrush);
+ gimp_data_make_internal (standard_mybrush, "gimp-mybrush-standard");
+
+ g_object_add_weak_pointer (G_OBJECT (standard_mybrush),
+ (gpointer *) &standard_mybrush);
+ }
+
+ return standard_mybrush;
+}
+
+const gchar *
+gimp_mybrush_get_brush_json (GimpMybrush *brush)
+{
+ g_return_val_if_fail (GIMP_IS_MYBRUSH (brush), NULL);
+
+ return brush->priv->brush_json;
+}
+
+gdouble
+gimp_mybrush_get_radius (GimpMybrush *brush)
+{
+ g_return_val_if_fail (GIMP_IS_MYBRUSH (brush), 1.0);
+
+ return brush->priv->radius;
+}
+
+gdouble
+gimp_mybrush_get_opaque (GimpMybrush *brush)
+{
+ g_return_val_if_fail (GIMP_IS_MYBRUSH (brush), 1.0);
+
+ return brush->priv->opaque;
+}
+
+gdouble
+gimp_mybrush_get_hardness (GimpMybrush *brush)
+{
+ g_return_val_if_fail (GIMP_IS_MYBRUSH (brush), 1.0);
+
+ return brush->priv->hardness;
+}
+
+gdouble
+gimp_mybrush_get_offset_by_random (GimpMybrush *brush)
+{
+ g_return_val_if_fail (GIMP_IS_MYBRUSH (brush), 1.0);
+
+ return brush->priv->offset_by_random;
+}
+
+gboolean
+gimp_mybrush_get_is_eraser (GimpMybrush *brush)
+{
+ g_return_val_if_fail (GIMP_IS_MYBRUSH (brush), FALSE);
+
+ return brush->priv->eraser;
+}
diff --git a/app/core/gimpmybrush.h b/app/core/gimpmybrush.h
new file mode 100644
index 0000000..2dad734
--- /dev/null
+++ b/app/core/gimpmybrush.h
@@ -0,0 +1,66 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpmybrush.h
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_MYBRUSH_H__
+#define __GIMP_MYBRUSH_H__
+
+
+#include "gimpdata.h"
+
+
+#define GIMP_TYPE_MYBRUSH (gimp_mybrush_get_type ())
+#define GIMP_MYBRUSH(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_MYBRUSH, GimpMybrush))
+#define GIMP_MYBRUSH_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_MYBRUSH, GimpMybrushClass))
+#define GIMP_IS_MYBRUSH(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_MYBRUSH))
+#define GIMP_IS_MYBRUSH_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_MYBRUSH))
+#define GIMP_MYBRUSH_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_MYBRUSH, GimpMybrushClass))
+
+
+typedef struct _GimpMybrushPrivate GimpMybrushPrivate;
+typedef struct _GimpMybrushClass GimpMybrushClass;
+
+struct _GimpMybrush
+{
+ GimpData parent_instance;
+
+ GimpMybrushPrivate *priv;
+};
+
+struct _GimpMybrushClass
+{
+ GimpDataClass parent_class;
+};
+
+
+GType gimp_mybrush_get_type (void) G_GNUC_CONST;
+
+GimpData * gimp_mybrush_new (GimpContext *context,
+ const gchar *name);
+GimpData * gimp_mybrush_get_standard (GimpContext *context);
+
+const gchar * gimp_mybrush_get_brush_json (GimpMybrush *brush);
+
+gdouble gimp_mybrush_get_radius (GimpMybrush *brush);
+gdouble gimp_mybrush_get_opaque (GimpMybrush *brush);
+gdouble gimp_mybrush_get_hardness (GimpMybrush *brush);
+gdouble gimp_mybrush_get_offset_by_random (GimpMybrush *brush);
+gboolean gimp_mybrush_get_is_eraser (GimpMybrush *brush);
+
+
+#endif /* __GIMP_MYBRUSH_H__ */
diff --git a/app/core/gimpobject.c b/app/core/gimpobject.c
new file mode 100644
index 0000000..0f3baf7
--- /dev/null
+++ b/app/core/gimpobject.c
@@ -0,0 +1,512 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-1997 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gio/gio.h>
+#include <gegl.h>
+
+#include "libgimpbase/gimpbase.h"
+
+#include "core-types.h"
+
+#include "gimp-memsize.h"
+#include "gimpmarshal.h"
+#include "gimpobject.h"
+
+#include "gimp-debug.h"
+
+
+enum
+{
+ DISCONNECT,
+ NAME_CHANGED,
+ LAST_SIGNAL
+};
+
+enum
+{
+ PROP_0,
+ PROP_NAME
+};
+
+
+struct _GimpObjectPrivate
+{
+ gchar *name;
+ gchar *normalized;
+ guint static_name : 1;
+ guint disconnected : 1;
+};
+
+
+static void gimp_object_constructed (GObject *object);
+static void gimp_object_dispose (GObject *object);
+static void gimp_object_finalize (GObject *object);
+static void gimp_object_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_object_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+static gint64 gimp_object_real_get_memsize (GimpObject *object,
+ gint64 *gui_size);
+static void gimp_object_name_normalize (GimpObject *object);
+
+
+G_DEFINE_TYPE_WITH_CODE (GimpObject, gimp_object, G_TYPE_OBJECT,
+ G_ADD_PRIVATE (GimpObject))
+
+#define parent_class gimp_object_parent_class
+
+static guint object_signals[LAST_SIGNAL] = { 0 };
+
+
+static void
+gimp_object_class_init (GimpObjectClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ parent_class = g_type_class_peek_parent (klass);
+
+ object_signals[DISCONNECT] =
+ g_signal_new ("disconnect",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpObjectClass, disconnect),
+ NULL, NULL,
+ gimp_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ object_signals[NAME_CHANGED] =
+ g_signal_new ("name-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpObjectClass, name_changed),
+ NULL, NULL,
+ gimp_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ object_class->constructed = gimp_object_constructed;
+ object_class->dispose = gimp_object_dispose;
+ object_class->finalize = gimp_object_finalize;
+ object_class->set_property = gimp_object_set_property;
+ object_class->get_property = gimp_object_get_property;
+
+ klass->disconnect = NULL;
+ klass->name_changed = NULL;
+ klass->get_memsize = gimp_object_real_get_memsize;
+
+ g_object_class_install_property (object_class, PROP_NAME,
+ g_param_spec_string ("name",
+ NULL, NULL,
+ NULL,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+}
+
+static void
+gimp_object_init (GimpObject *object)
+{
+ object->p = gimp_object_get_instance_private (object);
+
+ object->p->name = NULL;
+ object->p->normalized = NULL;
+}
+
+static void
+gimp_object_constructed (GObject *object)
+{
+ G_OBJECT_CLASS (parent_class)->constructed (object);
+
+ gimp_debug_add_instance (object, G_OBJECT_GET_CLASS (object));
+}
+
+static void
+gimp_object_dispose (GObject *object)
+{
+ GimpObject *gimp_object = GIMP_OBJECT (object);
+
+ if (! gimp_object->p->disconnected)
+ {
+ g_signal_emit (object, object_signals[DISCONNECT], 0);
+
+ gimp_object->p->disconnected = TRUE;
+ }
+
+ G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+gimp_object_finalize (GObject *object)
+{
+ gimp_object_name_free (GIMP_OBJECT (object));
+
+ gimp_debug_remove_instance (object);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gimp_object_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpObject *gimp_object = GIMP_OBJECT (object);
+
+ switch (property_id)
+ {
+ case PROP_NAME:
+ gimp_object_set_name (gimp_object, g_value_get_string (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_object_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpObject *gimp_object = GIMP_OBJECT (object);
+
+ switch (property_id)
+ {
+ case PROP_NAME:
+ if (gimp_object->p->static_name)
+ g_value_set_static_string (value, gimp_object->p->name);
+ else
+ g_value_set_string (value, gimp_object->p->name);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+/**
+ * gimp_object_set_name:
+ * @object: a #GimpObject
+ * @name: the @object's new name
+ *
+ * Sets the @object's name. Takes care of freeing the old name and
+ * emitting the ::name_changed signal if the old and new name differ.
+ **/
+void
+gimp_object_set_name (GimpObject *object,
+ const gchar *name)
+{
+ g_return_if_fail (GIMP_IS_OBJECT (object));
+
+ if (! g_strcmp0 (object->p->name, name))
+ return;
+
+ gimp_object_name_free (object);
+
+ object->p->name = g_strdup (name);
+ object->p->static_name = FALSE;
+
+ gimp_object_name_changed (object);
+ g_object_notify (G_OBJECT (object), "name");
+}
+
+/**
+ * gimp_object_set_name_safe:
+ * @object: a #GimpObject
+ * @name: the @object's new name
+ *
+ * A safe version of gimp_object_set_name() that takes care of
+ * handling newlines and overly long names. The actual name set
+ * may be different to the @name you pass.
+ **/
+void
+gimp_object_set_name_safe (GimpObject *object,
+ const gchar *name)
+{
+ g_return_if_fail (GIMP_IS_OBJECT (object));
+
+ if (! g_strcmp0 (object->p->name, name))
+ return;
+
+ gimp_object_name_free (object);
+
+ object->p->name = gimp_utf8_strtrim (name, 30);
+ object->p->static_name = FALSE;
+
+ gimp_object_name_changed (object);
+ g_object_notify (G_OBJECT (object), "name");
+}
+
+void
+gimp_object_set_static_name (GimpObject *object,
+ const gchar *name)
+{
+ g_return_if_fail (GIMP_IS_OBJECT (object));
+
+ if (! g_strcmp0 (object->p->name, name))
+ return;
+
+ gimp_object_name_free (object);
+
+ object->p->name = (gchar *) name;
+ object->p->static_name = TRUE;
+
+ gimp_object_name_changed (object);
+ g_object_notify (G_OBJECT (object), "name");
+}
+
+void
+gimp_object_take_name (GimpObject *object,
+ gchar *name)
+{
+ g_return_if_fail (GIMP_IS_OBJECT (object));
+
+ if (! g_strcmp0 (object->p->name, name))
+ {
+ g_free (name);
+ return;
+ }
+
+ gimp_object_name_free (object);
+
+ object->p->name = name;
+ object->p->static_name = FALSE;
+
+ gimp_object_name_changed (object);
+ g_object_notify (G_OBJECT (object), "name");
+}
+
+/**
+ * gimp_object_get_name:
+ * @object: a #GimpObject
+ *
+ * This function gives access to the name of a GimpObject. The
+ * returned name belongs to the object and must not be freed.
+ *
+ * Return value: a pointer to the @object's name
+ **/
+const gchar *
+gimp_object_get_name (gconstpointer object)
+{
+ const GimpObject *object_typed = object;
+ g_return_val_if_fail (GIMP_IS_OBJECT (object_typed), NULL);
+
+ return object_typed->p->name;
+}
+
+/**
+ * gimp_object_name_changed:
+ * @object: a #GimpObject
+ *
+ * Causes the ::name-changed signal to be emitted.
+ **/
+void
+gimp_object_name_changed (GimpObject *object)
+{
+ g_return_if_fail (GIMP_IS_OBJECT (object));
+
+ g_signal_emit (object, object_signals[NAME_CHANGED], 0);
+}
+
+/**
+ * gimp_object_name_free:
+ * @object: a #GimpObject
+ *
+ * Frees the name of @object and sets the name pointer to %NULL. Also
+ * takes care of the normalized name that the object might be caching.
+ *
+ * In general you should be using gimp_object_set_name() instead. But
+ * if you ever need to free the object name but don't want the
+ * ::name-changed signal to be emitted, then use this function. Never
+ * ever free the object name directly!
+ **/
+void
+gimp_object_name_free (GimpObject *object)
+{
+ if (object->p->normalized)
+ {
+ if (object->p->normalized != object->p->name)
+ g_free (object->p->normalized);
+
+ object->p->normalized = NULL;
+ }
+
+ if (object->p->name)
+ {
+ if (! object->p->static_name)
+ g_free (object->p->name);
+
+ object->p->name = NULL;
+ object->p->static_name = FALSE;
+ }
+}
+
+/**
+ * gimp_object_name_collate:
+ * @object1: a #GimpObject
+ * @object2: another #GimpObject
+ *
+ * Compares two object names for ordering using the linguistically
+ * correct rules for the current locale. It caches the normalized
+ * version of the object name to speed up subsequent calls.
+ *
+ * Return value: -1 if object1 compares before object2,
+ * 0 if they compare equal,
+ * 1 if object1 compares after object2.
+ **/
+gint
+gimp_object_name_collate (GimpObject *object1,
+ GimpObject *object2)
+{
+ if (! object1->p->normalized)
+ gimp_object_name_normalize (object1);
+
+ if (! object2->p->normalized)
+ gimp_object_name_normalize (object2);
+
+ return strcmp (object1->p->normalized, object2->p->normalized);
+}
+
+static void
+gimp_object_name_normalize (GimpObject *object)
+{
+ g_return_if_fail (object->p->normalized == NULL);
+
+ if (object->p->name)
+ {
+ gchar *key = g_utf8_collate_key (object->p->name, -1);
+
+ if (strcmp (key, object->p->name))
+ {
+ object->p->normalized = key;
+ }
+ else
+ {
+ g_free (key);
+ object->p->normalized = object->p->name;
+ }
+ }
+}
+
+
+#define DEBUG_MEMSIZE 1
+
+#ifdef DEBUG_MEMSIZE
+gboolean gimp_debug_memsize = FALSE;
+#endif
+
+gint64
+gimp_object_get_memsize (GimpObject *object,
+ gint64 *gui_size)
+{
+ gint64 my_size = 0;
+ gint64 my_gui_size = 0;
+
+ g_return_val_if_fail (object == NULL || GIMP_IS_OBJECT (object), 0);
+
+ if (! object)
+ {
+ if (gui_size)
+ *gui_size = 0;
+
+ return 0;
+ }
+
+#ifdef DEBUG_MEMSIZE
+ if (gimp_debug_memsize)
+ {
+ static gint indent_level = 0;
+ static GList *aggregation_tree = NULL;
+ static gchar indent_buf[256];
+
+ gint64 memsize;
+ gint64 gui_memsize = 0;
+ gint i;
+ gint my_indent_level;
+ gchar *object_size;
+
+ indent_level++;
+
+ my_indent_level = indent_level;
+
+ memsize = GIMP_OBJECT_GET_CLASS (object)->get_memsize (object,
+ &gui_memsize);
+
+ indent_level--;
+
+ for (i = 0; i < MIN (my_indent_level * 2, sizeof (indent_buf) - 1); i++)
+ indent_buf[i] = ' ';
+
+ indent_buf[i] = '\0';
+
+ object_size = g_strdup_printf ("%s%s \"%s\": "
+ "%" G_GINT64_FORMAT
+ "(%" G_GINT64_FORMAT ")\n",
+ indent_buf,
+ g_type_name (G_TYPE_FROM_INSTANCE (object)),
+ object->p->name ? object->p->name : "anonymous",
+ memsize,
+ gui_memsize);
+
+ aggregation_tree = g_list_prepend (aggregation_tree, object_size);
+
+ if (indent_level == 0)
+ {
+ GList *list;
+
+ for (list = aggregation_tree; list; list = g_list_next (list))
+ {
+ g_print ("%s", (gchar *) list->data);
+ g_free (list->data);
+ }
+
+ g_list_free (aggregation_tree);
+ aggregation_tree = NULL;
+ }
+
+ return memsize;
+ }
+#endif /* DEBUG_MEMSIZE */
+
+ my_size = GIMP_OBJECT_GET_CLASS (object)->get_memsize (object,
+ &my_gui_size);
+
+ if (gui_size)
+ *gui_size = my_gui_size;
+
+ return my_size;
+}
+
+static gint64
+gimp_object_real_get_memsize (GimpObject *object,
+ gint64 *gui_size)
+{
+ gint64 memsize = 0;
+
+ if (! object->p->static_name)
+ memsize += gimp_string_get_memsize (object->p->name);
+
+ return memsize + gimp_g_object_get_memsize ((GObject *) object);
+}
diff --git a/app/core/gimpobject.h b/app/core/gimpobject.h
new file mode 100644
index 0000000..17401c5
--- /dev/null
+++ b/app/core/gimpobject.h
@@ -0,0 +1,74 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-1997 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_OBJECT_H__
+#define __GIMP_OBJECT_H__
+
+
+#define GIMP_TYPE_OBJECT (gimp_object_get_type ())
+#define GIMP_OBJECT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_OBJECT, GimpObject))
+#define GIMP_OBJECT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_OBJECT, GimpObjectClass))
+#define GIMP_IS_OBJECT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_OBJECT))
+#define GIMP_IS_OBJECT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_OBJECT))
+#define GIMP_OBJECT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_OBJECT, GimpObjectClass))
+
+
+typedef struct _GimpObjectPrivate GimpObjectPrivate;
+typedef struct _GimpObjectClass GimpObjectClass;
+
+struct _GimpObject
+{
+ GObject parent_instance;
+
+ GimpObjectPrivate *p;
+};
+
+struct _GimpObjectClass
+{
+ GObjectClass parent_class;
+
+ /* signals */
+ void (* disconnect) (GimpObject *object);
+ void (* name_changed) (GimpObject *object);
+
+ /* virtual functions */
+ gint64 (* get_memsize) (GimpObject *object,
+ gint64 *gui_size);
+};
+
+
+GType gimp_object_get_type (void) G_GNUC_CONST;
+
+void gimp_object_set_name (GimpObject *object,
+ const gchar *name);
+void gimp_object_set_name_safe (GimpObject *object,
+ const gchar *name);
+void gimp_object_set_static_name (GimpObject *object,
+ const gchar *name);
+void gimp_object_take_name (GimpObject *object,
+ gchar *name);
+const gchar * gimp_object_get_name (gconstpointer object);
+void gimp_object_name_changed (GimpObject *object);
+void gimp_object_name_free (GimpObject *object);
+
+gint gimp_object_name_collate (GimpObject *object1,
+ GimpObject *object2);
+gint64 gimp_object_get_memsize (GimpObject *object,
+ gint64 *gui_size);
+
+
+#endif /* __GIMP_OBJECT_H__ */
diff --git a/app/core/gimpobjectqueue.c b/app/core/gimpobjectqueue.c
new file mode 100644
index 0000000..edadc09
--- /dev/null
+++ b/app/core/gimpobjectqueue.c
@@ -0,0 +1,198 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <glib-object.h>
+
+#include "core-types.h"
+
+#include "gimpcontainer.h"
+#include "gimpobject.h"
+#include "gimpobjectqueue.h"
+#include "gimpprogress.h"
+
+
+typedef struct
+{
+ GimpObject *object;
+ gint64 memsize;
+} Item;
+
+
+static void gimp_object_queue_dispose (GObject *object);
+
+static void gimp_object_queue_push_swapped (gpointer object,
+ GimpObjectQueue *queue);
+
+static Item * gimp_object_queue_item_new (GimpObject *object);
+static void gimp_object_queue_item_free (Item *item);
+
+
+G_DEFINE_TYPE (GimpObjectQueue, gimp_object_queue, GIMP_TYPE_SUB_PROGRESS);
+
+#define parent_class gimp_object_queue_parent_class
+
+
+/* private functions */
+
+
+static void
+gimp_object_queue_class_init (GimpObjectQueueClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->dispose = gimp_object_queue_dispose;
+}
+
+static void
+gimp_object_queue_init (GimpObjectQueue *queue)
+{
+ g_queue_init (&queue->items);
+
+ queue->processed_memsize = 0;
+ queue->total_memsize = 0;
+}
+
+static void
+gimp_object_queue_dispose (GObject *object)
+{
+ gimp_object_queue_clear (GIMP_OBJECT_QUEUE (object));
+
+ G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+gimp_object_queue_push_swapped (gpointer object,
+ GimpObjectQueue *queue)
+{
+ gimp_object_queue_push (queue, object);
+}
+
+static Item *
+gimp_object_queue_item_new (GimpObject *object)
+{
+ Item *item = g_slice_new (Item);
+
+ item->object = object;
+ item->memsize = gimp_object_get_memsize (object, NULL);
+
+ return item;
+}
+
+static void
+gimp_object_queue_item_free (Item *item)
+{
+ g_slice_free (Item, item);
+}
+
+
+/* public functions */
+
+
+GimpObjectQueue *
+gimp_object_queue_new (GimpProgress *progress)
+{
+ g_return_val_if_fail (progress == NULL || GIMP_IS_PROGRESS (progress), NULL);
+
+ return g_object_new (GIMP_TYPE_OBJECT_QUEUE,
+ "progress", progress,
+ NULL);
+}
+
+void
+gimp_object_queue_clear (GimpObjectQueue *queue)
+{
+ Item *item;
+
+ g_return_if_fail (GIMP_IS_OBJECT_QUEUE (queue));
+
+ while ((item = g_queue_pop_head (&queue->items)))
+ gimp_object_queue_item_free (item);
+
+ queue->processed_memsize = 0;
+ queue->total_memsize = 0;
+
+ gimp_sub_progress_set_range (GIMP_SUB_PROGRESS (queue), 0.0, 1.0);
+}
+
+void
+gimp_object_queue_push (GimpObjectQueue *queue,
+ gpointer object)
+{
+ Item *item;
+
+ g_return_if_fail (GIMP_IS_OBJECT_QUEUE (queue));
+ g_return_if_fail (GIMP_IS_OBJECT (object));
+
+ item = gimp_object_queue_item_new (GIMP_OBJECT (object));
+
+ g_queue_push_tail (&queue->items, item);
+
+ queue->total_memsize += item->memsize;
+}
+
+void
+gimp_object_queue_push_container (GimpObjectQueue *queue,
+ GimpContainer *container)
+{
+ g_return_if_fail (GIMP_IS_OBJECT_QUEUE (queue));
+ g_return_if_fail (GIMP_IS_CONTAINER (container));
+
+ gimp_container_foreach (container,
+ (GFunc) gimp_object_queue_push_swapped,
+ queue);
+}
+
+void
+gimp_object_queue_push_list (GimpObjectQueue *queue,
+ GList *list)
+{
+ g_return_if_fail (GIMP_IS_OBJECT_QUEUE (queue));
+
+ g_list_foreach (list,
+ (GFunc) gimp_object_queue_push_swapped,
+ queue);
+}
+
+gpointer
+gimp_object_queue_pop (GimpObjectQueue *queue)
+{
+ Item *item;
+ GimpObject *object;
+
+ g_return_val_if_fail (GIMP_IS_OBJECT_QUEUE (queue), NULL);
+
+ item = g_queue_pop_head (&queue->items);
+
+ if (! item)
+ return NULL;
+
+ object = item->object;
+
+ gimp_sub_progress_set_range (GIMP_SUB_PROGRESS (queue),
+ (gdouble) queue->processed_memsize /
+ (gdouble) queue->total_memsize,
+ (gdouble) (queue->processed_memsize +
+ item->memsize) /
+ (gdouble) queue->total_memsize);
+ queue->processed_memsize += item->memsize;
+
+ gimp_object_queue_item_free (item);
+
+ return object;
+}
diff --git a/app/core/gimpobjectqueue.h b/app/core/gimpobjectqueue.h
new file mode 100644
index 0000000..024754f
--- /dev/null
+++ b/app/core/gimpobjectqueue.h
@@ -0,0 +1,66 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_OBJECT_QUEUE_H__
+#define __GIMP_OBJECT_QUEUE_H__
+
+
+#include "gimpsubprogress.h"
+
+
+#define GIMP_TYPE_OBJECT_QUEUE (gimp_object_queue_get_type ())
+#define GIMP_OBJECT_QUEUE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_OBJECT_QUEUE, GimpObjectQueue))
+#define GIMP_OBJECT_QUEUE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_OBJECT_QUEUE, GimpObjectQueueClass))
+#define GIMP_IS_OBJECT_QUEUE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_OBJECT_QUEUE))
+#define GIMP_IS_OBJECT_QUEUE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_OBJECT_QUEUE))
+#define GIMP_OBJECT_QUEUE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_OBJECT_QUEUE, GimpObjectQueueClass))
+
+
+typedef struct _GimpObjectQueueClass GimpObjectQueueClass;
+
+struct _GimpObjectQueue
+{
+ GimpSubProgress parent_instance;
+
+ GQueue items;
+ gint64 processed_memsize;
+ gint64 total_memsize;
+};
+
+struct _GimpObjectQueueClass
+{
+ GimpSubProgressClass parent_class;
+};
+
+
+GType gimp_object_queue_get_type (void) G_GNUC_CONST;
+
+GimpObjectQueue * gimp_object_queue_new (GimpProgress *progress);
+
+void gimp_object_queue_clear (GimpObjectQueue *queue);
+
+void gimp_object_queue_push (GimpObjectQueue *queue,
+ gpointer object);
+void gimp_object_queue_push_container (GimpObjectQueue *queue,
+ GimpContainer *container);
+void gimp_object_queue_push_list (GimpObjectQueue *queue,
+ GList *list);
+
+gpointer gimp_object_queue_pop (GimpObjectQueue *queue);
+
+
+#endif /* __GIMP_OBJECT_QUEUE_H__ */
diff --git a/app/core/gimppaintinfo.c b/app/core/gimppaintinfo.c
new file mode 100644
index 0000000..c7d6d81
--- /dev/null
+++ b/app/core/gimppaintinfo.c
@@ -0,0 +1,142 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "core-types.h"
+
+#include "paint/gimppaintoptions.h"
+
+#include "gimp.h"
+#include "gimppaintinfo.h"
+
+
+static void gimp_paint_info_dispose (GObject *object);
+static void gimp_paint_info_finalize (GObject *object);
+static gchar * gimp_paint_info_get_description (GimpViewable *viewable,
+ gchar **tooltip);
+
+
+G_DEFINE_TYPE (GimpPaintInfo, gimp_paint_info, GIMP_TYPE_VIEWABLE)
+
+#define parent_class gimp_paint_info_parent_class
+
+
+static void
+gimp_paint_info_class_init (GimpPaintInfoClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpViewableClass *viewable_class = GIMP_VIEWABLE_CLASS (klass);
+
+ object_class->dispose = gimp_paint_info_dispose;
+ object_class->finalize = gimp_paint_info_finalize;
+
+ viewable_class->get_description = gimp_paint_info_get_description;
+}
+
+static void
+gimp_paint_info_init (GimpPaintInfo *paint_info)
+{
+ paint_info->gimp = NULL;
+ paint_info->paint_type = G_TYPE_NONE;
+ paint_info->blurb = NULL;
+ paint_info->paint_options = NULL;
+}
+
+static void
+gimp_paint_info_dispose (GObject *object)
+{
+ GimpPaintInfo *paint_info = GIMP_PAINT_INFO (object);
+
+ if (paint_info->paint_options)
+ {
+ g_object_run_dispose (G_OBJECT (paint_info->paint_options));
+ g_clear_object (&paint_info->paint_options);
+ }
+
+ G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+gimp_paint_info_finalize (GObject *object)
+{
+ GimpPaintInfo *paint_info = GIMP_PAINT_INFO (object);
+
+ g_clear_pointer (&paint_info->blurb, g_free);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static gchar *
+gimp_paint_info_get_description (GimpViewable *viewable,
+ gchar **tooltip)
+{
+ GimpPaintInfo *paint_info = GIMP_PAINT_INFO (viewable);
+
+ return g_strdup (paint_info->blurb);
+}
+
+GimpPaintInfo *
+gimp_paint_info_new (Gimp *gimp,
+ GType paint_type,
+ GType paint_options_type,
+ const gchar *identifier,
+ const gchar *blurb,
+ const gchar *icon_name)
+{
+ GimpPaintInfo *paint_info;
+
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
+ g_return_val_if_fail (identifier != NULL, NULL);
+ g_return_val_if_fail (blurb != NULL, NULL);
+ g_return_val_if_fail (icon_name != NULL, NULL);
+
+ paint_info = g_object_new (GIMP_TYPE_PAINT_INFO,
+ "name", identifier,
+ "icon-name", icon_name,
+ NULL);
+
+ paint_info->gimp = gimp;
+ paint_info->paint_type = paint_type;
+ paint_info->paint_options_type = paint_options_type;
+ paint_info->blurb = g_strdup (blurb);
+
+ paint_info->paint_options = gimp_paint_options_new (paint_info);
+
+ return paint_info;
+}
+
+void
+gimp_paint_info_set_standard (Gimp *gimp,
+ GimpPaintInfo *paint_info)
+{
+ g_return_if_fail (GIMP_IS_GIMP (gimp));
+ g_return_if_fail (! paint_info || GIMP_IS_PAINT_INFO (paint_info));
+
+ g_set_object (&gimp->standard_paint_info, paint_info);
+}
+
+GimpPaintInfo *
+gimp_paint_info_get_standard (Gimp *gimp)
+{
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
+
+ return gimp->standard_paint_info;
+}
diff --git a/app/core/gimppaintinfo.h b/app/core/gimppaintinfo.h
new file mode 100644
index 0000000..c67b326
--- /dev/null
+++ b/app/core/gimppaintinfo.h
@@ -0,0 +1,69 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_PAINT_INFO_H__
+#define __GIMP_PAINT_INFO_H__
+
+
+#include "gimpviewable.h"
+
+
+#define GIMP_TYPE_PAINT_INFO (gimp_paint_info_get_type ())
+#define GIMP_PAINT_INFO(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_PAINT_INFO, GimpPaintInfo))
+#define GIMP_PAINT_INFO_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_PAINT_INFO, GimpPaintInfoClass))
+#define GIMP_IS_PAINT_INFO(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_PAINT_INFO))
+#define GIMP_IS_PAINT_INFO_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_PAINT_INFO))
+#define GIMP_PAINT_INFO_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_PAINT_INFO, GimpPaintInfoClass))
+
+
+typedef struct _GimpPaintInfoClass GimpPaintInfoClass;
+
+struct _GimpPaintInfo
+{
+ GimpViewable parent_instance;
+
+ Gimp *gimp;
+
+ GType paint_type;
+ GType paint_options_type;
+
+ gchar *blurb;
+
+ GimpPaintOptions *paint_options;
+};
+
+struct _GimpPaintInfoClass
+{
+ GimpViewableClass parent_class;
+};
+
+
+GType gimp_paint_info_get_type (void) G_GNUC_CONST;
+
+GimpPaintInfo * gimp_paint_info_new (Gimp *gimp,
+ GType paint_type,
+ GType paint_options_type,
+ const gchar *identifier,
+ const gchar *blurb,
+ const gchar *icon_name);
+
+void gimp_paint_info_set_standard (Gimp *gimp,
+ GimpPaintInfo *paint_info);
+GimpPaintInfo * gimp_paint_info_get_standard (Gimp *gimp);
+
+
+#endif /* __GIMP_PAINT_INFO_H__ */
diff --git a/app/core/gimppalette-import.c b/app/core/gimppalette-import.c
new file mode 100644
index 0000000..aaa44ea
--- /dev/null
+++ b/app/core/gimppalette-import.c
@@ -0,0 +1,566 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <cairo.h>
+#include <gegl.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+#include "libgimpbase/gimpbase.h"
+#include "libgimpcolor/gimpcolor.h"
+
+#include "core-types.h"
+
+#include "gimpchannel.h"
+#include "gimpcontainer.h"
+#include "gimpcontext.h"
+#include "gimpgradient.h"
+#include "gimpimage.h"
+#include "gimpimage-colormap.h"
+#include "gimppalette.h"
+#include "gimppalette-import.h"
+#include "gimppalette-load.h"
+#include "gimppickable.h"
+
+#include "gimp-intl.h"
+
+
+#define MAX_IMAGE_COLORS (10000 * 2)
+
+
+/* create a palette from a gradient ****************************************/
+
+GimpPalette *
+gimp_palette_import_from_gradient (GimpGradient *gradient,
+ GimpContext *context,
+ gboolean reverse,
+ GimpGradientBlendColorSpace blend_color_space,
+ const gchar *palette_name,
+ gint n_colors)
+{
+ GimpPalette *palette;
+ GimpGradientSegment *seg = NULL;
+ gdouble dx, cur_x;
+ GimpRGB color;
+ gint i;
+
+ g_return_val_if_fail (GIMP_IS_GRADIENT (gradient), NULL);
+ g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL);
+ g_return_val_if_fail (palette_name != NULL, NULL);
+ g_return_val_if_fail (n_colors > 1, NULL);
+
+ palette = GIMP_PALETTE (gimp_palette_new (context, palette_name));
+
+ dx = 1.0 / (n_colors - 1);
+
+ for (i = 0, cur_x = 0; i < n_colors; i++, cur_x += dx)
+ {
+ seg = gimp_gradient_get_color_at (gradient, context,
+ seg, cur_x, reverse, blend_color_space,
+ &color);
+ gimp_palette_add_entry (palette, -1, NULL, &color);
+ }
+
+ return palette;
+}
+
+
+/* create a palette from a non-indexed image *******************************/
+
+typedef struct _ImgColors ImgColors;
+
+struct _ImgColors
+{
+ guint count;
+ guint r_adj;
+ guint g_adj;
+ guint b_adj;
+ guchar r;
+ guchar g;
+ guchar b;
+};
+
+static gint count_color_entries = 0;
+
+static GHashTable *
+gimp_palette_import_store_colors (GHashTable *table,
+ guchar *colors,
+ guchar *colors_real,
+ gint n_colors)
+{
+ gpointer found_color = NULL;
+ ImgColors *new_color;
+ guint key_colors = colors[0] * 256 * 256 + colors[1] * 256 + colors[2];
+
+ if (table == NULL)
+ {
+ table = g_hash_table_new (g_direct_hash, g_direct_equal);
+ count_color_entries = 0;
+ }
+ else
+ {
+ found_color = g_hash_table_lookup (table, GUINT_TO_POINTER (key_colors));
+ }
+
+ if (found_color == NULL)
+ {
+ if (count_color_entries > MAX_IMAGE_COLORS)
+ {
+ /* Don't add any more new ones */
+ return table;
+ }
+
+ count_color_entries++;
+
+ new_color = g_slice_new (ImgColors);
+
+ new_color->count = 1;
+ new_color->r_adj = 0;
+ new_color->g_adj = 0;
+ new_color->b_adj = 0;
+ new_color->r = colors[0];
+ new_color->g = colors[1];
+ new_color->b = colors[2];
+
+ g_hash_table_insert (table, GUINT_TO_POINTER (key_colors), new_color);
+ }
+ else
+ {
+ new_color = found_color;
+
+ if (new_color->count < (G_MAXINT - 1))
+ new_color->count++;
+
+ /* Now do the adjustments ...*/
+ new_color->r_adj += (colors_real[0] - colors[0]);
+ new_color->g_adj += (colors_real[1] - colors[1]);
+ new_color->b_adj += (colors_real[2] - colors[2]);
+
+ /* Boundary conditions */
+ if(new_color->r_adj > (G_MAXINT - 255))
+ new_color->r_adj /= new_color->count;
+
+ if(new_color->g_adj > (G_MAXINT - 255))
+ new_color->g_adj /= new_color->count;
+
+ if(new_color->b_adj > (G_MAXINT - 255))
+ new_color->b_adj /= new_color->count;
+ }
+
+ return table;
+}
+
+static void
+gimp_palette_import_create_list (gpointer key,
+ gpointer value,
+ gpointer user_data)
+{
+ GSList **list = user_data;
+ ImgColors *color_tab = value;
+
+ *list = g_slist_prepend (*list, color_tab);
+}
+
+static gint
+gimp_palette_import_sort_colors (gconstpointer a,
+ gconstpointer b)
+{
+ const ImgColors *s1 = a;
+ const ImgColors *s2 = b;
+
+ if(s1->count > s2->count)
+ return -1;
+ if(s1->count < s2->count)
+ return 1;
+
+ return 0;
+}
+
+static void
+gimp_palette_import_create_image_palette (gpointer data,
+ gpointer user_data)
+{
+ GimpPalette *palette = user_data;
+ ImgColors *color_tab = data;
+ gint n_colors;
+ gchar *lab;
+ GimpRGB color;
+
+ n_colors = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (palette),
+ "import-n-colors"));
+
+ if (gimp_palette_get_n_colors (palette) >= n_colors)
+ return;
+
+ /* TRANSLATORS: the "%s" is an item title and "%u" is the number of
+ occurrences for this item. */
+ lab = g_strdup_printf (_("%s (occurs %u)"),
+ _("Untitled"),
+ color_tab->count);
+
+ /* Adjust the colors to the mean of the the sample */
+ gimp_rgba_set_uchar
+ (&color,
+ (guchar) color_tab->r + (color_tab->r_adj / color_tab->count),
+ (guchar) color_tab->g + (color_tab->g_adj / color_tab->count),
+ (guchar) color_tab->b + (color_tab->b_adj / color_tab->count),
+ 255);
+
+ gimp_palette_add_entry (palette, -1, lab, &color);
+
+ g_free (lab);
+}
+
+static GimpPalette *
+gimp_palette_import_make_palette (GHashTable *table,
+ const gchar *palette_name,
+ GimpContext *context,
+ gint n_colors)
+{
+ GimpPalette *palette;
+ GSList *list = NULL;
+ GSList *iter;
+
+ palette = GIMP_PALETTE (gimp_palette_new (context, palette_name));
+
+ if (! table)
+ return palette;
+
+ g_hash_table_foreach (table, gimp_palette_import_create_list, &list);
+ list = g_slist_sort (list, gimp_palette_import_sort_colors);
+
+ g_object_set_data (G_OBJECT (palette), "import-n-colors",
+ GINT_TO_POINTER (n_colors));
+
+ g_slist_foreach (list, gimp_palette_import_create_image_palette, palette);
+
+ g_object_set_data (G_OBJECT (palette), "import-n-colors", NULL);
+
+ /* Free up used memory
+ * Note the same structure is on both the hash list and the sorted
+ * list. So only delete it once.
+ */
+ g_hash_table_destroy (table);
+
+ for (iter = list; iter; iter = iter->next)
+ g_slice_free (ImgColors, iter->data);
+
+ g_slist_free (list);
+
+ return palette;
+}
+
+static GHashTable *
+gimp_palette_import_extract (GimpImage *image,
+ GimpPickable *pickable,
+ gint pickable_off_x,
+ gint pickable_off_y,
+ gboolean selection_only,
+ gint x,
+ gint y,
+ gint width,
+ gint height,
+ gint n_colors,
+ gint threshold)
+{
+ GeglBuffer *buffer;
+ GeglBufferIterator *iter;
+ GeglRectangle *mask_roi = NULL;
+ GeglRectangle rect = { x, y, width, height };
+ GHashTable *colors = NULL;
+ const Babl *format;
+ gint bpp;
+ gint mask_bpp = 0;
+
+ buffer = gimp_pickable_get_buffer (pickable);
+ format = babl_format ("R'G'B'A u8");
+
+ iter = gegl_buffer_iterator_new (buffer, &rect, 0, format,
+ GEGL_ACCESS_READ, GEGL_ABYSS_NONE, 2);
+ bpp = babl_format_get_bytes_per_pixel (format);
+
+ if (selection_only &&
+ ! gimp_channel_is_empty (gimp_image_get_mask (image)))
+ {
+ GimpDrawable *mask = GIMP_DRAWABLE (gimp_image_get_mask (image));
+
+ rect.x = x + pickable_off_x;
+ rect.y = y + pickable_off_y;
+
+ buffer = gimp_drawable_get_buffer (mask);
+ format = babl_format ("Y u8");
+
+ gegl_buffer_iterator_add (iter, buffer, &rect, 0, format,
+ GEGL_ACCESS_READ, GEGL_ABYSS_NONE);
+ mask_roi = &iter->items[1].roi;
+ mask_bpp = babl_format_get_bytes_per_pixel (format);
+ }
+
+ while (gegl_buffer_iterator_next (iter))
+ {
+ const guchar *data = iter->items[0].data;
+ const guchar *mask_data = NULL;
+ gint length = iter->length;
+
+ if (mask_roi)
+ mask_data = iter->items[1].data;
+
+ while (length--)
+ {
+ /* ignore unselected, and completely transparent pixels */
+ if ((! mask_data || *mask_data) && data[ALPHA])
+ {
+ guchar rgba[MAX_CHANNELS] = { 0, };
+ guchar rgb_real[MAX_CHANNELS] = { 0, };
+
+ memcpy (rgba, data, 4);
+ memcpy (rgb_real, rgba, 4);
+
+ rgba[0] = (rgba[0] / threshold) * threshold;
+ rgba[1] = (rgba[1] / threshold) * threshold;
+ rgba[2] = (rgba[2] / threshold) * threshold;
+
+ colors = gimp_palette_import_store_colors (colors,
+ rgba, rgb_real,
+ n_colors);
+ }
+
+ data += bpp;
+
+ if (mask_data)
+ mask_data += mask_bpp;
+ }
+ }
+
+ return colors;
+}
+
+GimpPalette *
+gimp_palette_import_from_image (GimpImage *image,
+ GimpContext *context,
+ const gchar *palette_name,
+ gint n_colors,
+ gint threshold,
+ gboolean selection_only)
+{
+ GHashTable *colors;
+ gint x, y;
+ gint width, height;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+ g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL);
+ g_return_val_if_fail (palette_name != NULL, NULL);
+ g_return_val_if_fail (n_colors > 1, NULL);
+ g_return_val_if_fail (threshold > 0, NULL);
+
+ gimp_pickable_flush (GIMP_PICKABLE (image));
+
+ if (selection_only)
+ {
+ gimp_item_bounds (GIMP_ITEM (gimp_image_get_mask (image)),
+ &x, &y, &width, &height);
+ }
+ else
+ {
+ x = 0;
+ y = 0;
+ width = gimp_image_get_width (image);
+ height = gimp_image_get_height (image);
+ }
+
+ colors = gimp_palette_import_extract (image,
+ GIMP_PICKABLE (image),
+ 0, 0,
+ selection_only,
+ x, y, width, height,
+ n_colors, threshold);
+
+ return gimp_palette_import_make_palette (colors, palette_name, context,
+ n_colors);
+}
+
+
+/* create a palette from an indexed image **********************************/
+
+GimpPalette *
+gimp_palette_import_from_indexed_image (GimpImage *image,
+ GimpContext *context,
+ const gchar *palette_name)
+{
+ GimpPalette *palette;
+ const guchar *colormap;
+ guint n_colors;
+ gint count;
+ GimpRGB color;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+ g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL);
+ g_return_val_if_fail (gimp_image_get_base_type (image) == GIMP_INDEXED, NULL);
+ g_return_val_if_fail (palette_name != NULL, NULL);
+
+ palette = GIMP_PALETTE (gimp_palette_new (context, palette_name));
+
+ colormap = gimp_image_get_colormap (image);
+ n_colors = gimp_image_get_colormap_size (image);
+
+ for (count = 0; count < n_colors; ++count)
+ {
+ gchar name[256];
+
+ g_snprintf (name, sizeof (name), _("Index %d"), count);
+
+ gimp_rgba_set_uchar (&color,
+ colormap[count * 3 + 0],
+ colormap[count * 3 + 1],
+ colormap[count * 3 + 2],
+ 255);
+
+ gimp_palette_add_entry (palette, -1, name, &color);
+ }
+
+ return palette;
+}
+
+
+/* create a palette from a drawable ****************************************/
+
+GimpPalette *
+gimp_palette_import_from_drawable (GimpDrawable *drawable,
+ GimpContext *context,
+ const gchar *palette_name,
+ gint n_colors,
+ gint threshold,
+ gboolean selection_only)
+{
+ GHashTable *colors = NULL;
+ gint x, y;
+ gint width, height;
+ gint off_x, off_y;
+
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), NULL);
+ g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL);
+ g_return_val_if_fail (gimp_item_is_attached (GIMP_ITEM (drawable)), NULL);
+ g_return_val_if_fail (palette_name != NULL, NULL);
+ g_return_val_if_fail (n_colors > 1, NULL);
+ g_return_val_if_fail (threshold > 0, NULL);
+
+ if (selection_only)
+ {
+ if (! gimp_item_mask_intersect (GIMP_ITEM (drawable),
+ &x, &y, &width, &height))
+ return NULL;
+ }
+ else
+ {
+ x = 0;
+ y = 0;
+ width = gimp_item_get_width (GIMP_ITEM (drawable));
+ height = gimp_item_get_height (GIMP_ITEM (drawable));
+ }
+
+ gimp_item_get_offset (GIMP_ITEM (drawable), &off_x, &off_y);
+
+ colors =
+ gimp_palette_import_extract (gimp_item_get_image (GIMP_ITEM (drawable)),
+ GIMP_PICKABLE (drawable),
+ off_x, off_y,
+ selection_only,
+ x, y, width, height,
+ n_colors, threshold);
+
+ return gimp_palette_import_make_palette (colors, palette_name, context,
+ n_colors);
+}
+
+
+/* create a palette from a file **********************************/
+
+GimpPalette *
+gimp_palette_import_from_file (GimpContext *context,
+ GFile *file,
+ const gchar *palette_name,
+ GError **error)
+{
+ GList *palette_list = NULL;
+ GInputStream *input;
+ GError *my_error = NULL;
+
+ g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL);
+ g_return_val_if_fail (G_IS_FILE (file), NULL);
+ g_return_val_if_fail (palette_name != NULL, NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ input = G_INPUT_STREAM (g_file_read (file, NULL, &my_error));
+ if (! input)
+ {
+ g_set_error (error, GIMP_DATA_ERROR, GIMP_DATA_ERROR_OPEN,
+ _("Could not open '%s' for reading: %s"),
+ gimp_file_get_utf8_name (file), my_error->message);
+ g_clear_error (&my_error);
+ return NULL;
+ }
+
+ switch (gimp_palette_load_detect_format (file, input))
+ {
+ case GIMP_PALETTE_FILE_FORMAT_GPL:
+ palette_list = gimp_palette_load (context, file, input, error);
+ break;
+
+ case GIMP_PALETTE_FILE_FORMAT_ACT:
+ palette_list = gimp_palette_load_act (context, file, input, error);
+ break;
+
+ case GIMP_PALETTE_FILE_FORMAT_RIFF_PAL:
+ palette_list = gimp_palette_load_riff (context, file, input, error);
+ break;
+
+ case GIMP_PALETTE_FILE_FORMAT_PSP_PAL:
+ palette_list = gimp_palette_load_psp (context, file, input, error);
+ break;
+
+ case GIMP_PALETTE_FILE_FORMAT_ACO:
+ palette_list = gimp_palette_load_aco (context, file, input, error);
+ break;
+
+ case GIMP_PALETTE_FILE_FORMAT_CSS:
+ palette_list = gimp_palette_load_css (context, file, input, error);
+ break;
+
+ default:
+ g_set_error (error,
+ GIMP_DATA_ERROR, GIMP_DATA_ERROR_READ,
+ _("Unknown type of palette file: %s"),
+ gimp_file_get_utf8_name (file));
+ break;
+ }
+
+ g_object_unref (input);
+
+ if (palette_list)
+ {
+ GimpPalette *palette = g_object_ref (palette_list->data);
+
+ gimp_object_set_name (GIMP_OBJECT (palette), palette_name);
+
+ g_list_free_full (palette_list, (GDestroyNotify) g_object_unref);
+
+ return palette;
+ }
+
+ return NULL;
+}
diff --git a/app/core/gimppalette-import.h b/app/core/gimppalette-import.h
new file mode 100644
index 0000000..9cd3901
--- /dev/null
+++ b/app/core/gimppalette-import.h
@@ -0,0 +1,48 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_PALETTE_IMPORT__
+#define __GIMP_PALETTE_IMPORT__
+
+
+GimpPalette * gimp_palette_import_from_gradient (GimpGradient *gradient,
+ GimpContext *context,
+ gboolean reverse,
+ GimpGradientBlendColorSpace blend_color_space,
+ const gchar *palette_name,
+ gint n_colors);
+GimpPalette * gimp_palette_import_from_image (GimpImage *image,
+ GimpContext *context,
+ const gchar *palette_name,
+ gint n_colors,
+ gint threshold,
+ gboolean selection_only);
+GimpPalette * gimp_palette_import_from_indexed_image (GimpImage *image,
+ GimpContext *context,
+ const gchar *palette_name);
+GimpPalette * gimp_palette_import_from_drawable (GimpDrawable *drawable,
+ GimpContext *context,
+ const gchar *palette_name,
+ gint n_colors,
+ gint threshold,
+ gboolean selection_only);
+GimpPalette * gimp_palette_import_from_file (GimpContext *context,
+ GFile *file,
+ const gchar *palette_name,
+ GError **error);
+
+#endif /* __GIMP_PALETTE_IMPORT_H__ */
diff --git a/app/core/gimppalette-load.c b/app/core/gimppalette-load.c
new file mode 100644
index 0000000..32a1fc1
--- /dev/null
+++ b/app/core/gimppalette-load.c
@@ -0,0 +1,702 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+
+#include <cairo.h>
+#include <gegl.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+#include "libgimpbase/gimpbase.h"
+#include "libgimpcolor/gimpcolor.h"
+
+#include "core-types.h"
+
+#include "gimp-utils.h"
+#include "gimppalette.h"
+#include "gimppalette-load.h"
+
+#include "gimp-intl.h"
+
+
+GList *
+gimp_palette_load (GimpContext *context,
+ GFile *file,
+ GInputStream *input,
+ GError **error)
+{
+ GimpPalette *palette = NULL;
+ GimpPaletteEntry *entry;
+ GDataInputStream *data_input;
+ gchar *str;
+ gsize str_len;
+ gchar *tok;
+ gint r, g, b;
+ gint linenum;
+
+ g_return_val_if_fail (G_IS_FILE (file), NULL);
+ g_return_val_if_fail (G_IS_INPUT_STREAM (input), NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ data_input = g_data_input_stream_new (input);
+
+ r = g = b = 0;
+
+ linenum = 1;
+ str_len = 1024;
+ str = gimp_data_input_stream_read_line_always (data_input, &str_len,
+ NULL, error);
+ if (! str)
+ goto failed;
+
+ if (! g_str_has_prefix (str, "GIMP Palette"))
+ {
+ g_set_error (error, GIMP_DATA_ERROR, GIMP_DATA_ERROR_READ,
+ _("Missing magic header."));
+ g_free (str);
+ goto failed;
+ }
+
+ g_free (str);
+
+ palette = g_object_new (GIMP_TYPE_PALETTE,
+ "mime-type", "application/x-gimp-palette",
+ NULL);
+
+ linenum++;
+ str_len = 1024;
+ str = gimp_data_input_stream_read_line_always (data_input, &str_len,
+ NULL, error);
+ if (! str)
+ goto failed;
+
+ if (g_str_has_prefix (str, "Name: "))
+ {
+ gchar *utf8;
+
+ utf8 = gimp_any_to_utf8 (g_strstrip (str + strlen ("Name: ")), -1,
+ _("Invalid UTF-8 string in palette file '%s'"),
+ gimp_file_get_utf8_name (file));
+ gimp_object_take_name (GIMP_OBJECT (palette), utf8);
+ g_free (str);
+
+ linenum++;
+ str_len = 1024;
+ str = gimp_data_input_stream_read_line_always (data_input, &str_len,
+ NULL, error);
+ if (! str)
+ goto failed;
+
+ if (g_str_has_prefix (str, "Columns: "))
+ {
+ gint columns;
+
+ if (! gimp_ascii_strtoi (g_strstrip (str + strlen ("Columns: ")),
+ NULL, 10, &columns))
+ {
+ g_set_error (error, GIMP_DATA_ERROR, GIMP_DATA_ERROR_READ,
+ _("Invalid column count."));
+ g_free (str);
+ goto failed;
+ }
+
+ if (columns < 0 || columns > 256)
+ {
+ g_message (_("Reading palette file '%s': "
+ "Invalid number of columns in line %d. "
+ "Using default value."),
+ gimp_file_get_utf8_name (file), linenum);
+ columns = 0;
+ }
+
+ gimp_palette_set_columns (palette, columns);
+ g_free (str);
+
+ linenum++;
+ str_len = 1024;
+ str = gimp_data_input_stream_read_line_always (data_input, &str_len,
+ NULL, error);
+ if (! str)
+ goto failed;
+ }
+ }
+ else /* old palette format */
+ {
+ gimp_object_take_name (GIMP_OBJECT (palette),
+ g_path_get_basename (gimp_file_get_utf8_name (file)));
+ }
+
+ while (str)
+ {
+ GError *my_error = NULL;
+
+ if (str[0] != '#' && str[0] != '\0')
+ {
+ tok = strtok (str, " \t");
+ if (tok)
+ r = atoi (tok);
+ else
+ g_message (_("Reading palette file '%s': "
+ "Missing RED component in line %d."),
+ gimp_file_get_utf8_name (file), linenum);
+
+ tok = strtok (NULL, " \t");
+ if (tok)
+ g = atoi (tok);
+ else
+ g_message (_("Reading palette file '%s': "
+ "Missing GREEN component in line %d."),
+ gimp_file_get_utf8_name (file), linenum);
+
+ tok = strtok (NULL, " \t");
+ if (tok)
+ b = atoi (tok);
+ else
+ g_message (_("Reading palette file '%s': "
+ "Missing BLUE component in line %d."),
+ gimp_file_get_utf8_name (file), linenum);
+
+ /* optional name */
+ tok = strtok (NULL, "\n");
+
+ if (r < 0 || r > 255 ||
+ g < 0 || g > 255 ||
+ b < 0 || b > 255)
+ g_message (_("Reading palette file '%s': "
+ "RGB value out of range in line %d."),
+ gimp_file_get_utf8_name (file), linenum);
+
+ /* don't call gimp_palette_add_entry here, it's rather inefficient */
+ entry = g_slice_new0 (GimpPaletteEntry);
+
+ gimp_rgba_set_uchar (&entry->color,
+ (guchar) r,
+ (guchar) g,
+ (guchar) b,
+ 255);
+
+ entry->name = g_strdup (tok ? tok : _("Untitled"));
+ entry->position = gimp_palette_get_n_colors (palette);
+
+ palette->colors = g_list_prepend (palette->colors, entry);
+ palette->n_colors++;
+ }
+
+ g_free (str);
+
+ linenum++;
+ str_len = 1024;
+ str = g_data_input_stream_read_line (data_input, &str_len,
+ NULL, &my_error);
+ if (! str && my_error)
+ {
+ g_message (_("Reading palette file '%s': "
+ "Read %d colors from truncated file: %s"),
+ gimp_file_get_utf8_name (file),
+ g_list_length (palette->colors),
+ my_error->message);
+ g_clear_error (&my_error);
+ }
+ }
+
+ palette->colors = g_list_reverse (palette->colors);
+
+ g_object_unref (data_input);
+
+ return g_list_prepend (NULL, palette);
+
+ failed:
+
+ g_object_unref (data_input);
+
+ if (palette)
+ g_object_unref (palette);
+
+ g_prefix_error (error, _("In line %d of palette file: "), linenum);
+
+ return NULL;
+}
+
+GList *
+gimp_palette_load_act (GimpContext *context,
+ GFile *file,
+ GInputStream *input,
+ GError **error)
+{
+ GimpPalette *palette;
+ gchar *palette_name;
+ guchar color_bytes[3];
+ gsize bytes_read;
+
+ g_return_val_if_fail (G_IS_FILE (file), NULL);
+ g_return_val_if_fail (G_IS_INPUT_STREAM (input), NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ palette_name = g_path_get_basename (gimp_file_get_utf8_name (file));
+ palette = GIMP_PALETTE (gimp_palette_new (context, palette_name));
+ g_free (palette_name);
+
+ while (g_input_stream_read_all (input, color_bytes, sizeof (color_bytes),
+ &bytes_read, NULL, NULL) &&
+ bytes_read == sizeof (color_bytes))
+ {
+ GimpRGB color;
+
+ gimp_rgba_set_uchar (&color,
+ color_bytes[0],
+ color_bytes[1],
+ color_bytes[2],
+ 255);
+ gimp_palette_add_entry (palette, -1, NULL, &color);
+ }
+
+ return g_list_prepend (NULL, palette);
+}
+
+GList *
+gimp_palette_load_riff (GimpContext *context,
+ GFile *file,
+ GInputStream *input,
+ GError **error)
+{
+ GimpPalette *palette;
+ gchar *palette_name;
+ guchar color_bytes[4];
+ gsize bytes_read;
+
+ g_return_val_if_fail (G_IS_FILE (file), NULL);
+ g_return_val_if_fail (G_IS_INPUT_STREAM (input), NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ palette_name = g_path_get_basename (gimp_file_get_utf8_name (file));
+ palette = GIMP_PALETTE (gimp_palette_new (context, palette_name));
+ g_free (palette_name);
+
+ if (! g_seekable_seek (G_SEEKABLE (input), 28, G_SEEK_SET, NULL, error))
+ {
+ g_object_unref (palette);
+ return NULL;
+ }
+
+ while (g_input_stream_read_all (input, color_bytes, sizeof (color_bytes),
+ &bytes_read, NULL, NULL) &&
+ bytes_read == sizeof (color_bytes))
+ {
+ GimpRGB color;
+
+ gimp_rgba_set_uchar (&color,
+ color_bytes[0],
+ color_bytes[1],
+ color_bytes[2],
+ 255);
+ gimp_palette_add_entry (palette, -1, NULL, &color);
+ }
+
+ return g_list_prepend (NULL, palette);
+}
+
+GList *
+gimp_palette_load_psp (GimpContext *context,
+ GFile *file,
+ GInputStream *input,
+ GError **error)
+{
+ GimpPalette *palette;
+ gchar *palette_name;
+ guchar color_bytes[4];
+ gint number_of_colors;
+ gsize bytes_read;
+ gint i, j;
+ gboolean color_ok;
+ gchar buffer[4096];
+ /*Maximum valid file size: 256 * 4 * 3 + 256 * 2 ~= 3650 bytes */
+ gchar **lines;
+ gchar **ascii_colors;
+
+ g_return_val_if_fail (G_IS_FILE (file), NULL);
+ g_return_val_if_fail (G_IS_INPUT_STREAM (input), NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ palette_name = g_path_get_basename (gimp_file_get_utf8_name (file));
+ palette = GIMP_PALETTE (gimp_palette_new (context, palette_name));
+ g_free (palette_name);
+
+ if (! g_seekable_seek (G_SEEKABLE (input), 16, G_SEEK_SET, NULL, error))
+ {
+ g_object_unref (palette);
+ return NULL;
+ }
+
+ if (! g_input_stream_read_all (input, buffer, sizeof (buffer) - 1,
+ &bytes_read, NULL, error))
+ {
+ g_object_unref (palette);
+ return NULL;
+ }
+
+ buffer[bytes_read] = '\0';
+
+ lines = g_strsplit (buffer, "\x0d\x0a", -1);
+
+ number_of_colors = atoi (lines[0]);
+
+ for (i = 0; i < number_of_colors; i++)
+ {
+ if (lines[i + 1] == NULL)
+ {
+ g_printerr ("Premature end of file reading %s.",
+ gimp_file_get_utf8_name (file));
+ break;
+ }
+
+ ascii_colors = g_strsplit (lines[i + 1], " ", 3);
+ color_ok = TRUE;
+
+ for (j = 0 ; j < 3; j++)
+ {
+ if (ascii_colors[j] == NULL)
+ {
+ g_printerr ("Corrupted palette file %s.",
+ gimp_file_get_utf8_name (file));
+ color_ok = FALSE;
+ break;
+ }
+
+ color_bytes[j] = atoi (ascii_colors[j]);
+ }
+
+ if (color_ok)
+ {
+ GimpRGB color;
+
+ gimp_rgba_set_uchar (&color,
+ color_bytes[0],
+ color_bytes[1],
+ color_bytes[2],
+ 255);
+ gimp_palette_add_entry (palette, -1, NULL, &color);
+ }
+
+ g_strfreev (ascii_colors);
+ }
+
+ g_strfreev (lines);
+
+ return g_list_prepend (NULL, palette);
+}
+
+GList *
+gimp_palette_load_aco (GimpContext *context,
+ GFile *file,
+ GInputStream *input,
+ GError **error)
+{
+ GimpPalette *palette;
+ gchar *palette_name;
+ gint format_version;
+ gint number_of_colors;
+ gint i;
+ gchar header[4];
+ gsize bytes_read;
+
+ g_return_val_if_fail (G_IS_FILE (file), NULL);
+ g_return_val_if_fail (G_IS_INPUT_STREAM (input), NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ if (! g_input_stream_read_all (input, header, sizeof (header),
+ &bytes_read, NULL, error) ||
+ bytes_read != sizeof (header))
+ {
+ g_prefix_error (error,
+ _("Could not read header from palette file '%s': "),
+ gimp_file_get_utf8_name (file));
+ return NULL;
+ }
+
+ palette_name = g_path_get_basename (gimp_file_get_utf8_name (file));
+ palette = GIMP_PALETTE (gimp_palette_new (context, palette_name));
+ g_free (palette_name);
+
+ format_version = header[1] + (header[0] << 8);
+ number_of_colors = header[3] + (header[2] << 8);
+
+ for (i = 0; i < number_of_colors; i++)
+ {
+ gchar color_info[10];
+ gint color_space;
+ gint w, x, y, z;
+ gboolean color_ok = FALSE;
+ GimpRGB color;
+ GError *my_error = NULL;
+
+ if (! g_input_stream_read_all (input, color_info, sizeof (color_info),
+ &bytes_read, NULL, &my_error) ||
+ bytes_read != sizeof (color_info))
+ {
+ if (palette->colors)
+ {
+ g_message (_("Reading palette file '%s': "
+ "Read %d colors from truncated file: %s"),
+ gimp_file_get_utf8_name (file),
+ g_list_length (palette->colors),
+ my_error ?
+ my_error->message : _("Premature end of file."));
+ g_clear_error (&my_error);
+ break;
+ }
+
+ g_propagate_error (error, my_error);
+ g_object_unref (palette);
+
+ return NULL;
+ }
+
+ color_space = color_info[1] + (color_info[0] << 8);
+
+ w = (guchar) color_info[3] + ((guchar) color_info[2] << 8);
+ x = (guchar) color_info[5] + ((guchar) color_info[4] << 8);
+ y = (guchar) color_info[7] + ((guchar) color_info[6] << 8);
+ z = (guchar) color_info[9] + ((guchar) color_info[8] << 8);
+
+ if (color_space == 0) /* RGB */
+ {
+ gdouble R = ((gdouble) w) / 65536.0;
+ gdouble G = ((gdouble) x) / 65536.0;
+ gdouble B = ((gdouble) y) / 65536.0;
+
+ gimp_rgba_set (&color, R, G, B, 1.0);
+
+ color_ok = TRUE;
+ }
+ else if (color_space == 1) /* HSV */
+ {
+ GimpHSV hsv;
+
+ gdouble H = ((gdouble) w) / 65536.0;
+ gdouble S = ((gdouble) x) / 65536.0;
+ gdouble V = ((gdouble) y) / 65536.0;
+
+ gimp_hsva_set (&hsv, H, S, V, 1.0);
+ gimp_hsv_to_rgb (&hsv, &color);
+
+ color_ok = TRUE;
+ }
+ else if (color_space == 2) /* CMYK */
+ {
+ GimpCMYK cmyk;
+
+ gdouble C = 1.0 - (((gdouble) w) / 65536.0);
+ gdouble M = 1.0 - (((gdouble) x) / 65536.0);
+ gdouble Y = 1.0 - (((gdouble) y) / 65536.0);
+ gdouble K = 1.0 - (((gdouble) z) / 65536.0);
+
+ gimp_cmyka_set (&cmyk, C, M, Y, K, 1.0);
+ gimp_cmyk_to_rgb (&cmyk, &color);
+
+ color_ok = TRUE;
+ }
+ else if (color_space == 8) /* Grayscale */
+ {
+ gdouble K = 1.0 - (((gdouble) w) / 10000.0);
+
+ gimp_rgba_set (&color, K, K, K, 1.0);
+
+ color_ok = TRUE;
+ }
+ else if (color_space == 9) /* Wide? CMYK */
+ {
+ GimpCMYK cmyk;
+
+ gdouble C = 1.0 - (((gdouble) w) / 10000.0);
+ gdouble M = 1.0 - (((gdouble) x) / 10000.0);
+ gdouble Y = 1.0 - (((gdouble) y) / 10000.0);
+ gdouble K = 1.0 - (((gdouble) z) / 10000.0);
+
+ gimp_cmyka_set (&cmyk, C, M, Y, K, 1.0);
+ gimp_cmyk_to_rgb (&cmyk, &color);
+
+ color_ok = TRUE;
+ }
+ else
+ {
+ g_printerr ("Unsupported color space (%d) in ACO file %s\n",
+ color_space, gimp_file_get_utf8_name (file));
+ }
+
+ if (format_version == 2)
+ {
+ gchar format2_preamble[4];
+ gint number_of_chars;
+
+ if (! g_input_stream_read_all (input,
+ format2_preamble,
+ sizeof (format2_preamble),
+ &bytes_read, NULL, error) ||
+ bytes_read != sizeof (format2_preamble))
+ {
+ g_object_unref (palette);
+ return NULL;
+ }
+
+ number_of_chars = format2_preamble[3] + (format2_preamble[2] << 8);
+
+ if (! g_seekable_seek (G_SEEKABLE (input), number_of_chars * 2,
+ G_SEEK_SET, NULL, error))
+ {
+ g_object_unref (palette);
+ return NULL;
+ }
+ }
+
+ if (color_ok)
+ gimp_palette_add_entry (palette, -1, NULL, &color);
+ }
+
+ return g_list_prepend (NULL, palette);
+}
+
+
+GList *
+gimp_palette_load_css (GimpContext *context,
+ GFile *file,
+ GInputStream *input,
+ GError **error)
+{
+ GimpPalette *palette;
+ GDataInputStream *data_input;
+ gchar *name;
+ GRegex *regex;
+ gchar *buf;
+
+ g_return_val_if_fail (G_IS_FILE (file), NULL);
+ g_return_val_if_fail (G_IS_INPUT_STREAM (input), NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ regex = g_regex_new (".*color.*:(?P<param>.*);", G_REGEX_CASELESS, 0, error);
+ if (! regex)
+ return NULL;
+
+ name = g_path_get_basename (gimp_file_get_utf8_name (file));
+ palette = GIMP_PALETTE (gimp_palette_new (context, name));
+ g_free (name);
+
+ data_input = g_data_input_stream_new (input);
+
+ do
+ {
+ gsize buf_len = 1024;
+
+ buf = g_data_input_stream_read_line (data_input, &buf_len, NULL, NULL);
+
+ if (buf)
+ {
+ GMatchInfo *matches;
+
+ if (g_regex_match (regex, buf, 0, &matches))
+ {
+ GimpRGB color;
+ gchar *word = g_match_info_fetch_named (matches, "param");
+
+ if (gimp_rgb_parse_css (&color, word, -1))
+ {
+ if (! gimp_palette_find_entry (palette, &color, NULL))
+ {
+ gimp_palette_add_entry (palette, -1, NULL, &color);
+ }
+ }
+
+ g_free (word);
+ }
+ g_match_info_free (matches);
+ g_free (buf);
+ }
+ }
+ while (buf);
+
+ g_regex_unref (regex);
+ g_object_unref (data_input);
+
+ return g_list_prepend (NULL, palette);
+}
+
+GimpPaletteFileFormat
+gimp_palette_load_detect_format (GFile *file,
+ GInputStream *input)
+{
+ GimpPaletteFileFormat format = GIMP_PALETTE_FILE_FORMAT_UNKNOWN;
+ gchar header[16];
+ gsize bytes_read;
+
+ if (g_input_stream_read_all (input, &header, sizeof (header),
+ &bytes_read, NULL, NULL) &&
+ bytes_read == sizeof (header))
+ {
+ if (g_str_has_prefix (header + 0, "RIFF") &&
+ g_str_has_prefix (header + 8, "PAL data"))
+ {
+ format = GIMP_PALETTE_FILE_FORMAT_RIFF_PAL;
+ }
+ else if (g_str_has_prefix (header, "GIMP Palette"))
+ {
+ format = GIMP_PALETTE_FILE_FORMAT_GPL;
+ }
+ else if (g_str_has_prefix (header, "JASC-PAL"))
+ {
+ format = GIMP_PALETTE_FILE_FORMAT_PSP_PAL;
+ }
+ }
+
+ if (format == GIMP_PALETTE_FILE_FORMAT_UNKNOWN)
+ {
+ gchar *lower = g_ascii_strdown (gimp_file_get_utf8_name (file), -1);
+
+ if (g_str_has_suffix (lower, ".aco"))
+ {
+ format = GIMP_PALETTE_FILE_FORMAT_ACO;
+ }
+ else if (g_str_has_suffix (lower, ".css"))
+ {
+ format = GIMP_PALETTE_FILE_FORMAT_CSS;
+ }
+
+ g_free (lower);
+ }
+
+ if (format == GIMP_PALETTE_FILE_FORMAT_UNKNOWN)
+ {
+ GFileInfo *info = g_file_query_info (file,
+ G_FILE_ATTRIBUTE_STANDARD_SIZE,
+ G_FILE_QUERY_INFO_NONE,
+ NULL, NULL);
+
+ if (info)
+ {
+ goffset size = g_file_info_get_size (info);
+
+ if (size == 768)
+ format = GIMP_PALETTE_FILE_FORMAT_ACT;
+
+ g_object_unref (info);
+ }
+ }
+
+ g_seekable_seek (G_SEEKABLE (input), 0, G_SEEK_SET, NULL, NULL);
+
+ return format;
+}
diff --git a/app/core/gimppalette-load.h b/app/core/gimppalette-load.h
new file mode 100644
index 0000000..0dececf
--- /dev/null
+++ b/app/core/gimppalette-load.h
@@ -0,0 +1,66 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_PALETTE_LOAD_H__
+#define __GIMP_PALETTE_LOAD_H__
+
+
+#define GIMP_PALETTE_FILE_EXTENSION ".gpl"
+
+
+typedef enum
+{
+ GIMP_PALETTE_FILE_FORMAT_UNKNOWN,
+ GIMP_PALETTE_FILE_FORMAT_GPL, /* GIMP palette */
+ GIMP_PALETTE_FILE_FORMAT_RIFF_PAL, /* RIFF palette */
+ GIMP_PALETTE_FILE_FORMAT_ACT, /* Photoshop binary color palette */
+ GIMP_PALETTE_FILE_FORMAT_PSP_PAL, /* JASC's Paint Shop Pro color palette */
+ GIMP_PALETTE_FILE_FORMAT_ACO, /* Photoshop ACO color file */
+ GIMP_PALETTE_FILE_FORMAT_CSS /* Cascaded Stylesheet file (CSS) */
+} GimpPaletteFileFormat;
+
+
+GList * gimp_palette_load (GimpContext *context,
+ GFile *file,
+ GInputStream *input,
+ GError **error);
+GList * gimp_palette_load_act (GimpContext *context,
+ GFile *file,
+ GInputStream *input,
+ GError **error);
+GList * gimp_palette_load_riff (GimpContext *context,
+ GFile *file,
+ GInputStream *input,
+ GError **error);
+GList * gimp_palette_load_psp (GimpContext *context,
+ GFile *file,
+ GInputStream *input,
+ GError **error);
+GList * gimp_palette_load_aco (GimpContext *context,
+ GFile *file,
+ GInputStream *input,
+ GError **error);
+GList * gimp_palette_load_css (GimpContext *context,
+ GFile *file,
+ GInputStream *input,
+ GError **error);
+
+GimpPaletteFileFormat gimp_palette_load_detect_format (GFile *file,
+ GInputStream *input);
+
+
+#endif /* __GIMP_PALETTE_H__ */
diff --git a/app/core/gimppalette-save.c b/app/core/gimppalette-save.c
new file mode 100644
index 0000000..59556bf
--- /dev/null
+++ b/app/core/gimppalette-save.c
@@ -0,0 +1,77 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <cairo.h>
+#include <gegl.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+#include "libgimpbase/gimpbase.h"
+#include "libgimpcolor/gimpcolor.h"
+
+#include "core-types.h"
+
+#include "gimppalette.h"
+#include "gimppalette-save.h"
+
+#include "gimp-intl.h"
+
+
+gboolean
+gimp_palette_save (GimpData *data,
+ GOutputStream *output,
+ GError **error)
+{
+ GimpPalette *palette = GIMP_PALETTE (data);
+ GString *string;
+ GList *list;
+
+ string = g_string_new ("GIMP Palette\n");
+
+ g_string_append_printf (string,
+ "Name: %s\n"
+ "Columns: %d\n"
+ "#\n",
+ gimp_object_get_name (palette),
+ CLAMP (gimp_palette_get_columns (palette), 0, 256));
+
+ for (list = gimp_palette_get_colors (palette);
+ list;
+ list = g_list_next (list))
+ {
+ GimpPaletteEntry *entry = list->data;
+ guchar r, g, b;
+
+ gimp_rgb_get_uchar (&entry->color, &r, &g, &b);
+
+ g_string_append_printf (string, "%3d %3d %3d\t%s\n",
+ r, g, b, entry->name);
+ }
+
+ if (! g_output_stream_write_all (output, string->str, string->len,
+ NULL, NULL, error))
+ {
+ g_string_free (string, TRUE);
+
+ return FALSE;
+ }
+
+ g_string_free (string, TRUE);
+
+ return TRUE;
+}
diff --git a/app/core/gimppalette-save.h b/app/core/gimppalette-save.h
new file mode 100644
index 0000000..e72c448
--- /dev/null
+++ b/app/core/gimppalette-save.h
@@ -0,0 +1,28 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_PALETTE_SAVE_H__
+#define __GIMP_PALETTE_SAVE_H__
+
+
+/* don't call this function directly, use gimp_data_save() instead */
+gboolean gimp_palette_save (GimpData *data,
+ GOutputStream *output,
+ GError **error);
+
+
+#endif /* __GIMP_PALETTE_SAVE_H__ */
diff --git a/app/core/gimppalette.c b/app/core/gimppalette.c
new file mode 100644
index 0000000..f5b848c
--- /dev/null
+++ b/app/core/gimppalette.c
@@ -0,0 +1,721 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <cairo.h>
+#include <gegl.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+#include "libgimpbase/gimpbase.h"
+#include "libgimpcolor/gimpcolor.h"
+
+#include "core-types.h"
+
+#include "gimp-memsize.h"
+#include "gimppalette.h"
+#include "gimppalette-load.h"
+#include "gimppalette-save.h"
+#include "gimptagged.h"
+#include "gimptempbuf.h"
+
+#include "gimp-intl.h"
+
+
+#define RGB_EPSILON 1e-6
+
+
+/* local function prototypes */
+
+static void gimp_palette_tagged_iface_init (GimpTaggedInterface *iface);
+
+static void gimp_palette_finalize (GObject *object);
+
+static gint64 gimp_palette_get_memsize (GimpObject *object,
+ gint64 *gui_size);
+
+static void gimp_palette_get_preview_size (GimpViewable *viewable,
+ gint size,
+ gboolean popup,
+ gboolean dot_for_dot,
+ gint *width,
+ gint *height);
+static gboolean gimp_palette_get_popup_size (GimpViewable *viewable,
+ gint width,
+ gint height,
+ gboolean dot_for_dot,
+ gint *popup_width,
+ gint *popup_height);
+static GimpTempBuf * gimp_palette_get_new_preview (GimpViewable *viewable,
+ GimpContext *context,
+ gint width,
+ gint height);
+static gchar * gimp_palette_get_description (GimpViewable *viewable,
+ gchar **tooltip);
+static const gchar * gimp_palette_get_extension (GimpData *data);
+static void gimp_palette_copy (GimpData *data,
+ GimpData *src_data);
+
+static void gimp_palette_entry_free (GimpPaletteEntry *entry);
+static gint64 gimp_palette_entry_get_memsize (GimpPaletteEntry *entry,
+ gint64 *gui_size);
+static gchar * gimp_palette_get_checksum (GimpTagged *tagged);
+
+
+G_DEFINE_TYPE_WITH_CODE (GimpPalette, gimp_palette, GIMP_TYPE_DATA,
+ G_IMPLEMENT_INTERFACE (GIMP_TYPE_TAGGED,
+ gimp_palette_tagged_iface_init))
+
+#define parent_class gimp_palette_parent_class
+
+
+static void
+gimp_palette_class_init (GimpPaletteClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpObjectClass *gimp_object_class = GIMP_OBJECT_CLASS (klass);
+ GimpViewableClass *viewable_class = GIMP_VIEWABLE_CLASS (klass);
+ GimpDataClass *data_class = GIMP_DATA_CLASS (klass);
+
+ object_class->finalize = gimp_palette_finalize;
+
+ gimp_object_class->get_memsize = gimp_palette_get_memsize;
+
+ viewable_class->default_icon_name = "gtk-select-color";
+ viewable_class->get_preview_size = gimp_palette_get_preview_size;
+ viewable_class->get_popup_size = gimp_palette_get_popup_size;
+ viewable_class->get_new_preview = gimp_palette_get_new_preview;
+ viewable_class->get_description = gimp_palette_get_description;
+
+ data_class->save = gimp_palette_save;
+ data_class->get_extension = gimp_palette_get_extension;
+ data_class->copy = gimp_palette_copy;
+}
+
+static void
+gimp_palette_tagged_iface_init (GimpTaggedInterface *iface)
+{
+ iface->get_checksum = gimp_palette_get_checksum;
+}
+
+static void
+gimp_palette_init (GimpPalette *palette)
+{
+ palette->colors = NULL;
+ palette->n_colors = 0;
+ palette->n_columns = 0;
+}
+
+static void
+gimp_palette_finalize (GObject *object)
+{
+ GimpPalette *palette = GIMP_PALETTE (object);
+
+ if (palette->colors)
+ {
+ g_list_free_full (palette->colors,
+ (GDestroyNotify) gimp_palette_entry_free);
+ palette->colors = NULL;
+ }
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static gint64
+gimp_palette_get_memsize (GimpObject *object,
+ gint64 *gui_size)
+{
+ GimpPalette *palette = GIMP_PALETTE (object);
+ gint64 memsize = 0;
+
+ memsize += gimp_g_list_get_memsize_foreach (palette->colors,
+ (GimpMemsizeFunc)
+ gimp_palette_entry_get_memsize,
+ gui_size);
+
+ return memsize + GIMP_OBJECT_CLASS (parent_class)->get_memsize (object,
+ gui_size);
+}
+
+static void
+gimp_palette_get_preview_size (GimpViewable *viewable,
+ gint size,
+ gboolean popup,
+ gboolean dot_for_dot,
+ gint *width,
+ gint *height)
+{
+ *width = size;
+ *height = 1 + size / 2;
+}
+
+static gboolean
+gimp_palette_get_popup_size (GimpViewable *viewable,
+ gint width,
+ gint height,
+ gboolean dot_for_dot,
+ gint *popup_width,
+ gint *popup_height)
+{
+ GimpPalette *palette = GIMP_PALETTE (viewable);
+ gint p_width;
+ gint p_height;
+
+ if (! palette->n_colors)
+ return FALSE;
+
+ if (palette->n_columns)
+ p_width = palette->n_columns;
+ else
+ p_width = MIN (palette->n_colors, 16);
+
+ p_height = MAX (1, palette->n_colors / p_width);
+
+ if (p_width * 4 > width || p_height * 4 > height)
+ {
+ *popup_width = p_width * 4;
+ *popup_height = p_height * 4;
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static GimpTempBuf *
+gimp_palette_get_new_preview (GimpViewable *viewable,
+ GimpContext *context,
+ gint width,
+ gint height)
+{
+ GimpPalette *palette = GIMP_PALETTE (viewable);
+ GimpTempBuf *temp_buf;
+ guchar *buf;
+ guchar *b;
+ GList *list;
+ gint columns;
+ gint rows;
+ gint cell_size;
+ gint x, y;
+
+ temp_buf = gimp_temp_buf_new (width, height, babl_format ("R'G'B' u8"));
+ memset (gimp_temp_buf_get_data (temp_buf), 255, width * height * 3);
+
+ if (palette->n_columns > 1)
+ cell_size = MAX (4, width / palette->n_columns);
+ else
+ cell_size = 4;
+
+ columns = width / cell_size;
+ rows = height / cell_size;
+
+ buf = gimp_temp_buf_get_data (temp_buf);
+ b = g_new (guchar, width * 3);
+
+ list = palette->colors;
+
+ for (y = 0; y < rows && list; y++)
+ {
+ gint i;
+
+ memset (b, 255, width * 3);
+
+ for (x = 0; x < columns && list; x++)
+ {
+ GimpPaletteEntry *entry = list->data;
+
+ list = g_list_next (list);
+
+ gimp_rgb_get_uchar (&entry->color,
+ &b[x * cell_size * 3 + 0],
+ &b[x * cell_size * 3 + 1],
+ &b[x * cell_size * 3 + 2]);
+
+ for (i = 1; i < cell_size; i++)
+ {
+ b[(x * cell_size + i) * 3 + 0] = b[(x * cell_size) * 3 + 0];
+ b[(x * cell_size + i) * 3 + 1] = b[(x * cell_size) * 3 + 1];
+ b[(x * cell_size + i) * 3 + 2] = b[(x * cell_size) * 3 + 2];
+ }
+ }
+
+ for (i = 0; i < cell_size; i++)
+ memcpy (buf + ((y * cell_size + i) * width) * 3, b, width * 3);
+ }
+
+ g_free (b);
+
+ return temp_buf;
+}
+
+static gchar *
+gimp_palette_get_description (GimpViewable *viewable,
+ gchar **tooltip)
+{
+ GimpPalette *palette = GIMP_PALETTE (viewable);
+
+ return g_strdup_printf ("%s (%d)",
+ gimp_object_get_name (palette),
+ palette->n_colors);
+}
+
+GimpData *
+gimp_palette_new (GimpContext *context,
+ const gchar *name)
+{
+ g_return_val_if_fail (name != NULL, NULL);
+ g_return_val_if_fail (*name != '\0', NULL);
+
+ return g_object_new (GIMP_TYPE_PALETTE,
+ "name", name,
+ NULL);
+}
+
+GimpData *
+gimp_palette_get_standard (GimpContext *context)
+{
+ static GimpData *standard_palette = NULL;
+
+ if (! standard_palette)
+ {
+ standard_palette = gimp_palette_new (context, "Standard");
+
+ gimp_data_clean (standard_palette);
+ gimp_data_make_internal (standard_palette, "gimp-palette-standard");
+
+ g_object_add_weak_pointer (G_OBJECT (standard_palette),
+ (gpointer *) &standard_palette);
+ }
+
+ return standard_palette;
+}
+
+static const gchar *
+gimp_palette_get_extension (GimpData *data)
+{
+ return GIMP_PALETTE_FILE_EXTENSION;
+}
+
+static void
+gimp_palette_copy (GimpData *data,
+ GimpData *src_data)
+{
+ GimpPalette *palette = GIMP_PALETTE (data);
+ GimpPalette *src_palette = GIMP_PALETTE (src_data);
+ GList *list;
+
+ gimp_data_freeze (data);
+
+ if (palette->colors)
+ {
+ g_list_free_full (palette->colors,
+ (GDestroyNotify) gimp_palette_entry_free);
+ palette->colors = NULL;
+ }
+
+ palette->n_colors = 0;
+ palette->n_columns = src_palette->n_columns;
+
+ for (list = src_palette->colors; list; list = g_list_next (list))
+ {
+ GimpPaletteEntry *entry = list->data;
+
+ gimp_palette_add_entry (palette, -1, entry->name, &entry->color);
+ }
+
+ gimp_data_thaw (data);
+}
+
+static gchar *
+gimp_palette_get_checksum (GimpTagged *tagged)
+{
+ GimpPalette *palette = GIMP_PALETTE (tagged);
+ gchar *checksum_string = NULL;
+
+ if (palette->n_colors > 0)
+ {
+ GChecksum *checksum = g_checksum_new (G_CHECKSUM_MD5);
+ GList *color_iterator = palette->colors;
+
+ g_checksum_update (checksum, (const guchar *) &palette->n_colors, sizeof (palette->n_colors));
+ g_checksum_update (checksum, (const guchar *) &palette->n_columns, sizeof (palette->n_columns));
+
+ while (color_iterator)
+ {
+ GimpPaletteEntry *entry = (GimpPaletteEntry *) color_iterator->data;
+
+ g_checksum_update (checksum, (const guchar *) &entry->color, sizeof (entry->color));
+ if (entry->name)
+ g_checksum_update (checksum, (const guchar *) entry->name, strlen (entry->name));
+
+ color_iterator = g_list_next (color_iterator);
+ }
+
+ checksum_string = g_strdup (g_checksum_get_string (checksum));
+
+ g_checksum_free (checksum);
+ }
+
+ return checksum_string;
+}
+
+
+/* public functions */
+
+GList *
+gimp_palette_get_colors (GimpPalette *palette)
+{
+ g_return_val_if_fail (GIMP_IS_PALETTE (palette), NULL);
+
+ return palette->colors;
+}
+
+gint
+gimp_palette_get_n_colors (GimpPalette *palette)
+{
+ g_return_val_if_fail (GIMP_IS_PALETTE (palette), 0);
+
+ return palette->n_colors;
+}
+
+void
+gimp_palette_move_entry (GimpPalette *palette,
+ GimpPaletteEntry *entry,
+ gint position)
+{
+ GList *list;
+ gint pos = 0;
+
+ g_return_if_fail (GIMP_IS_PALETTE (palette));
+ g_return_if_fail (entry != NULL);
+
+ if (g_list_find (palette->colors, entry))
+ {
+ pos = entry->position;
+
+ if (entry->position == position)
+ return;
+
+ entry->position = position;
+ palette->colors = g_list_remove (palette->colors,
+ entry);
+ palette->colors = g_list_insert (palette->colors,
+ entry, position);
+
+ if (pos < position)
+ {
+ for (list = g_list_nth (palette->colors, pos);
+ list && pos < position;
+ list = g_list_next (list))
+ {
+ entry = (GimpPaletteEntry *) list->data;
+
+ entry->position = pos++;
+ }
+ }
+ else
+ {
+ for (list = g_list_nth (palette->colors, position + 1);
+ list && position < pos;
+ list = g_list_next (list))
+ {
+ entry = (GimpPaletteEntry *) list->data;
+
+ entry->position += 1;
+ pos--;
+ }
+ }
+
+ gimp_data_dirty (GIMP_DATA (palette));
+ }
+}
+
+GimpPaletteEntry *
+gimp_palette_add_entry (GimpPalette *palette,
+ gint position,
+ const gchar *name,
+ const GimpRGB *color)
+{
+ GimpPaletteEntry *entry;
+
+ g_return_val_if_fail (GIMP_IS_PALETTE (palette), NULL);
+ g_return_val_if_fail (color != NULL, NULL);
+
+ entry = g_slice_new0 (GimpPaletteEntry);
+
+ entry->color = *color;
+ entry->name = g_strdup (name ? name : _("Untitled"));
+
+ if (position < 0 || position >= palette->n_colors)
+ {
+ entry->position = palette->n_colors;
+ palette->colors = g_list_append (palette->colors, entry);
+ }
+ else
+ {
+ GList *list;
+
+ entry->position = position;
+ palette->colors = g_list_insert (palette->colors, entry, position);
+
+ /* renumber the displaced entries */
+ for (list = g_list_nth (palette->colors, position + 1);
+ list;
+ list = g_list_next (list))
+ {
+ GimpPaletteEntry *entry2 = list->data;
+
+ entry2->position += 1;
+ }
+ }
+
+ palette->n_colors += 1;
+
+ gimp_data_dirty (GIMP_DATA (palette));
+
+ return entry;
+}
+
+void
+gimp_palette_delete_entry (GimpPalette *palette,
+ GimpPaletteEntry *entry)
+{
+ GList *list;
+ gint pos = 0;
+
+ g_return_if_fail (GIMP_IS_PALETTE (palette));
+ g_return_if_fail (entry != NULL);
+
+ if (g_list_find (palette->colors, entry))
+ {
+ pos = entry->position;
+ gimp_palette_entry_free (entry);
+
+ palette->colors = g_list_remove (palette->colors, entry);
+
+ palette->n_colors--;
+
+ for (list = g_list_nth (palette->colors, pos);
+ list;
+ list = g_list_next (list))
+ {
+ entry = (GimpPaletteEntry *) list->data;
+
+ entry->position = pos++;
+ }
+
+ gimp_data_dirty (GIMP_DATA (palette));
+ }
+}
+
+gboolean
+gimp_palette_set_entry (GimpPalette *palette,
+ gint position,
+ const gchar *name,
+ const GimpRGB *color)
+{
+ GimpPaletteEntry *entry;
+
+ g_return_val_if_fail (GIMP_IS_PALETTE (palette), FALSE);
+ g_return_val_if_fail (color != NULL, FALSE);
+
+ entry = gimp_palette_get_entry (palette, position);
+
+ if (! entry)
+ return FALSE;
+
+ entry->color = *color;
+
+ if (entry->name)
+ g_free (entry->name);
+
+ entry->name = g_strdup (name);
+
+ gimp_data_dirty (GIMP_DATA (palette));
+
+ return TRUE;
+}
+
+gboolean
+gimp_palette_set_entry_color (GimpPalette *palette,
+ gint position,
+ const GimpRGB *color)
+{
+ GimpPaletteEntry *entry;
+
+ g_return_val_if_fail (GIMP_IS_PALETTE (palette), FALSE);
+ g_return_val_if_fail (color != NULL, FALSE);
+
+ entry = gimp_palette_get_entry (palette, position);
+
+ if (! entry)
+ return FALSE;
+
+ entry->color = *color;
+
+ gimp_data_dirty (GIMP_DATA (palette));
+
+ return TRUE;
+}
+
+gboolean
+gimp_palette_set_entry_name (GimpPalette *palette,
+ gint position,
+ const gchar *name)
+{
+ GimpPaletteEntry *entry;
+
+ g_return_val_if_fail (GIMP_IS_PALETTE (palette), FALSE);
+
+ entry = gimp_palette_get_entry (palette, position);
+
+ if (! entry)
+ return FALSE;
+
+ if (entry->name)
+ g_free (entry->name);
+
+ entry->name = g_strdup (name);
+
+ gimp_data_dirty (GIMP_DATA (palette));
+
+ return TRUE;
+}
+
+GimpPaletteEntry *
+gimp_palette_get_entry (GimpPalette *palette,
+ gint position)
+{
+ g_return_val_if_fail (GIMP_IS_PALETTE (palette), NULL);
+
+ return g_list_nth_data (palette->colors, position);
+}
+
+void
+gimp_palette_set_columns (GimpPalette *palette,
+ gint columns)
+{
+ g_return_if_fail (GIMP_IS_PALETTE (palette));
+
+ columns = CLAMP (columns, 0, 64);
+
+ if (palette->n_columns != columns)
+ {
+ palette->n_columns = columns;
+
+ gimp_data_dirty (GIMP_DATA (palette));
+ }
+}
+
+gint
+gimp_palette_get_columns (GimpPalette *palette)
+{
+ g_return_val_if_fail (GIMP_IS_PALETTE (palette), 0);
+
+ return palette->n_columns;
+}
+
+GimpPaletteEntry *
+gimp_palette_find_entry (GimpPalette *palette,
+ const GimpRGB *color,
+ GimpPaletteEntry *start_from)
+{
+ GimpPaletteEntry *entry;
+
+ g_return_val_if_fail (GIMP_IS_PALETTE (palette), NULL);
+ g_return_val_if_fail (color != NULL, NULL);
+
+ if (! start_from)
+ {
+ GList *list;
+
+ /* search from the start */
+
+ for (list = palette->colors; list; list = g_list_next (list))
+ {
+ entry = (GimpPaletteEntry *) list->data;
+ if (gimp_rgb_distance (&entry->color, color) < RGB_EPSILON)
+ return entry;
+ }
+ }
+ else if (gimp_rgb_distance (&start_from->color, color) < RGB_EPSILON)
+ {
+ return start_from;
+ }
+ else
+ {
+ GList *old = g_list_find (palette->colors, start_from);
+ GList *next;
+ GList *prev;
+
+ g_return_val_if_fail (old != NULL, NULL);
+
+ next = old->next;
+ prev = old->prev;
+
+ /* proximity-based search */
+
+ while (next || prev)
+ {
+ if (next)
+ {
+ entry = (GimpPaletteEntry *) next->data;
+ if (gimp_rgb_distance (&entry->color, color) < RGB_EPSILON)
+ return entry;
+
+ next = next->next;
+ }
+
+ if (prev)
+ {
+ entry = (GimpPaletteEntry *) prev->data;
+ if (gimp_rgb_distance (&entry->color, color) < RGB_EPSILON)
+ return entry;
+
+ prev = prev->prev;
+ }
+ }
+ }
+
+ return NULL;
+}
+
+
+/* private functions */
+
+static void
+gimp_palette_entry_free (GimpPaletteEntry *entry)
+{
+ g_return_if_fail (entry != NULL);
+
+ g_free (entry->name);
+
+ g_slice_free (GimpPaletteEntry, entry);
+}
+
+static gint64
+gimp_palette_entry_get_memsize (GimpPaletteEntry *entry,
+ gint64 *gui_size)
+{
+ gint64 memsize = sizeof (GimpPaletteEntry);
+
+ memsize += gimp_string_get_memsize (entry->name);
+
+ return memsize;
+}
diff --git a/app/core/gimppalette.h b/app/core/gimppalette.h
new file mode 100644
index 0000000..5bc6d8c
--- /dev/null
+++ b/app/core/gimppalette.h
@@ -0,0 +1,103 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_PALETTE_H__
+#define __GIMP_PALETTE_H__
+
+
+#include "gimpdata.h"
+
+
+#define GIMP_TYPE_PALETTE (gimp_palette_get_type ())
+#define GIMP_PALETTE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_PALETTE, GimpPalette))
+#define GIMP_PALETTE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_PALETTE, GimpPaletteClass))
+#define GIMP_IS_PALETTE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_PALETTE))
+#define GIMP_IS_PALETTE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_PALETTE))
+#define GIMP_PALETTE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_PALETTE, GimpPaletteClass))
+
+
+struct _GimpPaletteEntry
+{
+ GimpRGB color;
+ gchar *name;
+
+ /* EEK */
+ gint position;
+};
+
+
+typedef struct _GimpPaletteClass GimpPaletteClass;
+
+struct _GimpPalette
+{
+ GimpData parent_instance;
+
+ GList *colors;
+ gint n_colors;
+
+ gint n_columns;
+};
+
+struct _GimpPaletteClass
+{
+ GimpDataClass parent_class;
+};
+
+
+GType gimp_palette_get_type (void) G_GNUC_CONST;
+
+GimpData * gimp_palette_new (GimpContext *context,
+ const gchar *name);
+GimpData * gimp_palette_get_standard (GimpContext *context);
+
+GList * gimp_palette_get_colors (GimpPalette *palette);
+gint gimp_palette_get_n_colors (GimpPalette *palette);
+
+void gimp_palette_move_entry (GimpPalette *palette,
+ GimpPaletteEntry *entry,
+ gint position);
+
+GimpPaletteEntry * gimp_palette_add_entry (GimpPalette *palette,
+ gint position,
+ const gchar *name,
+ const GimpRGB *color);
+void gimp_palette_delete_entry (GimpPalette *palette,
+ GimpPaletteEntry *entry);
+
+gboolean gimp_palette_set_entry (GimpPalette *palette,
+ gint position,
+ const gchar *name,
+ const GimpRGB *color);
+gboolean gimp_palette_set_entry_color (GimpPalette *palette,
+ gint position,
+ const GimpRGB *color);
+gboolean gimp_palette_set_entry_name (GimpPalette *palette,
+ gint position,
+ const gchar *name);
+GimpPaletteEntry * gimp_palette_get_entry (GimpPalette *palette,
+ gint position);
+
+void gimp_palette_set_columns (GimpPalette *palette,
+ gint columns);
+gint gimp_palette_get_columns (GimpPalette *palette);
+
+GimpPaletteEntry * gimp_palette_find_entry (GimpPalette *palette,
+ const GimpRGB *color,
+ GimpPaletteEntry *start_from);
+
+
+#endif /* __GIMP_PALETTE_H__ */
diff --git a/app/core/gimppalettemru.c b/app/core/gimppalettemru.c
new file mode 100644
index 0000000..f68c469
--- /dev/null
+++ b/app/core/gimppalettemru.c
@@ -0,0 +1,230 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimppalettemru.c
+ * Copyright (C) 2014 Michael Natterer <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <cairo.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpcolor/gimpcolor.h"
+#include "libgimpconfig/gimpconfig.h"
+
+#include "core-types.h"
+
+#include "gimppalettemru.h"
+
+#include "gimp-intl.h"
+
+
+#define MAX_N_COLORS 256
+#define RGBA_EPSILON 1e-4
+
+enum
+{
+ COLOR_HISTORY = 1
+};
+
+
+G_DEFINE_TYPE (GimpPaletteMru, gimp_palette_mru, GIMP_TYPE_PALETTE)
+
+#define parent_class gimp_palette_mru_parent_class
+
+
+static void
+gimp_palette_mru_class_init (GimpPaletteMruClass *klass)
+{
+}
+
+static void
+gimp_palette_mru_init (GimpPaletteMru *palette)
+{
+}
+
+
+/* public functions */
+
+GimpData *
+gimp_palette_mru_new (const gchar *name)
+{
+ GimpPaletteMru *palette;
+
+ g_return_val_if_fail (name != NULL, NULL);
+ g_return_val_if_fail (*name != '\0', NULL);
+
+ palette = g_object_new (GIMP_TYPE_PALETTE_MRU,
+ "name", name,
+ "mime-type", "application/x-gimp-palette",
+ NULL);
+
+ return GIMP_DATA (palette);
+}
+
+void
+gimp_palette_mru_load (GimpPaletteMru *mru,
+ GFile *file)
+{
+ GimpPalette *palette;
+ GScanner *scanner;
+ GTokenType token;
+
+ g_return_if_fail (GIMP_IS_PALETTE_MRU (mru));
+ g_return_if_fail (G_IS_FILE (file));
+
+ palette = GIMP_PALETTE (mru);
+
+ scanner = gimp_scanner_new_gfile (file, NULL);
+ if (! scanner)
+ return;
+
+ g_scanner_scope_add_symbol (scanner, 0, "color-history",
+ GINT_TO_POINTER (COLOR_HISTORY));
+
+ token = G_TOKEN_LEFT_PAREN;
+
+ while (g_scanner_peek_next_token (scanner) == token)
+ {
+ token = g_scanner_get_next_token (scanner);
+
+ switch (token)
+ {
+ case G_TOKEN_LEFT_PAREN:
+ token = G_TOKEN_SYMBOL;
+ break;
+
+ case G_TOKEN_SYMBOL:
+ if (scanner->value.v_symbol == GINT_TO_POINTER (COLOR_HISTORY))
+ {
+ while (g_scanner_peek_next_token (scanner) == G_TOKEN_LEFT_PAREN)
+ {
+ GimpRGB color;
+
+ if (! gimp_scanner_parse_color (scanner, &color))
+ goto end;
+
+ gimp_palette_add_entry (palette, -1,
+ _("History Color"), &color);
+
+ if (gimp_palette_get_n_colors (palette) == MAX_N_COLORS)
+ goto end;
+ }
+ }
+ token = G_TOKEN_RIGHT_PAREN;
+ break;
+
+ case G_TOKEN_RIGHT_PAREN:
+ token = G_TOKEN_LEFT_PAREN;
+ break;
+
+ default: /* do nothing */
+ break;
+ }
+ }
+
+ end:
+ gimp_scanner_destroy (scanner);
+}
+
+void
+gimp_palette_mru_save (GimpPaletteMru *mru,
+ GFile *file)
+{
+ GimpPalette *palette;
+ GimpConfigWriter *writer;
+ GList *list;
+
+ g_return_if_fail (GIMP_IS_PALETTE_MRU (mru));
+ g_return_if_fail (G_IS_FILE (file));
+
+ writer = gimp_config_writer_new_gfile (file,
+ TRUE,
+ "GIMP colorrc\n\n"
+ "This file holds a list of "
+ "recently used colors.",
+ NULL);
+ if (! writer)
+ return;
+
+ palette = GIMP_PALETTE (mru);
+
+ gimp_config_writer_open (writer, "color-history");
+
+ for (list = palette->colors; list; list = g_list_next (list))
+ {
+ GimpPaletteEntry *entry = list->data;
+ gchar buf[4][G_ASCII_DTOSTR_BUF_SIZE];
+
+ g_ascii_dtostr (buf[0], G_ASCII_DTOSTR_BUF_SIZE, entry->color.r);
+ g_ascii_dtostr (buf[1], G_ASCII_DTOSTR_BUF_SIZE, entry->color.g);
+ g_ascii_dtostr (buf[2], G_ASCII_DTOSTR_BUF_SIZE, entry->color.b);
+ g_ascii_dtostr (buf[3], G_ASCII_DTOSTR_BUF_SIZE, entry->color.a);
+
+ gimp_config_writer_open (writer, "color-rgba");
+ gimp_config_writer_printf (writer, "%s %s %s %s",
+ buf[0], buf[1], buf[2], buf[3]);
+ gimp_config_writer_close (writer);
+ }
+
+ gimp_config_writer_close (writer);
+
+ gimp_config_writer_finish (writer, "end of colorrc", NULL);
+}
+
+void
+gimp_palette_mru_add (GimpPaletteMru *mru,
+ const GimpRGB *color)
+{
+ GimpPalette *palette;
+ GList *list;
+
+ g_return_if_fail (GIMP_IS_PALETTE_MRU (mru));
+ g_return_if_fail (color != NULL);
+
+ palette = GIMP_PALETTE (mru);
+
+ /* is the added color already there? */
+ for (list = gimp_palette_get_colors (palette);
+ list;
+ list = g_list_next (list))
+ {
+ GimpPaletteEntry *entry = list->data;
+
+ if (gimp_rgba_distance (&entry->color, color) < RGBA_EPSILON)
+ {
+ gimp_palette_move_entry (palette, entry, 0);
+
+ /* Even though they are nearly the same color, let's make them
+ * exactly equal.
+ */
+ gimp_palette_set_entry_color (palette, 0, color);
+
+ return;
+ }
+ }
+
+ if (gimp_palette_get_n_colors (palette) == MAX_N_COLORS)
+ {
+ gimp_palette_delete_entry (palette,
+ gimp_palette_get_entry (palette,
+ MAX_N_COLORS - 1));
+ }
+
+ gimp_palette_add_entry (palette, 0, _("History Color"), color);
+}
diff --git a/app/core/gimppalettemru.h b/app/core/gimppalettemru.h
new file mode 100644
index 0000000..0525553
--- /dev/null
+++ b/app/core/gimppalettemru.h
@@ -0,0 +1,62 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimppalettemru.h
+ * Copyright (C) 2014 Michael Natterer <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_PALETTE_MRU_H__
+#define __GIMP_PALETTE_MRU_H__
+
+
+#include "gimppalette.h"
+
+
+#define GIMP_TYPE_PALETTE_MRU (gimp_palette_mru_get_type ())
+#define GIMP_PALETTE_MRU(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_PALETTE_MRU, GimpPaletteMru))
+#define GIMP_PALETTE_MRU_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_PALETTE_MRU, GimpPaletteMruClass))
+#define GIMP_IS_PALETTE_MRU(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_PALETTE_MRU))
+#define GIMP_IS_PALETTE_MRU_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_PALETTE_MRU))
+#define GIMP_PALETTE_MRU_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_PALETTE_MRU, GimpPaletteMruClass))
+
+
+typedef struct _GimpPaletteMruClass GimpPaletteMruClass;
+
+struct _GimpPaletteMru
+{
+ GimpPalette parent_instance;
+};
+
+struct _GimpPaletteMruClass
+{
+ GimpPaletteClass parent_class;
+};
+
+
+GType gimp_palette_mru_get_type (void) G_GNUC_CONST;
+
+GimpData * gimp_palette_mru_new (const gchar *name);
+
+void gimp_palette_mru_load (GimpPaletteMru *mru,
+ GFile *file);
+void gimp_palette_mru_save (GimpPaletteMru *mru,
+ GFile *file);
+
+void gimp_palette_mru_add (GimpPaletteMru *mru,
+ const GimpRGB *color);
+
+
+#endif /* __GIMP_PALETTE_MRU_H__ */
diff --git a/app/core/gimpparamspecs-desc.c b/app/core/gimpparamspecs-desc.c
new file mode 100644
index 0000000..a59f14f
--- /dev/null
+++ b/app/core/gimpparamspecs-desc.c
@@ -0,0 +1,193 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gio/gio.h>
+
+#include "libgimpbase/gimpbase.h"
+
+#include "core-types.h"
+
+#include "gimpparamspecs.h"
+#include "gimpparamspecs-desc.h"
+
+
+static inline const gchar *
+gimp_param_spec_get_blurb (GParamSpec *pspec)
+{
+ const gchar *blurb = g_param_spec_get_blurb (pspec);
+
+ return blurb ? blurb : "";
+}
+
+static gchar *
+gimp_param_spec_boolean_desc (GParamSpec *pspec)
+{
+ const gchar *blurb = gimp_param_spec_get_blurb (pspec);
+
+ return g_strconcat (blurb, " (TRUE or FALSE)", NULL);
+}
+
+static gchar *
+gimp_param_spec_int32_desc (GParamSpec *pspec)
+{
+ GParamSpecInt *ispec = G_PARAM_SPEC_INT (pspec);
+ const gchar *blurb = gimp_param_spec_get_blurb (pspec);
+
+ if (ispec->minimum == G_MININT32 && ispec->maximum == G_MAXINT32)
+ return g_strdup (blurb);
+
+ if (ispec->minimum == G_MININT32)
+ return g_strdup_printf ("%s (%s <= %d)", blurb,
+ g_param_spec_get_name (pspec),
+ ispec->maximum);
+
+ if (ispec->maximum == G_MAXINT32)
+ return g_strdup_printf ("%s (%s >= %d)", blurb,
+ g_param_spec_get_name (pspec),
+ ispec->minimum);
+
+ return g_strdup_printf ("%s (%d <= %s <= %d)", blurb,
+ ispec->minimum,
+ g_param_spec_get_name (pspec),
+ ispec->maximum);
+}
+
+static gchar *
+gimp_param_spec_double_desc (GParamSpec *pspec)
+{
+ GParamSpecDouble *dspec = G_PARAM_SPEC_DOUBLE (pspec);
+ const gchar *blurb = gimp_param_spec_get_blurb (pspec);
+
+ if (dspec->minimum == - G_MAXDOUBLE && dspec->maximum == G_MAXDOUBLE)
+ return g_strdup (blurb);
+
+ if (dspec->minimum == - G_MAXDOUBLE)
+ return g_strdup_printf ("%s (%s <= %g)", blurb,
+ g_param_spec_get_name (pspec),
+ dspec->maximum);
+
+ if (dspec->maximum == G_MAXDOUBLE)
+ return g_strdup_printf ("%s (%s >= %g)", blurb,
+ g_param_spec_get_name (pspec),
+ dspec->minimum);
+
+ return g_strdup_printf ("%s (%g <= %s <= %g)", blurb,
+ dspec->minimum,
+ g_param_spec_get_name (pspec),
+ dspec->maximum);
+}
+
+static gchar *
+gimp_param_spec_enum_desc (GParamSpec *pspec)
+{
+ const gchar *blurb = gimp_param_spec_get_blurb (pspec);
+ GString *str = g_string_new (blurb);
+ GEnumClass *enum_class = g_type_class_peek (pspec->value_type);
+ GEnumValue *enum_value;
+ GSList *excluded;
+ gint i, n;
+
+ if (GIMP_IS_PARAM_SPEC_ENUM (pspec))
+ excluded = GIMP_PARAM_SPEC_ENUM (pspec)->excluded_values;
+ else
+ excluded = NULL;
+
+ g_string_append (str, " { ");
+
+ for (i = 0, n = 0, enum_value = enum_class->values;
+ i < enum_class->n_values;
+ i++, enum_value++)
+ {
+ GSList *list;
+ gchar *name;
+
+ for (list = excluded; list; list = list->next)
+ {
+ gint value = GPOINTER_TO_INT (list->data);
+
+ if (value == enum_value->value)
+ break;
+ }
+
+ if (list)
+ continue;
+
+ if (n > 0)
+ g_string_append (str, ", ");
+
+ if (G_LIKELY (g_str_has_prefix (enum_value->value_name, "GIMP_")))
+ name = gimp_canonicalize_identifier (enum_value->value_name + 5);
+ else
+ name = gimp_canonicalize_identifier (enum_value->value_name);
+
+ g_string_append (str, name);
+ g_free (name);
+
+ g_string_append_printf (str, " (%d)", enum_value->value);
+
+ n++;
+ }
+
+ g_string_append (str, " }");
+
+ return g_string_free (str, FALSE);
+}
+
+/**
+ * gimp_param_spec_get_desc:
+ * @pspec: a #GParamSpec
+ *
+ * This function creates a description of the passed @pspec, which is
+ * suitable for use in the PDB. Actually, it currently only deals with
+ * parameter types used in the PDB and should not be used for anything
+ * else.
+ *
+ * Return value: A newly allocated string describing the parameter.
+ */
+gchar *
+gimp_param_spec_get_desc (GParamSpec *pspec)
+{
+ g_return_val_if_fail (G_IS_PARAM_SPEC (pspec), NULL);
+
+ if (GIMP_IS_PARAM_SPEC_UNIT (pspec))
+ {
+ }
+ else if (GIMP_IS_PARAM_SPEC_INT32 (pspec))
+ {
+ return gimp_param_spec_int32_desc (pspec);
+ }
+ else
+ {
+ switch (G_TYPE_FUNDAMENTAL (pspec->value_type))
+ {
+ case G_TYPE_BOOLEAN:
+ return gimp_param_spec_boolean_desc (pspec);
+
+ case G_TYPE_DOUBLE:
+ return gimp_param_spec_double_desc (pspec);
+
+ case G_TYPE_ENUM:
+ return gimp_param_spec_enum_desc (pspec);
+ }
+ }
+
+ return g_strdup (g_param_spec_get_blurb (pspec));
+}
diff --git a/app/core/gimpparamspecs-desc.h b/app/core/gimpparamspecs-desc.h
new file mode 100644
index 0000000..1e6ea2d
--- /dev/null
+++ b/app/core/gimpparamspecs-desc.h
@@ -0,0 +1,25 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_PARAM_SPECS_DESC_H__
+#define __GIMP_PARAM_SPECS_DESC_H__
+
+
+gchar * gimp_param_spec_get_desc (GParamSpec *pspec);
+
+
+#endif /* __GIMP_PARAM_SPECS_DESC_H__ */
diff --git a/app/core/gimpparamspecs-duplicate.c b/app/core/gimpparamspecs-duplicate.c
new file mode 100644
index 0000000..ff0587b
--- /dev/null
+++ b/app/core/gimpparamspecs-duplicate.c
@@ -0,0 +1,269 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpparamspecs-duplicate.c
+ * Copyright (C) 2008-2014 Michael Natterer <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <cairo.h>
+#include <gegl.h>
+#include <gegl-paramspecs.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+#include "libgimpcolor/gimpcolor.h"
+#include "libgimpconfig/gimpconfig.h"
+
+#include "core-types.h"
+
+#include "gegl/gimp-gegl-utils.h"
+
+#include "gimpparamspecs.h"
+#include "gimpparamspecs-duplicate.h"
+
+
+/* FIXME: this code is not yet general as it should be (gegl tool only atm) */
+
+GParamSpec *
+gimp_param_spec_duplicate (GParamSpec *pspec)
+{
+ GParamSpec *copy = NULL;
+ GParamFlags flags;
+
+ g_return_val_if_fail (pspec != NULL, NULL);
+
+ flags = pspec->flags;
+
+ if (! gimp_gegl_param_spec_has_key (pspec, "role", "output-extent"))
+ flags |= GIMP_CONFIG_PARAM_SERIALIZE;
+
+ if (G_IS_PARAM_SPEC_STRING (pspec))
+ {
+ GParamSpecString *spec = G_PARAM_SPEC_STRING (pspec);
+
+ if (GEGL_IS_PARAM_SPEC_FILE_PATH (pspec))
+ {
+ copy = gimp_param_spec_config_path (pspec->name,
+ g_param_spec_get_nick (pspec),
+ g_param_spec_get_blurb (pspec),
+ GIMP_CONFIG_PATH_FILE,
+ spec->default_value,
+ flags);
+ }
+ else
+ {
+ copy = g_param_spec_string (pspec->name,
+ g_param_spec_get_nick (pspec),
+ g_param_spec_get_blurb (pspec),
+ spec->default_value,
+ flags);
+ }
+ }
+ else if (G_IS_PARAM_SPEC_BOOLEAN (pspec))
+ {
+ GParamSpecBoolean *spec = G_PARAM_SPEC_BOOLEAN (pspec);
+
+ copy = g_param_spec_boolean (pspec->name,
+ g_param_spec_get_nick (pspec),
+ g_param_spec_get_blurb (pspec),
+ spec->default_value,
+ flags);
+ }
+ else if (G_IS_PARAM_SPEC_ENUM (pspec))
+ {
+ GParamSpecEnum *spec = G_PARAM_SPEC_ENUM (pspec);
+
+ copy = g_param_spec_enum (pspec->name,
+ g_param_spec_get_nick (pspec),
+ g_param_spec_get_blurb (pspec),
+ G_TYPE_FROM_CLASS (spec->enum_class),
+ spec->default_value,
+ flags);
+ }
+ else if (GEGL_IS_PARAM_SPEC_DOUBLE (pspec))
+ {
+ GeglParamSpecDouble *gspec = GEGL_PARAM_SPEC_DOUBLE (pspec);
+ GParamSpecDouble *spec = G_PARAM_SPEC_DOUBLE (pspec);
+
+ copy = gegl_param_spec_double (pspec->name,
+ g_param_spec_get_nick (pspec),
+ g_param_spec_get_blurb (pspec),
+ spec->minimum,
+ spec->maximum,
+ spec->default_value,
+ gspec->ui_minimum,
+ gspec->ui_maximum,
+ gspec->ui_gamma,
+ flags);
+ gegl_param_spec_double_set_steps (GEGL_PARAM_SPEC_DOUBLE (copy),
+ gspec->ui_step_small,
+ gspec->ui_step_big);
+ gegl_param_spec_double_set_digits (GEGL_PARAM_SPEC_DOUBLE (copy),
+ gspec->ui_digits);
+ }
+ else if (G_IS_PARAM_SPEC_DOUBLE (pspec))
+ {
+ GParamSpecDouble *spec = G_PARAM_SPEC_DOUBLE (pspec);
+
+ copy = g_param_spec_double (pspec->name,
+ g_param_spec_get_nick (pspec),
+ g_param_spec_get_blurb (pspec),
+ spec->minimum,
+ spec->maximum,
+ spec->default_value,
+ flags);
+ }
+ else if (G_IS_PARAM_SPEC_FLOAT (pspec))
+ {
+ GParamSpecFloat *spec = G_PARAM_SPEC_FLOAT (pspec);
+
+ copy = g_param_spec_float (pspec->name,
+ g_param_spec_get_nick (pspec),
+ g_param_spec_get_blurb (pspec),
+ spec->minimum,
+ spec->maximum,
+ spec->default_value,
+ flags);
+ }
+ else if (GEGL_IS_PARAM_SPEC_INT (pspec))
+ {
+ GeglParamSpecInt *gspec = GEGL_PARAM_SPEC_INT (pspec);
+ GParamSpecInt *spec = G_PARAM_SPEC_INT (pspec);
+
+ copy = gegl_param_spec_int (pspec->name,
+ g_param_spec_get_nick (pspec),
+ g_param_spec_get_blurb (pspec),
+ spec->minimum,
+ spec->maximum,
+ spec->default_value,
+ gspec->ui_minimum,
+ gspec->ui_maximum,
+ gspec->ui_gamma,
+ flags);
+ gegl_param_spec_int_set_steps (GEGL_PARAM_SPEC_INT (copy),
+ gspec->ui_step_small,
+ gspec->ui_step_big);
+ }
+ else if (GEGL_IS_PARAM_SPEC_SEED (pspec))
+ {
+ GParamSpecUInt *spec = G_PARAM_SPEC_UINT (pspec);
+ GeglParamSpecSeed *gspec = GEGL_PARAM_SPEC_SEED (pspec);
+
+ copy = gegl_param_spec_seed (pspec->name,
+ g_param_spec_get_nick (pspec),
+ g_param_spec_get_blurb (pspec),
+ pspec->flags |
+ GIMP_CONFIG_PARAM_SERIALIZE);
+
+ G_PARAM_SPEC_UINT (copy)->minimum = spec->minimum;
+ G_PARAM_SPEC_UINT (copy)->maximum = spec->maximum;
+
+ GEGL_PARAM_SPEC_SEED (copy)->ui_minimum = gspec->ui_minimum;
+ GEGL_PARAM_SPEC_SEED (copy)->ui_maximum = gspec->ui_maximum;
+ }
+ else if (G_IS_PARAM_SPEC_INT (pspec))
+ {
+ GParamSpecInt *spec = G_PARAM_SPEC_INT (pspec);
+
+ copy = g_param_spec_int (pspec->name,
+ g_param_spec_get_nick (pspec),
+ g_param_spec_get_blurb (pspec),
+ spec->minimum,
+ spec->maximum,
+ spec->default_value,
+ flags);
+ }
+ else if (G_IS_PARAM_SPEC_UINT (pspec))
+ {
+ GParamSpecUInt *spec = G_PARAM_SPEC_UINT (pspec);
+
+ copy = g_param_spec_uint (pspec->name,
+ g_param_spec_get_nick (pspec),
+ g_param_spec_get_blurb (pspec),
+ spec->minimum,
+ spec->maximum,
+ spec->default_value,
+ flags);
+ }
+ else if (GIMP_IS_PARAM_SPEC_RGB (pspec))
+ {
+ GValue value = G_VALUE_INIT;
+ GimpRGB color;
+
+ g_value_init (&value, GIMP_TYPE_RGB);
+ g_param_value_set_default (pspec, &value);
+ gimp_value_get_rgb (&value, &color);
+ g_value_unset (&value);
+
+ copy = gimp_param_spec_rgb (pspec->name,
+ g_param_spec_get_nick (pspec),
+ g_param_spec_get_blurb (pspec),
+ gimp_param_spec_rgb_has_alpha (pspec),
+ &color,
+ flags);
+ }
+ else if (GEGL_IS_PARAM_SPEC_COLOR (pspec))
+ {
+ GeglColor *gegl_color;
+ GimpRGB gimp_color;
+ gdouble r = 0.0;
+ gdouble g = 0.0;
+ gdouble b = 0.0;
+ gdouble a = 1.0;
+ GValue value = G_VALUE_INIT;
+
+ g_value_init (&value, GEGL_TYPE_COLOR);
+ g_param_value_set_default (pspec, &value);
+
+ gegl_color = g_value_get_object (&value);
+ if (gegl_color)
+ gegl_color_get_rgba (gegl_color, &r, &g, &b, &a);
+
+ gimp_rgba_set (&gimp_color, r, g, b, a);
+
+ g_value_unset (&value);
+
+ copy = gimp_param_spec_rgb (pspec->name,
+ g_param_spec_get_nick (pspec),
+ g_param_spec_get_blurb (pspec),
+ TRUE,
+ &gimp_color,
+ flags);
+ }
+ else if (G_IS_PARAM_SPEC_OBJECT (pspec) ||
+ G_IS_PARAM_SPEC_POINTER (pspec))
+ {
+ /* silently ignore object properties */
+ }
+ else
+ {
+ g_warning ("%s: not supported: %s (%s)\n", G_STRFUNC,
+ g_type_name (G_TYPE_FROM_INSTANCE (pspec)), pspec->name);
+ }
+
+ if (copy)
+ {
+ GQuark quark = g_quark_from_static_string ("gegl-property-keys");
+ GHashTable *keys = g_param_spec_get_qdata (pspec, quark);
+
+ if (keys)
+ g_param_spec_set_qdata_full (copy, quark, g_hash_table_ref (keys),
+ (GDestroyNotify) g_hash_table_unref);
+ }
+
+ return copy;
+}
diff --git a/app/core/gimpparamspecs-duplicate.h b/app/core/gimpparamspecs-duplicate.h
new file mode 100644
index 0000000..2bb1432
--- /dev/null
+++ b/app/core/gimpparamspecs-duplicate.h
@@ -0,0 +1,28 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpparamspecs-duplicate.h
+ * Copyright (C) 2008-2009 Michael Natterer <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_PARAM_SPECS_DUPLICATE_H__
+#define __GIMP_PARAM_SPECS_DUPLICATE_H__
+
+
+GParamSpec * gimp_param_spec_duplicate (GParamSpec *pspec);
+
+
+#endif /* __GIMP_PARAM_SPECS_DUPLICATE_H__ */
diff --git a/app/core/gimpparamspecs.c b/app/core/gimpparamspecs.c
new file mode 100644
index 0000000..17ba6a5
--- /dev/null
+++ b/app/core/gimpparamspecs.c
@@ -0,0 +1,2925 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpbase/gimpbase.h"
+
+#include "core-types.h"
+
+#include "gimp.h"
+#include "gimpimage.h"
+#include "gimplayer.h"
+#include "gimplayermask.h"
+#include "gimpparamspecs.h"
+#include "gimpselection.h"
+
+#include "vectors/gimpvectors.h"
+
+
+/*
+ * GIMP_TYPE_INT32
+ */
+
+GType
+gimp_int32_get_type (void)
+{
+ static GType type = 0;
+
+ if (! type)
+ {
+ const GTypeInfo info = { 0, };
+
+ type = g_type_register_static (G_TYPE_INT, "GimpInt32", &info, 0);
+ }
+
+ return type;
+}
+
+
+/*
+ * GIMP_TYPE_PARAM_INT32
+ */
+
+static void gimp_param_int32_class_init (GParamSpecClass *klass);
+static void gimp_param_int32_init (GParamSpec *pspec);
+
+GType
+gimp_param_int32_get_type (void)
+{
+ static GType type = 0;
+
+ if (! type)
+ {
+ const GTypeInfo info =
+ {
+ sizeof (GParamSpecClass),
+ NULL, NULL,
+ (GClassInitFunc) gimp_param_int32_class_init,
+ NULL, NULL,
+ sizeof (GimpParamSpecInt32),
+ 0,
+ (GInstanceInitFunc) gimp_param_int32_init
+ };
+
+ type = g_type_register_static (G_TYPE_PARAM_INT,
+ "GimpParamInt32", &info, 0);
+ }
+
+ return type;
+}
+
+static void
+gimp_param_int32_class_init (GParamSpecClass *klass)
+{
+ klass->value_type = GIMP_TYPE_INT32;
+}
+
+static void
+gimp_param_int32_init (GParamSpec *pspec)
+{
+}
+
+GParamSpec *
+gimp_param_spec_int32 (const gchar *name,
+ const gchar *nick,
+ const gchar *blurb,
+ gint minimum,
+ gint maximum,
+ gint default_value,
+ GParamFlags flags)
+{
+ GParamSpecInt *ispec;
+
+ g_return_val_if_fail (minimum >= G_MININT32, NULL);
+ g_return_val_if_fail (maximum <= G_MAXINT32, NULL);
+ g_return_val_if_fail (default_value >= minimum &&
+ default_value <= maximum, NULL);
+
+ ispec = g_param_spec_internal (GIMP_TYPE_PARAM_INT32,
+ name, nick, blurb, flags);
+
+ ispec->minimum = minimum;
+ ispec->maximum = maximum;
+ ispec->default_value = default_value;
+
+ return G_PARAM_SPEC (ispec);
+}
+
+
+/*
+ * GIMP_TYPE_INT16
+ */
+
+GType
+gimp_int16_get_type (void)
+{
+ static GType type = 0;
+
+ if (! type)
+ {
+ const GTypeInfo info = { 0, };
+
+ type = g_type_register_static (G_TYPE_INT, "GimpInt16", &info, 0);
+ }
+
+ return type;
+}
+
+
+/*
+ * GIMP_TYPE_PARAM_INT16
+ */
+
+static void gimp_param_int16_class_init (GParamSpecClass *klass);
+static void gimp_param_int16_init (GParamSpec *pspec);
+
+GType
+gimp_param_int16_get_type (void)
+{
+ static GType type = 0;
+
+ if (! type)
+ {
+ const GTypeInfo info =
+ {
+ sizeof (GParamSpecClass),
+ NULL, NULL,
+ (GClassInitFunc) gimp_param_int16_class_init,
+ NULL, NULL,
+ sizeof (GimpParamSpecInt16),
+ 0,
+ (GInstanceInitFunc) gimp_param_int16_init
+ };
+
+ type = g_type_register_static (G_TYPE_PARAM_INT,
+ "GimpParamInt16", &info, 0);
+ }
+
+ return type;
+}
+
+static void
+gimp_param_int16_class_init (GParamSpecClass *klass)
+{
+ klass->value_type = GIMP_TYPE_INT16;
+}
+
+static void
+gimp_param_int16_init (GParamSpec *pspec)
+{
+}
+
+GParamSpec *
+gimp_param_spec_int16 (const gchar *name,
+ const gchar *nick,
+ const gchar *blurb,
+ gint minimum,
+ gint maximum,
+ gint default_value,
+ GParamFlags flags)
+{
+ GParamSpecInt *ispec;
+
+ g_return_val_if_fail (minimum >= G_MININT16, NULL);
+ g_return_val_if_fail (maximum <= G_MAXINT16, NULL);
+ g_return_val_if_fail (default_value >= minimum &&
+ default_value <= maximum, NULL);
+
+ ispec = g_param_spec_internal (GIMP_TYPE_PARAM_INT16,
+ name, nick, blurb, flags);
+
+ ispec->minimum = minimum;
+ ispec->maximum = maximum;
+ ispec->default_value = default_value;
+
+ return G_PARAM_SPEC (ispec);
+}
+
+
+/*
+ * GIMP_TYPE_INT8
+ */
+
+GType
+gimp_int8_get_type (void)
+{
+ static GType type = 0;
+
+ if (! type)
+ {
+ const GTypeInfo info = { 0, };
+
+ type = g_type_register_static (G_TYPE_UINT, "GimpInt8", &info, 0);
+ }
+
+ return type;
+}
+
+
+/*
+ * GIMP_TYPE_PARAM_INT8
+ */
+
+static void gimp_param_int8_class_init (GParamSpecClass *klass);
+static void gimp_param_int8_init (GParamSpec *pspec);
+
+GType
+gimp_param_int8_get_type (void)
+{
+ static GType type = 0;
+
+ if (! type)
+ {
+ const GTypeInfo info =
+ {
+ sizeof (GParamSpecClass),
+ NULL, NULL,
+ (GClassInitFunc) gimp_param_int8_class_init,
+ NULL, NULL,
+ sizeof (GimpParamSpecInt8),
+ 0,
+ (GInstanceInitFunc) gimp_param_int8_init
+ };
+
+ type = g_type_register_static (G_TYPE_PARAM_UINT,
+ "GimpParamInt8", &info, 0);
+ }
+
+ return type;
+}
+
+static void
+gimp_param_int8_class_init (GParamSpecClass *klass)
+{
+ klass->value_type = GIMP_TYPE_INT8;
+}
+
+static void
+gimp_param_int8_init (GParamSpec *pspec)
+{
+}
+
+GParamSpec *
+gimp_param_spec_int8 (const gchar *name,
+ const gchar *nick,
+ const gchar *blurb,
+ guint minimum,
+ guint maximum,
+ guint default_value,
+ GParamFlags flags)
+{
+ GParamSpecInt *ispec;
+
+ g_return_val_if_fail (maximum <= G_MAXUINT8, NULL);
+ g_return_val_if_fail (default_value >= minimum &&
+ default_value <= maximum, NULL);
+
+ ispec = g_param_spec_internal (GIMP_TYPE_PARAM_INT8,
+ name, nick, blurb, flags);
+
+ ispec->minimum = minimum;
+ ispec->maximum = maximum;
+ ispec->default_value = default_value;
+
+ return G_PARAM_SPEC (ispec);
+}
+
+
+/*
+ * GIMP_TYPE_PARAM_STRING
+ */
+
+static void gimp_param_string_class_init (GParamSpecClass *klass);
+static void gimp_param_string_init (GParamSpec *pspec);
+static gboolean gimp_param_string_validate (GParamSpec *pspec,
+ GValue *value);
+
+static GParamSpecClass * gimp_param_string_parent_class = NULL;
+
+GType
+gimp_param_string_get_type (void)
+{
+ static GType type = 0;
+
+ if (! type)
+ {
+ const GTypeInfo info =
+ {
+ sizeof (GParamSpecClass),
+ NULL, NULL,
+ (GClassInitFunc) gimp_param_string_class_init,
+ NULL, NULL,
+ sizeof (GimpParamSpecString),
+ 0,
+ (GInstanceInitFunc) gimp_param_string_init
+ };
+
+ type = g_type_register_static (G_TYPE_PARAM_STRING,
+ "GimpParamString", &info, 0);
+ }
+
+ return type;
+}
+
+static void
+gimp_param_string_class_init (GParamSpecClass *klass)
+{
+ gimp_param_string_parent_class = g_type_class_peek_parent (klass);
+
+ klass->value_type = G_TYPE_STRING;
+ klass->value_validate = gimp_param_string_validate;
+}
+
+static void
+gimp_param_string_init (GParamSpec *pspec)
+{
+ GimpParamSpecString *sspec = GIMP_PARAM_SPEC_STRING (pspec);
+
+ G_PARAM_SPEC_STRING (pspec)->ensure_non_null = TRUE;
+
+ sspec->allow_non_utf8 = FALSE;
+ sspec->non_empty = FALSE;
+}
+
+static gboolean
+gimp_param_string_validate (GParamSpec *pspec,
+ GValue *value)
+{
+ GimpParamSpecString *sspec = GIMP_PARAM_SPEC_STRING (pspec);
+ gchar *string = value->data[0].v_pointer;
+
+ if (gimp_param_string_parent_class->value_validate (pspec, value))
+ return TRUE;
+
+ if (string)
+ {
+ gchar *s;
+
+ if (sspec->non_empty && ! string[0])
+ {
+ if (!(value->data[1].v_uint & G_VALUE_NOCOPY_CONTENTS))
+ g_free (string);
+ else
+ value->data[1].v_uint &= ~G_VALUE_NOCOPY_CONTENTS;
+
+ value->data[0].v_pointer = g_strdup ("none");
+ return TRUE;
+ }
+
+ if (! sspec->allow_non_utf8 &&
+ ! g_utf8_validate (string, -1, (const gchar **) &s))
+ {
+ if (value->data[1].v_uint & G_VALUE_NOCOPY_CONTENTS)
+ {
+ value->data[0].v_pointer = g_strdup (string);
+ value->data[1].v_uint &= ~G_VALUE_NOCOPY_CONTENTS;
+ string = value->data[0].v_pointer;
+ }
+
+ for (s = string; *s; s++)
+ if (*s < ' ')
+ *s = '?';
+
+ return TRUE;
+ }
+ }
+ else if (sspec->non_empty)
+ {
+ value->data[1].v_uint &= ~G_VALUE_NOCOPY_CONTENTS;
+ value->data[0].v_pointer = g_strdup ("none");
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+GParamSpec *
+gimp_param_spec_string (const gchar *name,
+ const gchar *nick,
+ const gchar *blurb,
+ gboolean allow_non_utf8,
+ gboolean null_ok,
+ gboolean non_empty,
+ const gchar *default_value,
+ GParamFlags flags)
+{
+ GimpParamSpecString *sspec;
+
+ g_return_val_if_fail (! (null_ok && non_empty), NULL);
+
+ sspec = g_param_spec_internal (GIMP_TYPE_PARAM_STRING,
+ name, nick, blurb, flags);
+
+ if (sspec)
+ {
+ g_free (G_PARAM_SPEC_STRING (sspec)->default_value);
+ G_PARAM_SPEC_STRING (sspec)->default_value = g_strdup (default_value);
+
+ G_PARAM_SPEC_STRING (sspec)->ensure_non_null = null_ok ? FALSE : TRUE;
+
+ sspec->allow_non_utf8 = allow_non_utf8 ? TRUE : FALSE;
+ sspec->non_empty = non_empty ? TRUE : FALSE;
+ }
+
+ return G_PARAM_SPEC (sspec);
+}
+
+
+/*
+ * GIMP_TYPE_PARAM_ENUM
+ */
+
+static void gimp_param_enum_class_init (GParamSpecClass *klass);
+static void gimp_param_enum_init (GParamSpec *pspec);
+static void gimp_param_enum_finalize (GParamSpec *pspec);
+static gboolean gimp_param_enum_validate (GParamSpec *pspec,
+ GValue *value);
+
+GType
+gimp_param_enum_get_type (void)
+{
+ static GType type = 0;
+
+ if (! type)
+ {
+ const GTypeInfo info =
+ {
+ sizeof (GParamSpecClass),
+ NULL, NULL,
+ (GClassInitFunc) gimp_param_enum_class_init,
+ NULL, NULL,
+ sizeof (GimpParamSpecEnum),
+ 0,
+ (GInstanceInitFunc) gimp_param_enum_init
+ };
+
+ type = g_type_register_static (G_TYPE_PARAM_ENUM,
+ "GimpParamEnum", &info, 0);
+ }
+
+ return type;
+}
+
+static void
+gimp_param_enum_class_init (GParamSpecClass *klass)
+{
+ klass->value_type = G_TYPE_ENUM;
+ klass->finalize = gimp_param_enum_finalize;
+ klass->value_validate = gimp_param_enum_validate;
+}
+
+static void
+gimp_param_enum_init (GParamSpec *pspec)
+{
+ GimpParamSpecEnum *espec = GIMP_PARAM_SPEC_ENUM (pspec);
+
+ espec->excluded_values = NULL;
+}
+
+static void
+gimp_param_enum_finalize (GParamSpec *pspec)
+{
+ GimpParamSpecEnum *espec = GIMP_PARAM_SPEC_ENUM (pspec);
+ GParamSpecClass *parent_class;
+
+ parent_class = g_type_class_peek (g_type_parent (GIMP_TYPE_PARAM_ENUM));
+
+ g_slist_free (espec->excluded_values);
+
+ parent_class->finalize (pspec);
+}
+
+static gboolean
+gimp_param_enum_validate (GParamSpec *pspec,
+ GValue *value)
+{
+ GimpParamSpecEnum *espec = GIMP_PARAM_SPEC_ENUM (pspec);
+ GParamSpecClass *parent_class;
+ GSList *list;
+
+ parent_class = g_type_class_peek (g_type_parent (GIMP_TYPE_PARAM_ENUM));
+
+ if (parent_class->value_validate (pspec, value))
+ return TRUE;
+
+ for (list = espec->excluded_values; list; list = g_slist_next (list))
+ {
+ if (GPOINTER_TO_INT (list->data) == value->data[0].v_long)
+ {
+ value->data[0].v_long = G_PARAM_SPEC_ENUM (pspec)->default_value;
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+GParamSpec *
+gimp_param_spec_enum (const gchar *name,
+ const gchar *nick,
+ const gchar *blurb,
+ GType enum_type,
+ gint default_value,
+ GParamFlags flags)
+{
+ GimpParamSpecEnum *espec;
+ GEnumClass *enum_class;
+
+ g_return_val_if_fail (G_TYPE_IS_ENUM (enum_type), NULL);
+
+ enum_class = g_type_class_ref (enum_type);
+
+ g_return_val_if_fail (g_enum_get_value (enum_class, default_value) != NULL,
+ NULL);
+
+ espec = g_param_spec_internal (GIMP_TYPE_PARAM_ENUM,
+ name, nick, blurb, flags);
+
+ G_PARAM_SPEC_ENUM (espec)->enum_class = enum_class;
+ G_PARAM_SPEC_ENUM (espec)->default_value = default_value;
+ G_PARAM_SPEC (espec)->value_type = enum_type;
+
+ return G_PARAM_SPEC (espec);
+}
+
+void
+gimp_param_spec_enum_exclude_value (GimpParamSpecEnum *espec,
+ gint value)
+{
+ g_return_if_fail (GIMP_IS_PARAM_SPEC_ENUM (espec));
+ g_return_if_fail (g_enum_get_value (G_PARAM_SPEC_ENUM (espec)->enum_class,
+ value) != NULL);
+
+ espec->excluded_values = g_slist_prepend (espec->excluded_values,
+ GINT_TO_POINTER (value));
+}
+
+
+/*
+ * GIMP_TYPE_IMAGE_ID
+ */
+
+GType
+gimp_image_id_get_type (void)
+{
+ static GType type = 0;
+
+ if (! type)
+ {
+ const GTypeInfo info = { 0, };
+
+ type = g_type_register_static (G_TYPE_INT, "GimpImageID", &info, 0);
+ }
+
+ return type;
+}
+
+
+/*
+ * GIMP_TYPE_PARAM_IMAGE_ID
+ */
+
+static void gimp_param_image_id_class_init (GParamSpecClass *klass);
+static void gimp_param_image_id_init (GParamSpec *pspec);
+static void gimp_param_image_id_set_default (GParamSpec *pspec,
+ GValue *value);
+static gboolean gimp_param_image_id_validate (GParamSpec *pspec,
+ GValue *value);
+static gint gimp_param_image_id_values_cmp (GParamSpec *pspec,
+ const GValue *value1,
+ const GValue *value2);
+
+GType
+gimp_param_image_id_get_type (void)
+{
+ static GType type = 0;
+
+ if (! type)
+ {
+ const GTypeInfo info =
+ {
+ sizeof (GParamSpecClass),
+ NULL, NULL,
+ (GClassInitFunc) gimp_param_image_id_class_init,
+ NULL, NULL,
+ sizeof (GimpParamSpecImageID),
+ 0,
+ (GInstanceInitFunc) gimp_param_image_id_init
+ };
+
+ type = g_type_register_static (G_TYPE_PARAM_INT,
+ "GimpParamImageID", &info, 0);
+ }
+
+ return type;
+}
+
+static void
+gimp_param_image_id_class_init (GParamSpecClass *klass)
+{
+ klass->value_type = GIMP_TYPE_IMAGE_ID;
+ klass->value_set_default = gimp_param_image_id_set_default;
+ klass->value_validate = gimp_param_image_id_validate;
+ klass->values_cmp = gimp_param_image_id_values_cmp;
+}
+
+static void
+gimp_param_image_id_init (GParamSpec *pspec)
+{
+ GimpParamSpecImageID *ispec = GIMP_PARAM_SPEC_IMAGE_ID (pspec);
+
+ ispec->gimp = NULL;
+ ispec->none_ok = FALSE;
+}
+
+static void
+gimp_param_image_id_set_default (GParamSpec *pspec,
+ GValue *value)
+{
+ value->data[0].v_int = -1;
+}
+
+static gboolean
+gimp_param_image_id_validate (GParamSpec *pspec,
+ GValue *value)
+{
+ GimpParamSpecImageID *ispec = GIMP_PARAM_SPEC_IMAGE_ID (pspec);
+ gint image_id = value->data[0].v_int;
+ GimpImage *image;
+
+ if (ispec->none_ok && (image_id == 0 || image_id == -1))
+ return FALSE;
+
+ image = gimp_image_get_by_ID (ispec->gimp, image_id);
+
+ if (! GIMP_IS_IMAGE (image))
+ {
+ value->data[0].v_int = -1;
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gint
+gimp_param_image_id_values_cmp (GParamSpec *pspec,
+ const GValue *value1,
+ const GValue *value2)
+{
+ gint image_id1 = value1->data[0].v_int;
+ gint image_id2 = value2->data[0].v_int;
+
+ /* try to return at least *something*, it's useless anyway... */
+
+ if (image_id1 < image_id2)
+ return -1;
+ else if (image_id1 > image_id2)
+ return 1;
+ else
+ return 0;
+}
+
+GParamSpec *
+gimp_param_spec_image_id (const gchar *name,
+ const gchar *nick,
+ const gchar *blurb,
+ Gimp *gimp,
+ gboolean none_ok,
+ GParamFlags flags)
+{
+ GimpParamSpecImageID *ispec;
+
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
+
+ ispec = g_param_spec_internal (GIMP_TYPE_PARAM_IMAGE_ID,
+ name, nick, blurb, flags);
+
+ ispec->gimp = gimp;
+ ispec->none_ok = none_ok ? TRUE : FALSE;
+
+ return G_PARAM_SPEC (ispec);
+}
+
+GimpImage *
+gimp_value_get_image (const GValue *value,
+ Gimp *gimp)
+{
+ g_return_val_if_fail (GIMP_VALUE_HOLDS_IMAGE_ID (value), NULL);
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
+
+ return gimp_image_get_by_ID (gimp, value->data[0].v_int);
+}
+
+void
+gimp_value_set_image (GValue *value,
+ GimpImage *image)
+{
+ g_return_if_fail (GIMP_VALUE_HOLDS_IMAGE_ID (value));
+ g_return_if_fail (image == NULL || GIMP_IS_IMAGE (image));
+
+ value->data[0].v_int = image ? gimp_image_get_ID (image) : -1;
+}
+
+
+/*
+ * GIMP_TYPE_ITEM_ID
+ */
+
+GType
+gimp_item_id_get_type (void)
+{
+ static GType type = 0;
+
+ if (! type)
+ {
+ const GTypeInfo info = { 0, };
+
+ type = g_type_register_static (G_TYPE_INT, "GimpItemID", &info, 0);
+ }
+
+ return type;
+}
+
+
+/*
+ * GIMP_TYPE_PARAM_ITEM_ID
+ */
+
+static void gimp_param_item_id_class_init (GParamSpecClass *klass);
+static void gimp_param_item_id_init (GParamSpec *pspec);
+static void gimp_param_item_id_set_default (GParamSpec *pspec,
+ GValue *value);
+static gboolean gimp_param_item_id_validate (GParamSpec *pspec,
+ GValue *value);
+static gint gimp_param_item_id_values_cmp (GParamSpec *pspec,
+ const GValue *value1,
+ const GValue *value2);
+
+GType
+gimp_param_item_id_get_type (void)
+{
+ static GType type = 0;
+
+ if (! type)
+ {
+ const GTypeInfo info =
+ {
+ sizeof (GParamSpecClass),
+ NULL, NULL,
+ (GClassInitFunc) gimp_param_item_id_class_init,
+ NULL, NULL,
+ sizeof (GimpParamSpecItemID),
+ 0,
+ (GInstanceInitFunc) gimp_param_item_id_init
+ };
+
+ type = g_type_register_static (G_TYPE_PARAM_INT,
+ "GimpParamItemID", &info, 0);
+ }
+
+ return type;
+}
+
+static void
+gimp_param_item_id_class_init (GParamSpecClass *klass)
+{
+ klass->value_type = GIMP_TYPE_ITEM_ID;
+ klass->value_set_default = gimp_param_item_id_set_default;
+ klass->value_validate = gimp_param_item_id_validate;
+ klass->values_cmp = gimp_param_item_id_values_cmp;
+}
+
+static void
+gimp_param_item_id_init (GParamSpec *pspec)
+{
+ GimpParamSpecItemID *ispec = GIMP_PARAM_SPEC_ITEM_ID (pspec);
+
+ ispec->gimp = NULL;
+ ispec->item_type = GIMP_TYPE_ITEM;
+ ispec->none_ok = FALSE;
+}
+
+static void
+gimp_param_item_id_set_default (GParamSpec *pspec,
+ GValue *value)
+{
+ value->data[0].v_int = -1;
+}
+
+static gboolean
+gimp_param_item_id_validate (GParamSpec *pspec,
+ GValue *value)
+{
+ GimpParamSpecItemID *ispec = GIMP_PARAM_SPEC_ITEM_ID (pspec);
+ gint item_id = value->data[0].v_int;
+ GimpItem *item;
+
+ if (ispec->none_ok && (item_id == 0 || item_id == -1))
+ return FALSE;
+
+ item = gimp_item_get_by_ID (ispec->gimp, item_id);
+
+ if (! item || ! g_type_is_a (G_TYPE_FROM_INSTANCE (item), ispec->item_type))
+ {
+ value->data[0].v_int = -1;
+ return TRUE;
+ }
+ else if (gimp_item_is_removed (item))
+ {
+ value->data[0].v_int = -1;
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gint
+gimp_param_item_id_values_cmp (GParamSpec *pspec,
+ const GValue *value1,
+ const GValue *value2)
+{
+ gint item_id1 = value1->data[0].v_int;
+ gint item_id2 = value2->data[0].v_int;
+
+ /* try to return at least *something*, it's useless anyway... */
+
+ if (item_id1 < item_id2)
+ return -1;
+ else if (item_id1 > item_id2)
+ return 1;
+ else
+ return 0;
+}
+
+GParamSpec *
+gimp_param_spec_item_id (const gchar *name,
+ const gchar *nick,
+ const gchar *blurb,
+ Gimp *gimp,
+ gboolean none_ok,
+ GParamFlags flags)
+{
+ GimpParamSpecItemID *ispec;
+
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
+
+ ispec = g_param_spec_internal (GIMP_TYPE_PARAM_ITEM_ID,
+ name, nick, blurb, flags);
+
+ ispec->gimp = gimp;
+ ispec->none_ok = none_ok;
+
+ return G_PARAM_SPEC (ispec);
+}
+
+GimpItem *
+gimp_value_get_item (const GValue *value,
+ Gimp *gimp)
+{
+ GimpItem *item;
+
+ g_return_val_if_fail (GIMP_VALUE_HOLDS_ITEM_ID (value), NULL);
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
+
+ item = gimp_item_get_by_ID (gimp, value->data[0].v_int);
+
+ if (item && ! GIMP_IS_ITEM (item))
+ return NULL;
+
+ return item;
+}
+
+void
+gimp_value_set_item (GValue *value,
+ GimpItem *item)
+{
+ g_return_if_fail (item == NULL || GIMP_IS_ITEM (item));
+
+ /* FIXME remove hack as soon as bug #375864 is fixed */
+
+ if (GIMP_VALUE_HOLDS_ITEM_ID (value))
+ {
+ value->data[0].v_int = item ? gimp_item_get_ID (item) : -1;
+ }
+ else if (GIMP_VALUE_HOLDS_DRAWABLE_ID (value) &&
+ (item == NULL || GIMP_IS_DRAWABLE (item)))
+ {
+ gimp_value_set_drawable (value, GIMP_DRAWABLE (item));
+ }
+ else if (GIMP_VALUE_HOLDS_LAYER_ID (value) &&
+ (item == NULL || GIMP_IS_LAYER (item)))
+ {
+ gimp_value_set_layer (value, GIMP_LAYER (item));
+ }
+ else if (GIMP_VALUE_HOLDS_CHANNEL_ID (value) &&
+ (item == NULL || GIMP_IS_CHANNEL (item)))
+ {
+ gimp_value_set_channel (value, GIMP_CHANNEL (item));
+ }
+ else if (GIMP_VALUE_HOLDS_LAYER_MASK_ID (value) &&
+ (item == NULL || GIMP_IS_LAYER_MASK (item)))
+ {
+ gimp_value_set_layer_mask (value, GIMP_LAYER_MASK (item));
+ }
+ else if (GIMP_VALUE_HOLDS_SELECTION_ID (value) &&
+ (item == NULL || GIMP_IS_SELECTION (item)))
+ {
+ gimp_value_set_selection (value, GIMP_SELECTION (item));
+ }
+ else if (GIMP_VALUE_HOLDS_VECTORS_ID (value) &&
+ (item == NULL || GIMP_IS_VECTORS (item)))
+ {
+ gimp_value_set_vectors (value, GIMP_VECTORS (item));
+ }
+ else
+ {
+ g_return_if_reached ();
+ }
+}
+
+
+/*
+ * GIMP_TYPE_DRAWABLE_ID
+ */
+
+GType
+gimp_drawable_id_get_type (void)
+{
+ static GType type = 0;
+
+ if (! type)
+ {
+ const GTypeInfo info = { 0, };
+
+ type = g_type_register_static (G_TYPE_INT, "GimpDrawableID", &info, 0);
+ }
+
+ return type;
+}
+
+
+/*
+ * GIMP_TYPE_PARAM_DRAWABLE_ID
+ */
+
+static void gimp_param_drawable_id_class_init (GParamSpecClass *klass);
+static void gimp_param_drawable_id_init (GParamSpec *pspec);
+
+GType
+gimp_param_drawable_id_get_type (void)
+{
+ static GType type = 0;
+
+ if (! type)
+ {
+ const GTypeInfo info =
+ {
+ sizeof (GParamSpecClass),
+ NULL, NULL,
+ (GClassInitFunc) gimp_param_drawable_id_class_init,
+ NULL, NULL,
+ sizeof (GimpParamSpecDrawableID),
+ 0,
+ (GInstanceInitFunc) gimp_param_drawable_id_init
+ };
+
+ type = g_type_register_static (GIMP_TYPE_PARAM_ITEM_ID,
+ "GimpParamDrawableID", &info, 0);
+ }
+
+ return type;
+}
+
+static void
+gimp_param_drawable_id_class_init (GParamSpecClass *klass)
+{
+ klass->value_type = GIMP_TYPE_DRAWABLE_ID;
+}
+
+static void
+gimp_param_drawable_id_init (GParamSpec *pspec)
+{
+ GimpParamSpecItemID *ispec = GIMP_PARAM_SPEC_ITEM_ID (pspec);
+
+ ispec->item_type = GIMP_TYPE_DRAWABLE;
+}
+
+GParamSpec *
+gimp_param_spec_drawable_id (const gchar *name,
+ const gchar *nick,
+ const gchar *blurb,
+ Gimp *gimp,
+ gboolean none_ok,
+ GParamFlags flags)
+{
+ GimpParamSpecItemID *ispec;
+
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
+
+ ispec = g_param_spec_internal (GIMP_TYPE_PARAM_DRAWABLE_ID,
+ name, nick, blurb, flags);
+
+ ispec->gimp = gimp;
+ ispec->none_ok = none_ok ? TRUE : FALSE;
+
+ return G_PARAM_SPEC (ispec);
+}
+
+GimpDrawable *
+gimp_value_get_drawable (const GValue *value,
+ Gimp *gimp)
+{
+ GimpItem *item;
+
+ g_return_val_if_fail (GIMP_VALUE_HOLDS_DRAWABLE_ID (value), NULL);
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
+
+ item = gimp_item_get_by_ID (gimp, value->data[0].v_int);
+
+ if (item && ! GIMP_IS_DRAWABLE (item))
+ return NULL;
+
+ return GIMP_DRAWABLE (item);
+}
+
+void
+gimp_value_set_drawable (GValue *value,
+ GimpDrawable *drawable)
+{
+ g_return_if_fail (GIMP_VALUE_HOLDS_DRAWABLE_ID (value));
+ g_return_if_fail (drawable == NULL || GIMP_IS_DRAWABLE (drawable));
+
+ value->data[0].v_int = drawable ? gimp_item_get_ID (GIMP_ITEM (drawable)) : -1;
+}
+
+
+/*
+ * GIMP_TYPE_LAYER_ID
+ */
+
+GType
+gimp_layer_id_get_type (void)
+{
+ static GType type = 0;
+
+ if (! type)
+ {
+ const GTypeInfo info = { 0, };
+
+ type = g_type_register_static (G_TYPE_INT, "GimpLayerID", &info, 0);
+ }
+
+ return type;
+}
+
+
+/*
+ * GIMP_TYPE_PARAM_LAYER_ID
+ */
+
+static void gimp_param_layer_id_class_init (GParamSpecClass *klass);
+static void gimp_param_layer_id_init (GParamSpec *pspec);
+
+GType
+gimp_param_layer_id_get_type (void)
+{
+ static GType type = 0;
+
+ if (! type)
+ {
+ const GTypeInfo info =
+ {
+ sizeof (GParamSpecClass),
+ NULL, NULL,
+ (GClassInitFunc) gimp_param_layer_id_class_init,
+ NULL, NULL,
+ sizeof (GimpParamSpecLayerID),
+ 0,
+ (GInstanceInitFunc) gimp_param_layer_id_init
+ };
+
+ type = g_type_register_static (GIMP_TYPE_PARAM_DRAWABLE_ID,
+ "GimpParamLayerID", &info, 0);
+ }
+
+ return type;
+}
+
+static void
+gimp_param_layer_id_class_init (GParamSpecClass *klass)
+{
+ klass->value_type = GIMP_TYPE_LAYER_ID;
+}
+
+static void
+gimp_param_layer_id_init (GParamSpec *pspec)
+{
+ GimpParamSpecItemID *ispec = GIMP_PARAM_SPEC_ITEM_ID (pspec);
+
+ ispec->item_type = GIMP_TYPE_LAYER;
+}
+
+GParamSpec *
+gimp_param_spec_layer_id (const gchar *name,
+ const gchar *nick,
+ const gchar *blurb,
+ Gimp *gimp,
+ gboolean none_ok,
+ GParamFlags flags)
+{
+ GimpParamSpecItemID *ispec;
+
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
+
+ ispec = g_param_spec_internal (GIMP_TYPE_PARAM_LAYER_ID,
+ name, nick, blurb, flags);
+
+ ispec->gimp = gimp;
+ ispec->none_ok = none_ok ? TRUE : FALSE;
+
+ return G_PARAM_SPEC (ispec);
+}
+
+GimpLayer *
+gimp_value_get_layer (const GValue *value,
+ Gimp *gimp)
+{
+ GimpItem *item;
+
+ g_return_val_if_fail (GIMP_VALUE_HOLDS_LAYER_ID (value), NULL);
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
+
+ item = gimp_item_get_by_ID (gimp, value->data[0].v_int);
+
+ if (item && ! GIMP_IS_LAYER (item))
+ return NULL;
+
+ return GIMP_LAYER (item);
+}
+
+void
+gimp_value_set_layer (GValue *value,
+ GimpLayer *layer)
+{
+ g_return_if_fail (GIMP_VALUE_HOLDS_LAYER_ID (value));
+ g_return_if_fail (layer == NULL || GIMP_IS_LAYER (layer));
+
+ value->data[0].v_int = layer ? gimp_item_get_ID (GIMP_ITEM (layer)) : -1;
+}
+
+
+/*
+ * GIMP_TYPE_CHANNEL_ID
+ */
+
+GType
+gimp_channel_id_get_type (void)
+{
+ static GType type = 0;
+
+ if (! type)
+ {
+ const GTypeInfo info = { 0, };
+
+ type = g_type_register_static (G_TYPE_INT, "GimpChannelID", &info, 0);
+ }
+
+ return type;
+}
+
+
+/*
+ * GIMP_TYPE_PARAM_CHANNEL_ID
+ */
+
+static void gimp_param_channel_id_class_init (GParamSpecClass *klass);
+static void gimp_param_channel_id_init (GParamSpec *pspec);
+
+GType
+gimp_param_channel_id_get_type (void)
+{
+ static GType type = 0;
+
+ if (! type)
+ {
+ const GTypeInfo info =
+ {
+ sizeof (GParamSpecClass),
+ NULL, NULL,
+ (GClassInitFunc) gimp_param_channel_id_class_init,
+ NULL, NULL,
+ sizeof (GimpParamSpecChannelID),
+ 0,
+ (GInstanceInitFunc) gimp_param_channel_id_init
+ };
+
+ type = g_type_register_static (GIMP_TYPE_PARAM_DRAWABLE_ID,
+ "GimpParamChannelID", &info, 0);
+ }
+
+ return type;
+}
+
+static void
+gimp_param_channel_id_class_init (GParamSpecClass *klass)
+{
+ klass->value_type = GIMP_TYPE_CHANNEL_ID;
+}
+
+static void
+gimp_param_channel_id_init (GParamSpec *pspec)
+{
+ GimpParamSpecItemID *ispec = GIMP_PARAM_SPEC_ITEM_ID (pspec);
+
+ ispec->item_type = GIMP_TYPE_CHANNEL;
+}
+
+GParamSpec *
+gimp_param_spec_channel_id (const gchar *name,
+ const gchar *nick,
+ const gchar *blurb,
+ Gimp *gimp,
+ gboolean none_ok,
+ GParamFlags flags)
+{
+ GimpParamSpecItemID *ispec;
+
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
+
+ ispec = g_param_spec_internal (GIMP_TYPE_PARAM_CHANNEL_ID,
+ name, nick, blurb, flags);
+
+ ispec->gimp = gimp;
+ ispec->none_ok = none_ok ? TRUE : FALSE;
+
+ return G_PARAM_SPEC (ispec);
+}
+
+GimpChannel *
+gimp_value_get_channel (const GValue *value,
+ Gimp *gimp)
+{
+ GimpItem *item;
+
+ g_return_val_if_fail (GIMP_VALUE_HOLDS_CHANNEL_ID (value), NULL);
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
+
+ item = gimp_item_get_by_ID (gimp, value->data[0].v_int);
+
+ if (item && ! GIMP_IS_CHANNEL (item))
+ return NULL;
+
+ return GIMP_CHANNEL (item);
+}
+
+void
+gimp_value_set_channel (GValue *value,
+ GimpChannel *channel)
+{
+ g_return_if_fail (GIMP_VALUE_HOLDS_CHANNEL_ID (value));
+ g_return_if_fail (channel == NULL || GIMP_IS_CHANNEL (channel));
+
+ value->data[0].v_int = channel ? gimp_item_get_ID (GIMP_ITEM (channel)) : -1;
+}
+
+
+/*
+ * GIMP_TYPE_LAYER_MASK_ID
+ */
+
+GType
+gimp_layer_mask_id_get_type (void)
+{
+ static GType type = 0;
+
+ if (! type)
+ {
+ const GTypeInfo info = { 0, };
+
+ type = g_type_register_static (G_TYPE_INT, "GimpLayerMaskID", &info, 0);
+ }
+
+ return type;
+}
+
+
+/*
+ * GIMP_TYPE_PARAM_LAYER_MASK_ID
+ */
+
+static void gimp_param_layer_mask_id_class_init (GParamSpecClass *klass);
+static void gimp_param_layer_mask_id_init (GParamSpec *pspec);
+
+GType
+gimp_param_layer_mask_id_get_type (void)
+{
+ static GType type = 0;
+
+ if (! type)
+ {
+ const GTypeInfo info =
+ {
+ sizeof (GParamSpecClass),
+ NULL, NULL,
+ (GClassInitFunc) gimp_param_layer_mask_id_class_init,
+ NULL, NULL,
+ sizeof (GimpParamSpecLayerMaskID),
+ 0,
+ (GInstanceInitFunc) gimp_param_layer_mask_id_init
+ };
+
+ type = g_type_register_static (GIMP_TYPE_PARAM_CHANNEL_ID,
+ "GimpParamLayerMaskID", &info, 0);
+ }
+
+ return type;
+}
+
+static void
+gimp_param_layer_mask_id_class_init (GParamSpecClass *klass)
+{
+ klass->value_type = GIMP_TYPE_LAYER_MASK_ID;
+}
+
+static void
+gimp_param_layer_mask_id_init (GParamSpec *pspec)
+{
+ GimpParamSpecItemID *ispec = GIMP_PARAM_SPEC_ITEM_ID (pspec);
+
+ ispec->item_type = GIMP_TYPE_LAYER_MASK;
+}
+
+GParamSpec *
+gimp_param_spec_layer_mask_id (const gchar *name,
+ const gchar *nick,
+ const gchar *blurb,
+ Gimp *gimp,
+ gboolean none_ok,
+ GParamFlags flags)
+{
+ GimpParamSpecItemID *ispec;
+
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
+
+ ispec = g_param_spec_internal (GIMP_TYPE_PARAM_LAYER_MASK_ID,
+ name, nick, blurb, flags);
+
+ ispec->gimp = gimp;
+ ispec->none_ok = none_ok ? TRUE : FALSE;
+
+ return G_PARAM_SPEC (ispec);
+}
+
+GimpLayerMask *
+gimp_value_get_layer_mask (const GValue *value,
+ Gimp *gimp)
+{
+ GimpItem *item;
+
+ g_return_val_if_fail (GIMP_VALUE_HOLDS_LAYER_MASK_ID (value), NULL);
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
+
+ item = gimp_item_get_by_ID (gimp, value->data[0].v_int);
+
+ if (item && ! GIMP_IS_LAYER_MASK (item))
+ return NULL;
+
+ return GIMP_LAYER_MASK (item);
+}
+
+void
+gimp_value_set_layer_mask (GValue *value,
+ GimpLayerMask *layer_mask)
+{
+ g_return_if_fail (GIMP_VALUE_HOLDS_LAYER_MASK_ID (value));
+ g_return_if_fail (layer_mask == NULL || GIMP_IS_LAYER_MASK (layer_mask));
+
+ value->data[0].v_int = layer_mask ? gimp_item_get_ID (GIMP_ITEM (layer_mask)) : -1;
+}
+
+
+/*
+ * GIMP_TYPE_SELECTION_ID
+ */
+
+GType
+gimp_selection_id_get_type (void)
+{
+ static GType type = 0;
+
+ if (! type)
+ {
+ const GTypeInfo info = { 0, };
+
+ type = g_type_register_static (G_TYPE_INT, "GimpSelectionID", &info, 0);
+ }
+
+ return type;
+}
+
+
+/*
+ * GIMP_TYPE_PARAM_SELECTION_ID
+ */
+
+static void gimp_param_selection_id_class_init (GParamSpecClass *klass);
+static void gimp_param_selection_id_init (GParamSpec *pspec);
+
+GType
+gimp_param_selection_id_get_type (void)
+{
+ static GType type = 0;
+
+ if (! type)
+ {
+ const GTypeInfo info =
+ {
+ sizeof (GParamSpecClass),
+ NULL, NULL,
+ (GClassInitFunc) gimp_param_selection_id_class_init,
+ NULL, NULL,
+ sizeof (GimpParamSpecSelectionID),
+ 0,
+ (GInstanceInitFunc) gimp_param_selection_id_init
+ };
+
+ type = g_type_register_static (GIMP_TYPE_PARAM_CHANNEL_ID,
+ "GimpParamSelectionID", &info, 0);
+ }
+
+ return type;
+}
+
+static void
+gimp_param_selection_id_class_init (GParamSpecClass *klass)
+{
+ klass->value_type = GIMP_TYPE_SELECTION_ID;
+}
+
+static void
+gimp_param_selection_id_init (GParamSpec *pspec)
+{
+ GimpParamSpecItemID *ispec = GIMP_PARAM_SPEC_ITEM_ID (pspec);
+
+ ispec->item_type = GIMP_TYPE_SELECTION;
+}
+
+GParamSpec *
+gimp_param_spec_selection_id (const gchar *name,
+ const gchar *nick,
+ const gchar *blurb,
+ Gimp *gimp,
+ gboolean none_ok,
+ GParamFlags flags)
+{
+ GimpParamSpecItemID *ispec;
+
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
+
+ ispec = g_param_spec_internal (GIMP_TYPE_PARAM_SELECTION_ID,
+ name, nick, blurb, flags);
+
+ ispec->gimp = gimp;
+ ispec->none_ok = none_ok ? TRUE : FALSE;
+
+ return G_PARAM_SPEC (ispec);
+}
+
+GimpSelection *
+gimp_value_get_selection (const GValue *value,
+ Gimp *gimp)
+{
+ GimpItem *item;
+
+ g_return_val_if_fail (GIMP_VALUE_HOLDS_SELECTION_ID (value), NULL);
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
+
+ item = gimp_item_get_by_ID (gimp, value->data[0].v_int);
+
+ if (item && ! GIMP_IS_SELECTION (item))
+ return NULL;
+
+ return GIMP_SELECTION (item);
+}
+
+void
+gimp_value_set_selection (GValue *value,
+ GimpSelection *selection)
+{
+ g_return_if_fail (GIMP_VALUE_HOLDS_SELECTION_ID (value));
+ g_return_if_fail (selection == NULL || GIMP_IS_SELECTION (selection));
+
+ value->data[0].v_int = selection ? gimp_item_get_ID (GIMP_ITEM (selection)) : -1;
+}
+
+
+/*
+ * GIMP_TYPE_VECTORS_ID
+ */
+
+GType
+gimp_vectors_id_get_type (void)
+{
+ static GType type = 0;
+
+ if (! type)
+ {
+ const GTypeInfo info = { 0, };
+
+ type = g_type_register_static (G_TYPE_INT, "GimpVectorsID", &info, 0);
+ }
+
+ return type;
+}
+
+
+/*
+ * GIMP_TYPE_PARAM_VECTORS_ID
+ */
+
+static void gimp_param_vectors_id_class_init (GParamSpecClass *klass);
+static void gimp_param_vectors_id_init (GParamSpec *pspec);
+
+GType
+gimp_param_vectors_id_get_type (void)
+{
+ static GType type = 0;
+
+ if (! type)
+ {
+ const GTypeInfo info =
+ {
+ sizeof (GParamSpecClass),
+ NULL, NULL,
+ (GClassInitFunc) gimp_param_vectors_id_class_init,
+ NULL, NULL,
+ sizeof (GimpParamSpecVectorsID),
+ 0,
+ (GInstanceInitFunc) gimp_param_vectors_id_init
+ };
+
+ type = g_type_register_static (GIMP_TYPE_PARAM_ITEM_ID,
+ "GimpParamVectorsID", &info, 0);
+ }
+
+ return type;
+}
+
+static void
+gimp_param_vectors_id_class_init (GParamSpecClass *klass)
+{
+ klass->value_type = GIMP_TYPE_VECTORS_ID;
+}
+
+static void
+gimp_param_vectors_id_init (GParamSpec *pspec)
+{
+ GimpParamSpecItemID *ispec = GIMP_PARAM_SPEC_ITEM_ID (pspec);
+
+ ispec->item_type = GIMP_TYPE_VECTORS;
+}
+
+GParamSpec *
+gimp_param_spec_vectors_id (const gchar *name,
+ const gchar *nick,
+ const gchar *blurb,
+ Gimp *gimp,
+ gboolean none_ok,
+ GParamFlags flags)
+{
+ GimpParamSpecItemID *ispec;
+
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
+
+ ispec = g_param_spec_internal (GIMP_TYPE_PARAM_VECTORS_ID,
+ name, nick, blurb, flags);
+
+ ispec->gimp = gimp;
+ ispec->none_ok = none_ok ? TRUE : FALSE;
+
+ return G_PARAM_SPEC (ispec);
+}
+
+GimpVectors *
+gimp_value_get_vectors (const GValue *value,
+ Gimp *gimp)
+{
+ GimpItem *item;
+
+ g_return_val_if_fail (GIMP_VALUE_HOLDS_VECTORS_ID (value), NULL);
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
+
+ item = gimp_item_get_by_ID (gimp, value->data[0].v_int);
+
+ if (item && ! GIMP_IS_VECTORS (item))
+ return NULL;
+
+ return GIMP_VECTORS (item);
+}
+
+void
+gimp_value_set_vectors (GValue *value,
+ GimpVectors *vectors)
+{
+ g_return_if_fail (GIMP_VALUE_HOLDS_VECTORS_ID (value));
+ g_return_if_fail (vectors == NULL || GIMP_IS_VECTORS (vectors));
+
+ value->data[0].v_int = vectors ? gimp_item_get_ID (GIMP_ITEM (vectors)) : -1;
+}
+
+
+/*
+ * GIMP_TYPE_DISPLAY_ID
+ */
+
+GType
+gimp_display_id_get_type (void)
+{
+ static GType type = 0;
+
+ if (! type)
+ {
+ const GTypeInfo info = { 0, };
+
+ type = g_type_register_static (G_TYPE_INT, "GimpDisplayID", &info, 0);
+ }
+
+ return type;
+}
+
+
+/*
+ * GIMP_TYPE_PARAM_DISPLAY_ID
+ */
+
+static void gimp_param_display_id_class_init (GParamSpecClass *klass);
+static void gimp_param_display_id_init (GParamSpec *pspec);
+static void gimp_param_display_id_set_default (GParamSpec *pspec,
+ GValue *value);
+static gboolean gimp_param_display_id_validate (GParamSpec *pspec,
+ GValue *value);
+static gint gimp_param_display_id_values_cmp (GParamSpec *pspec,
+ const GValue *value1,
+ const GValue *value2);
+
+GType
+gimp_param_display_id_get_type (void)
+{
+ static GType type = 0;
+
+ if (! type)
+ {
+ const GTypeInfo info =
+ {
+ sizeof (GParamSpecClass),
+ NULL, NULL,
+ (GClassInitFunc) gimp_param_display_id_class_init,
+ NULL, NULL,
+ sizeof (GimpParamSpecDisplayID),
+ 0,
+ (GInstanceInitFunc) gimp_param_display_id_init
+ };
+
+ type = g_type_register_static (G_TYPE_PARAM_INT,
+ "GimpParamDisplayID", &info, 0);
+ }
+
+ return type;
+}
+
+static void
+gimp_param_display_id_class_init (GParamSpecClass *klass)
+{
+ klass->value_type = GIMP_TYPE_DISPLAY_ID;
+ klass->value_set_default = gimp_param_display_id_set_default;
+ klass->value_validate = gimp_param_display_id_validate;
+ klass->values_cmp = gimp_param_display_id_values_cmp;
+}
+
+static void
+gimp_param_display_id_init (GParamSpec *pspec)
+{
+ GimpParamSpecDisplayID *ispec = GIMP_PARAM_SPEC_DISPLAY_ID (pspec);
+
+ ispec->gimp = NULL;
+ ispec->none_ok = FALSE;
+}
+
+static void
+gimp_param_display_id_set_default (GParamSpec *pspec,
+ GValue *value)
+{
+ value->data[0].v_int = -1;
+}
+
+static gboolean
+gimp_param_display_id_validate (GParamSpec *pspec,
+ GValue *value)
+{
+ GimpParamSpecDisplayID *ispec = GIMP_PARAM_SPEC_DISPLAY_ID (pspec);
+ gint display_id = value->data[0].v_int;
+ GimpObject *display;
+
+ if (ispec->none_ok && (display_id == 0 || display_id == -1))
+ return FALSE;
+
+ display = gimp_get_display_by_ID (ispec->gimp, display_id);
+
+ if (! GIMP_IS_OBJECT (display))
+ {
+ value->data[0].v_int = -1;
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gint
+gimp_param_display_id_values_cmp (GParamSpec *pspec,
+ const GValue *value1,
+ const GValue *value2)
+{
+ gint display_id1 = value1->data[0].v_int;
+ gint display_id2 = value2->data[0].v_int;
+
+ /* try to return at least *something*, it's useless anyway... */
+
+ if (display_id1 < display_id2)
+ return -1;
+ else if (display_id1 > display_id2)
+ return 1;
+ else
+ return 0;
+}
+
+GParamSpec *
+gimp_param_spec_display_id (const gchar *name,
+ const gchar *nick,
+ const gchar *blurb,
+ Gimp *gimp,
+ gboolean none_ok,
+ GParamFlags flags)
+{
+ GimpParamSpecDisplayID *ispec;
+
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
+
+ ispec = g_param_spec_internal (GIMP_TYPE_PARAM_DISPLAY_ID,
+ name, nick, blurb, flags);
+
+ ispec->gimp = gimp;
+ ispec->none_ok = none_ok ? TRUE : FALSE;
+
+ return G_PARAM_SPEC (ispec);
+}
+
+GimpObject *
+gimp_value_get_display (const GValue *value,
+ Gimp *gimp)
+{
+ g_return_val_if_fail (GIMP_VALUE_HOLDS_DISPLAY_ID (value), NULL);
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
+
+ return gimp_get_display_by_ID (gimp, value->data[0].v_int);
+}
+
+void
+gimp_value_set_display (GValue *value,
+ GimpObject *display)
+{
+ gint id = -1;
+
+ g_return_if_fail (GIMP_VALUE_HOLDS_DISPLAY_ID (value));
+ g_return_if_fail (display == NULL || GIMP_IS_OBJECT (display));
+
+ if (display)
+ g_object_get (display, "id", &id, NULL);
+
+ value->data[0].v_int = id;
+}
+
+
+/*
+ * GIMP_TYPE_ARRAY
+ */
+
+GimpArray *
+gimp_array_new (const guint8 *data,
+ gsize length,
+ gboolean static_data)
+{
+ GimpArray *array;
+
+ g_return_val_if_fail ((data == NULL && length == 0) ||
+ (data != NULL && length > 0), NULL);
+
+ array = g_slice_new0 (GimpArray);
+
+ array->data = static_data ? (guint8 *) data : g_memdup (data, length);
+ array->length = length;
+ array->static_data = static_data;
+
+ return array;
+}
+
+GimpArray *
+gimp_array_copy (const GimpArray *array)
+{
+ if (array)
+ return gimp_array_new (array->data, array->length, FALSE);
+
+ return NULL;
+}
+
+void
+gimp_array_free (GimpArray *array)
+{
+ if (array)
+ {
+ if (! array->static_data)
+ g_free (array->data);
+
+ g_slice_free (GimpArray, array);
+ }
+}
+
+GType
+gimp_array_get_type (void)
+{
+ static GType type = 0;
+
+ if (! type)
+ type = g_boxed_type_register_static ("GimpArray",
+ (GBoxedCopyFunc) gimp_array_copy,
+ (GBoxedFreeFunc) gimp_array_free);
+
+ return type;
+}
+
+
+/*
+ * GIMP_TYPE_PARAM_ARRAY
+ */
+
+static void gimp_param_array_class_init (GParamSpecClass *klass);
+static void gimp_param_array_init (GParamSpec *pspec);
+static gboolean gimp_param_array_validate (GParamSpec *pspec,
+ GValue *value);
+static gint gimp_param_array_values_cmp (GParamSpec *pspec,
+ const GValue *value1,
+ const GValue *value2);
+
+GType
+gimp_param_array_get_type (void)
+{
+ static GType type = 0;
+
+ if (! type)
+ {
+ const GTypeInfo info =
+ {
+ sizeof (GParamSpecClass),
+ NULL, NULL,
+ (GClassInitFunc) gimp_param_array_class_init,
+ NULL, NULL,
+ sizeof (GimpParamSpecArray),
+ 0,
+ (GInstanceInitFunc) gimp_param_array_init
+ };
+
+ type = g_type_register_static (G_TYPE_PARAM_BOXED,
+ "GimpParamArray", &info, 0);
+ }
+
+ return type;
+}
+
+static void
+gimp_param_array_class_init (GParamSpecClass *klass)
+{
+ klass->value_type = GIMP_TYPE_ARRAY;
+ klass->value_validate = gimp_param_array_validate;
+ klass->values_cmp = gimp_param_array_values_cmp;
+}
+
+static void
+gimp_param_array_init (GParamSpec *pspec)
+{
+}
+
+static gboolean
+gimp_param_array_validate (GParamSpec *pspec,
+ GValue *value)
+{
+ GimpArray *array = value->data[0].v_pointer;
+
+ if (array)
+ {
+ if ((array->data == NULL && array->length != 0) ||
+ (array->data != NULL && array->length == 0))
+ {
+ g_value_set_boxed (value, NULL);
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+static gint
+gimp_param_array_values_cmp (GParamSpec *pspec,
+ const GValue *value1,
+ const GValue *value2)
+{
+ GimpArray *array1 = value1->data[0].v_pointer;
+ GimpArray *array2 = value2->data[0].v_pointer;
+
+ /* try to return at least *something*, it's useless anyway... */
+
+ if (! array1)
+ return array2 != NULL ? -1 : 0;
+ else if (! array2)
+ return array1 != NULL ? 1 : 0;
+ else if (array1->length < array2->length)
+ return -1;
+ else if (array1->length > array2->length)
+ return 1;
+
+ return 0;
+}
+
+GParamSpec *
+gimp_param_spec_array (const gchar *name,
+ const gchar *nick,
+ const gchar *blurb,
+ GParamFlags flags)
+{
+ GimpParamSpecArray *array_spec;
+
+ array_spec = g_param_spec_internal (GIMP_TYPE_PARAM_ARRAY,
+ name, nick, blurb, flags);
+
+ return G_PARAM_SPEC (array_spec);
+}
+
+static const guint8 *
+gimp_value_get_array (const GValue *value)
+{
+ GimpArray *array = value->data[0].v_pointer;
+
+ if (array)
+ return array->data;
+
+ return NULL;
+}
+
+static guint8 *
+gimp_value_dup_array (const GValue *value)
+{
+ GimpArray *array = value->data[0].v_pointer;
+
+ if (array)
+ return g_memdup (array->data, array->length);
+
+ return NULL;
+}
+
+static void
+gimp_value_set_array (GValue *value,
+ const guint8 *data,
+ gsize length)
+{
+ GimpArray *array = gimp_array_new (data, length, FALSE);
+
+ g_value_take_boxed (value, array);
+}
+
+static void
+gimp_value_set_static_array (GValue *value,
+ const guint8 *data,
+ gsize length)
+{
+ GimpArray *array = gimp_array_new (data, length, TRUE);
+
+ g_value_take_boxed (value, array);
+}
+
+static void
+gimp_value_take_array (GValue *value,
+ guint8 *data,
+ gsize length)
+{
+ GimpArray *array = gimp_array_new (data, length, TRUE);
+
+ array->static_data = FALSE;
+
+ g_value_take_boxed (value, array);
+}
+
+
+/*
+ * GIMP_TYPE_INT8_ARRAY
+ */
+
+GType
+gimp_int8_array_get_type (void)
+{
+ static GType type = 0;
+
+ if (! type)
+ type = g_boxed_type_register_static ("GimpInt8Array",
+ (GBoxedCopyFunc) gimp_array_copy,
+ (GBoxedFreeFunc) gimp_array_free);
+
+ return type;
+}
+
+
+/*
+ * GIMP_TYPE_PARAM_INT8_ARRAY
+ */
+
+static void gimp_param_int8_array_class_init (GParamSpecClass *klass);
+static void gimp_param_int8_array_init (GParamSpec *pspec);
+
+GType
+gimp_param_int8_array_get_type (void)
+{
+ static GType type = 0;
+
+ if (! type)
+ {
+ const GTypeInfo info =
+ {
+ sizeof (GParamSpecClass),
+ NULL, NULL,
+ (GClassInitFunc) gimp_param_int8_array_class_init,
+ NULL, NULL,
+ sizeof (GimpParamSpecArray),
+ 0,
+ (GInstanceInitFunc) gimp_param_int8_array_init
+ };
+
+ type = g_type_register_static (GIMP_TYPE_PARAM_ARRAY,
+ "GimpParamInt8Array", &info, 0);
+ }
+
+ return type;
+}
+
+static void
+gimp_param_int8_array_class_init (GParamSpecClass *klass)
+{
+ klass->value_type = GIMP_TYPE_INT8_ARRAY;
+}
+
+static void
+gimp_param_int8_array_init (GParamSpec *pspec)
+{
+}
+
+GParamSpec *
+gimp_param_spec_int8_array (const gchar *name,
+ const gchar *nick,
+ const gchar *blurb,
+ GParamFlags flags)
+{
+ GimpParamSpecArray *array_spec;
+
+ array_spec = g_param_spec_internal (GIMP_TYPE_PARAM_INT8_ARRAY,
+ name, nick, blurb, flags);
+
+ return G_PARAM_SPEC (array_spec);
+}
+
+const guint8 *
+gimp_value_get_int8array (const GValue *value)
+{
+ g_return_val_if_fail (GIMP_VALUE_HOLDS_INT8_ARRAY (value), NULL);
+
+ return gimp_value_get_array (value);
+}
+
+guint8 *
+gimp_value_dup_int8array (const GValue *value)
+{
+ g_return_val_if_fail (GIMP_VALUE_HOLDS_INT8_ARRAY (value), NULL);
+
+ return gimp_value_dup_array (value);
+}
+
+void
+gimp_value_set_int8array (GValue *value,
+ const guint8 *data,
+ gsize length)
+{
+ g_return_if_fail (GIMP_VALUE_HOLDS_INT8_ARRAY (value));
+
+ gimp_value_set_array (value, data, length);
+}
+
+void
+gimp_value_set_static_int8array (GValue *value,
+ const guint8 *data,
+ gsize length)
+{
+ g_return_if_fail (GIMP_VALUE_HOLDS_INT8_ARRAY (value));
+
+ gimp_value_set_static_array (value, data, length);
+}
+
+void
+gimp_value_take_int8array (GValue *value,
+ guint8 *data,
+ gsize length)
+{
+ g_return_if_fail (GIMP_VALUE_HOLDS_INT8_ARRAY (value));
+
+ gimp_value_take_array (value, data, length);
+}
+
+
+/*
+ * GIMP_TYPE_INT16_ARRAY
+ */
+
+GType
+gimp_int16_array_get_type (void)
+{
+ static GType type = 0;
+
+ if (! type)
+ type = g_boxed_type_register_static ("GimpInt16Array",
+ (GBoxedCopyFunc) gimp_array_copy,
+ (GBoxedFreeFunc) gimp_array_free);
+
+ return type;
+}
+
+
+/*
+ * GIMP_TYPE_PARAM_INT16_ARRAY
+ */
+
+static void gimp_param_int16_array_class_init (GParamSpecClass *klass);
+static void gimp_param_int16_array_init (GParamSpec *pspec);
+
+GType
+gimp_param_int16_array_get_type (void)
+{
+ static GType type = 0;
+
+ if (! type)
+ {
+ const GTypeInfo info =
+ {
+ sizeof (GParamSpecClass),
+ NULL, NULL,
+ (GClassInitFunc) gimp_param_int16_array_class_init,
+ NULL, NULL,
+ sizeof (GimpParamSpecArray),
+ 0,
+ (GInstanceInitFunc) gimp_param_int16_array_init
+ };
+
+ type = g_type_register_static (GIMP_TYPE_PARAM_ARRAY,
+ "GimpParamInt16Array", &info, 0);
+ }
+
+ return type;
+}
+
+static void
+gimp_param_int16_array_class_init (GParamSpecClass *klass)
+{
+ klass->value_type = GIMP_TYPE_INT16_ARRAY;
+}
+
+static void
+gimp_param_int16_array_init (GParamSpec *pspec)
+{
+}
+
+GParamSpec *
+gimp_param_spec_int16_array (const gchar *name,
+ const gchar *nick,
+ const gchar *blurb,
+ GParamFlags flags)
+{
+ GimpParamSpecArray *array_spec;
+
+ array_spec = g_param_spec_internal (GIMP_TYPE_PARAM_INT16_ARRAY,
+ name, nick, blurb, flags);
+
+ return G_PARAM_SPEC (array_spec);
+}
+
+const gint16 *
+gimp_value_get_int16array (const GValue *value)
+{
+ g_return_val_if_fail (GIMP_VALUE_HOLDS_INT16_ARRAY (value), NULL);
+
+ return (const gint16 *) gimp_value_get_array (value);
+}
+
+gint16 *
+gimp_value_dup_int16array (const GValue *value)
+{
+ g_return_val_if_fail (GIMP_VALUE_HOLDS_INT16_ARRAY (value), NULL);
+
+ return (gint16 *) gimp_value_dup_array (value);
+}
+
+void
+gimp_value_set_int16array (GValue *value,
+ const gint16 *data,
+ gsize length)
+{
+ g_return_if_fail (GIMP_VALUE_HOLDS_INT16_ARRAY (value));
+
+ gimp_value_set_array (value, (const guint8 *) data,
+ length * sizeof (gint16));
+}
+
+void
+gimp_value_set_static_int16array (GValue *value,
+ const gint16 *data,
+ gsize length)
+{
+ g_return_if_fail (GIMP_VALUE_HOLDS_INT16_ARRAY (value));
+
+ gimp_value_set_static_array (value, (const guint8 *) data,
+ length * sizeof (gint16));
+}
+
+void
+gimp_value_take_int16array (GValue *value,
+ gint16 *data,
+ gsize length)
+{
+ g_return_if_fail (GIMP_VALUE_HOLDS_INT16_ARRAY (value));
+
+ gimp_value_take_array (value, (guint8 *) data,
+ length * sizeof (gint16));
+}
+
+
+/*
+ * GIMP_TYPE_INT32_ARRAY
+ */
+
+GType
+gimp_int32_array_get_type (void)
+{
+ static GType type = 0;
+
+ if (! type)
+ type = g_boxed_type_register_static ("GimpInt32Array",
+ (GBoxedCopyFunc) gimp_array_copy,
+ (GBoxedFreeFunc) gimp_array_free);
+
+ return type;
+}
+
+
+/*
+ * GIMP_TYPE_PARAM_INT32_ARRAY
+ */
+
+static void gimp_param_int32_array_class_init (GParamSpecClass *klass);
+static void gimp_param_int32_array_init (GParamSpec *pspec);
+
+GType
+gimp_param_int32_array_get_type (void)
+{
+ static GType type = 0;
+
+ if (! type)
+ {
+ const GTypeInfo info =
+ {
+ sizeof (GParamSpecClass),
+ NULL, NULL,
+ (GClassInitFunc) gimp_param_int32_array_class_init,
+ NULL, NULL,
+ sizeof (GimpParamSpecArray),
+ 0,
+ (GInstanceInitFunc) gimp_param_int32_array_init
+ };
+
+ type = g_type_register_static (GIMP_TYPE_PARAM_ARRAY,
+ "GimpParamInt32Array", &info, 0);
+ }
+
+ return type;
+}
+
+static void
+gimp_param_int32_array_class_init (GParamSpecClass *klass)
+{
+ klass->value_type = GIMP_TYPE_INT32_ARRAY;
+}
+
+static void
+gimp_param_int32_array_init (GParamSpec *pspec)
+{
+}
+
+GParamSpec *
+gimp_param_spec_int32_array (const gchar *name,
+ const gchar *nick,
+ const gchar *blurb,
+ GParamFlags flags)
+{
+ GimpParamSpecArray *array_spec;
+
+ array_spec = g_param_spec_internal (GIMP_TYPE_PARAM_INT32_ARRAY,
+ name, nick, blurb, flags);
+
+ return G_PARAM_SPEC (array_spec);
+}
+
+const gint32 *
+gimp_value_get_int32array (const GValue *value)
+{
+ g_return_val_if_fail (GIMP_VALUE_HOLDS_INT32_ARRAY (value), NULL);
+
+ return (const gint32 *) gimp_value_get_array (value);
+}
+
+gint32 *
+gimp_value_dup_int32array (const GValue *value)
+{
+ g_return_val_if_fail (GIMP_VALUE_HOLDS_INT32_ARRAY (value), NULL);
+
+ return (gint32 *) gimp_value_dup_array (value);
+}
+
+void
+gimp_value_set_int32array (GValue *value,
+ const gint32 *data,
+ gsize length)
+{
+ g_return_if_fail (GIMP_VALUE_HOLDS_INT32_ARRAY (value));
+
+ gimp_value_set_array (value, (const guint8 *) data,
+ length * sizeof (gint32));
+}
+
+void
+gimp_value_set_static_int32array (GValue *value,
+ const gint32 *data,
+ gsize length)
+{
+ g_return_if_fail (GIMP_VALUE_HOLDS_INT32_ARRAY (value));
+
+ gimp_value_set_static_array (value, (const guint8 *) data,
+ length * sizeof (gint32));
+}
+
+void
+gimp_value_take_int32array (GValue *value,
+ gint32 *data,
+ gsize length)
+{
+ g_return_if_fail (GIMP_VALUE_HOLDS_INT32_ARRAY (value));
+
+ gimp_value_take_array (value, (guint8 *) data,
+ length * sizeof (gint32));
+}
+
+
+/*
+ * GIMP_TYPE_FLOAT_ARRAY
+ */
+
+GType
+gimp_float_array_get_type (void)
+{
+ static GType type = 0;
+
+ if (! type)
+ type = g_boxed_type_register_static ("GimpFloatArray",
+ (GBoxedCopyFunc) gimp_array_copy,
+ (GBoxedFreeFunc) gimp_array_free);
+
+ return type;
+}
+
+
+/*
+ * GIMP_TYPE_PARAM_FLOAT_ARRAY
+ */
+
+static void gimp_param_float_array_class_init (GParamSpecClass *klass);
+static void gimp_param_float_array_init (GParamSpec *pspec);
+
+GType
+gimp_param_float_array_get_type (void)
+{
+ static GType type = 0;
+
+ if (! type)
+ {
+ const GTypeInfo info =
+ {
+ sizeof (GParamSpecClass),
+ NULL, NULL,
+ (GClassInitFunc) gimp_param_float_array_class_init,
+ NULL, NULL,
+ sizeof (GimpParamSpecArray),
+ 0,
+ (GInstanceInitFunc) gimp_param_float_array_init
+ };
+
+ type = g_type_register_static (GIMP_TYPE_PARAM_ARRAY,
+ "GimpParamFloatArray", &info, 0);
+ }
+
+ return type;
+}
+
+static void
+gimp_param_float_array_class_init (GParamSpecClass *klass)
+{
+ klass->value_type = GIMP_TYPE_FLOAT_ARRAY;
+}
+
+static void
+gimp_param_float_array_init (GParamSpec *pspec)
+{
+}
+
+GParamSpec *
+gimp_param_spec_float_array (const gchar *name,
+ const gchar *nick,
+ const gchar *blurb,
+ GParamFlags flags)
+{
+ GimpParamSpecArray *array_spec;
+
+ array_spec = g_param_spec_internal (GIMP_TYPE_PARAM_FLOAT_ARRAY,
+ name, nick, blurb, flags);
+
+ return G_PARAM_SPEC (array_spec);
+}
+
+const gdouble *
+gimp_value_get_floatarray (const GValue *value)
+{
+ g_return_val_if_fail (GIMP_VALUE_HOLDS_FLOAT_ARRAY (value), NULL);
+
+ return (const gdouble *) gimp_value_get_array (value);
+}
+
+gdouble *
+gimp_value_dup_floatarray (const GValue *value)
+{
+ g_return_val_if_fail (GIMP_VALUE_HOLDS_FLOAT_ARRAY (value), NULL);
+
+ return (gdouble *) gimp_value_dup_array (value);
+}
+
+void
+gimp_value_set_floatarray (GValue *value,
+ const gdouble *data,
+ gsize length)
+{
+ g_return_if_fail (GIMP_VALUE_HOLDS_FLOAT_ARRAY (value));
+
+ gimp_value_set_array (value, (const guint8 *) data,
+ length * sizeof (gdouble));
+}
+
+void
+gimp_value_set_static_floatarray (GValue *value,
+ const gdouble *data,
+ gsize length)
+{
+ g_return_if_fail (GIMP_VALUE_HOLDS_FLOAT_ARRAY (value));
+
+ gimp_value_set_static_array (value, (const guint8 *) data,
+ length * sizeof (gdouble));
+}
+
+void
+gimp_value_take_floatarray (GValue *value,
+ gdouble *data,
+ gsize length)
+{
+ g_return_if_fail (GIMP_VALUE_HOLDS_FLOAT_ARRAY (value));
+
+ gimp_value_take_array (value, (guint8 *) data,
+ length * sizeof (gdouble));
+}
+
+
+/*
+ * GIMP_TYPE_STRING_ARRAY
+ */
+
+GimpArray *
+gimp_string_array_new (const gchar **data,
+ gsize length,
+ gboolean static_data)
+{
+ GimpArray *array;
+
+ g_return_val_if_fail ((data == NULL && length == 0) ||
+ (data != NULL && length > 0), NULL);
+
+ array = g_slice_new0 (GimpArray);
+
+ if (! static_data)
+ {
+ gchar **tmp = g_new (gchar *, length);
+ gint i;
+
+ for (i = 0; i < length; i++)
+ tmp[i] = g_strdup (data[i]);
+
+ array->data = (guint8 *) tmp;
+ }
+ else
+ {
+ array->data = (guint8 *) data;
+ }
+
+ array->length = length;
+ array->static_data = static_data;
+
+ return array;
+}
+
+GimpArray *
+gimp_string_array_copy (const GimpArray *array)
+{
+ if (array)
+ return gimp_string_array_new ((const gchar **) array->data,
+ array->length, FALSE);
+
+ return NULL;
+}
+
+void
+gimp_string_array_free (GimpArray *array)
+{
+ if (array)
+ {
+ if (! array->static_data)
+ {
+ gchar **tmp = (gchar **) array->data;
+ gint i;
+
+ for (i = 0; i < array->length; i++)
+ g_free (tmp[i]);
+
+ g_free (array->data);
+ }
+
+ g_slice_free (GimpArray, array);
+ }
+}
+
+GType
+gimp_string_array_get_type (void)
+{
+ static GType type = 0;
+
+ if (! type)
+ type = g_boxed_type_register_static ("GimpStringArray",
+ (GBoxedCopyFunc) gimp_string_array_copy,
+ (GBoxedFreeFunc) gimp_string_array_free);
+
+ return type;
+}
+
+
+/*
+ * GIMP_TYPE_PARAM_STRING_ARRAY
+ */
+
+static void gimp_param_string_array_class_init (GParamSpecClass *klass);
+static void gimp_param_string_array_init (GParamSpec *pspec);
+static gboolean gimp_param_string_array_validate (GParamSpec *pspec,
+ GValue *value);
+static gint gimp_param_string_array_values_cmp (GParamSpec *pspec,
+ const GValue *value1,
+ const GValue *value2);
+
+GType
+gimp_param_string_array_get_type (void)
+{
+ static GType type = 0;
+
+ if (! type)
+ {
+ const GTypeInfo info =
+ {
+ sizeof (GParamSpecClass),
+ NULL, NULL,
+ (GClassInitFunc) gimp_param_string_array_class_init,
+ NULL, NULL,
+ sizeof (GimpParamSpecArray),
+ 0,
+ (GInstanceInitFunc) gimp_param_string_array_init
+ };
+
+ type = g_type_register_static (G_TYPE_PARAM_BOXED,
+ "GimpParamStringArray", &info, 0);
+ }
+
+ return type;
+}
+
+static void
+gimp_param_string_array_class_init (GParamSpecClass *klass)
+{
+ klass->value_type = GIMP_TYPE_STRING_ARRAY;
+ klass->value_validate = gimp_param_string_array_validate;
+ klass->values_cmp = gimp_param_string_array_values_cmp;
+}
+
+static void
+gimp_param_string_array_init (GParamSpec *pspec)
+{
+}
+
+static gboolean
+gimp_param_string_array_validate (GParamSpec *pspec,
+ GValue *value)
+{
+ GimpArray *array = value->data[0].v_pointer;
+
+ if (array)
+ {
+ if ((array->data == NULL && array->length != 0) ||
+ (array->data != NULL && array->length == 0))
+ {
+ g_value_set_boxed (value, NULL);
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+static gint
+gimp_param_string_array_values_cmp (GParamSpec *pspec,
+ const GValue *value1,
+ const GValue *value2)
+{
+ GimpArray *array1 = value1->data[0].v_pointer;
+ GimpArray *array2 = value2->data[0].v_pointer;
+
+ /* try to return at least *something*, it's useless anyway... */
+
+ if (! array1)
+ return array2 != NULL ? -1 : 0;
+ else if (! array2)
+ return array1 != NULL ? 1 : 0;
+ else if (array1->length < array2->length)
+ return -1;
+ else if (array1->length > array2->length)
+ return 1;
+
+ return 0;
+}
+
+GParamSpec *
+gimp_param_spec_string_array (const gchar *name,
+ const gchar *nick,
+ const gchar *blurb,
+ GParamFlags flags)
+{
+ GimpParamSpecStringArray *array_spec;
+
+ array_spec = g_param_spec_internal (GIMP_TYPE_PARAM_STRING_ARRAY,
+ name, nick, blurb, flags);
+
+ return G_PARAM_SPEC (array_spec);
+}
+
+const gchar **
+gimp_value_get_stringarray (const GValue *value)
+{
+ GimpArray *array;
+
+ g_return_val_if_fail (GIMP_VALUE_HOLDS_STRING_ARRAY (value), NULL);
+
+ array = value->data[0].v_pointer;
+
+ if (array)
+ return (const gchar **) array->data;
+
+ return NULL;
+}
+
+gchar **
+gimp_value_dup_stringarray (const GValue *value)
+{
+ GimpArray *array;
+
+ g_return_val_if_fail (GIMP_VALUE_HOLDS_STRING_ARRAY (value), NULL);
+
+ array = value->data[0].v_pointer;
+
+ if (array)
+ {
+ gchar **ret = g_memdup (array->data, array->length * sizeof (gchar *));
+ gint i;
+
+ for (i = 0; i < array->length; i++)
+ ret[i] = g_strdup (ret[i]);
+
+ return ret;
+ }
+
+ return NULL;
+}
+
+void
+gimp_value_set_stringarray (GValue *value,
+ const gchar **data,
+ gsize length)
+{
+ GimpArray *array;
+
+ g_return_if_fail (GIMP_VALUE_HOLDS_STRING_ARRAY (value));
+
+ array = gimp_string_array_new (data, length, FALSE);
+
+ g_value_take_boxed (value, array);
+}
+
+void
+gimp_value_set_static_stringarray (GValue *value,
+ const gchar **data,
+ gsize length)
+{
+ GimpArray *array;
+
+ g_return_if_fail (GIMP_VALUE_HOLDS_STRING_ARRAY (value));
+
+ array = gimp_string_array_new (data, length, TRUE);
+
+ g_value_take_boxed (value, array);
+}
+
+void
+gimp_value_take_stringarray (GValue *value,
+ gchar **data,
+ gsize length)
+{
+ GimpArray *array;
+
+ g_return_if_fail (GIMP_VALUE_HOLDS_STRING_ARRAY (value));
+
+ array = gimp_string_array_new ((const gchar **) data, length, TRUE);
+ array->static_data = FALSE;
+
+ g_value_take_boxed (value, array);
+}
+
+
+/*
+ * GIMP_TYPE_COLOR_ARRAY
+ */
+
+GType
+gimp_color_array_get_type (void)
+{
+ static GType type = 0;
+
+ if (! type)
+ type = g_boxed_type_register_static ("GimpColorArray",
+ (GBoxedCopyFunc) gimp_array_copy,
+ (GBoxedFreeFunc) gimp_array_free);
+
+ return type;
+}
+
+
+/*
+ * GIMP_TYPE_PARAM_COLOR_ARRAY
+ */
+
+static void gimp_param_color_array_class_init (GParamSpecClass *klass);
+static void gimp_param_color_array_init (GParamSpec *pspec);
+
+GType
+gimp_param_color_array_get_type (void)
+{
+ static GType type = 0;
+
+ if (! type)
+ {
+ const GTypeInfo info =
+ {
+ sizeof (GParamSpecClass),
+ NULL, NULL,
+ (GClassInitFunc) gimp_param_color_array_class_init,
+ NULL, NULL,
+ sizeof (GimpParamSpecArray),
+ 0,
+ (GInstanceInitFunc) gimp_param_color_array_init
+ };
+
+ type = g_type_register_static (G_TYPE_PARAM_BOXED,
+ "GimpParamColorArray", &info, 0);
+ }
+
+ return type;
+}
+
+static void
+gimp_param_color_array_class_init (GParamSpecClass *klass)
+{
+ klass->value_type = GIMP_TYPE_COLOR_ARRAY;
+}
+
+static void
+gimp_param_color_array_init (GParamSpec *pspec)
+{
+}
+
+GParamSpec *
+gimp_param_spec_color_array (const gchar *name,
+ const gchar *nick,
+ const gchar *blurb,
+ GParamFlags flags)
+{
+ GimpParamSpecColorArray *array_spec;
+
+ array_spec = g_param_spec_internal (GIMP_TYPE_PARAM_COLOR_ARRAY,
+ name, nick, blurb, flags);
+
+ return G_PARAM_SPEC (array_spec);
+}
+
+const GimpRGB *
+gimp_value_get_colorarray (const GValue *value)
+{
+ g_return_val_if_fail (GIMP_VALUE_HOLDS_COLOR_ARRAY (value), NULL);
+
+ return (const GimpRGB *) gimp_value_get_array (value);
+}
+
+GimpRGB *
+gimp_value_dup_colorarray (const GValue *value)
+{
+ g_return_val_if_fail (GIMP_VALUE_HOLDS_COLOR_ARRAY (value), NULL);
+
+ return (GimpRGB *) gimp_value_dup_array (value);
+}
+
+void
+gimp_value_set_colorarray (GValue *value,
+ const GimpRGB *data,
+ gsize length)
+{
+ g_return_if_fail (GIMP_VALUE_HOLDS_COLOR_ARRAY (value));
+
+ gimp_value_set_array (value, (const guint8 *) data,
+ length * sizeof (GimpRGB));
+}
+
+void
+gimp_value_set_static_colorarray (GValue *value,
+ const GimpRGB *data,
+ gsize length)
+{
+ g_return_if_fail (GIMP_VALUE_HOLDS_COLOR_ARRAY (value));
+
+ gimp_value_set_static_array (value, (const guint8 *) data,
+ length * sizeof (GimpRGB));
+}
+
+void
+gimp_value_take_colorarray (GValue *value,
+ GimpRGB *data,
+ gsize length)
+{
+ g_return_if_fail (GIMP_VALUE_HOLDS_COLOR_ARRAY (value));
+
+ gimp_value_take_array (value, (guint8 *) data,
+ length * sizeof (GimpRGB));
+}
diff --git a/app/core/gimpparamspecs.h b/app/core/gimpparamspecs.h
new file mode 100644
index 0000000..abfb56f
--- /dev/null
+++ b/app/core/gimpparamspecs.h
@@ -0,0 +1,904 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_PARAM_SPECS_H__
+#define __GIMP_PARAM_SPECS_H__
+
+
+/*
+ * Keep in sync with libgimpconfig/gimpconfig-params.h
+ */
+#define GIMP_PARAM_NO_VALIDATE (1 << (6 + G_PARAM_USER_SHIFT))
+
+
+/*
+ * GIMP_TYPE_INT32
+ */
+
+#define GIMP_TYPE_INT32 (gimp_int32_get_type ())
+#define GIMP_VALUE_HOLDS_INT32(value) (G_TYPE_CHECK_VALUE_TYPE ((value),\
+ GIMP_TYPE_INT32))
+
+GType gimp_int32_get_type (void) G_GNUC_CONST;
+
+
+/*
+ * GIMP_TYPE_PARAM_INT32
+ */
+
+#define GIMP_TYPE_PARAM_INT32 (gimp_param_int32_get_type ())
+#define GIMP_PARAM_SPEC_INT32(pspec) (G_TYPE_CHECK_INSTANCE_CAST ((pspec), GIMP_TYPE_PARAM_INT32, GimpParamSpecInt32))
+#define GIMP_IS_PARAM_SPEC_INT32(pspec) (G_TYPE_CHECK_INSTANCE_TYPE ((pspec), GIMP_TYPE_PARAM_INT32))
+
+typedef struct _GimpParamSpecInt32 GimpParamSpecInt32;
+
+struct _GimpParamSpecInt32
+{
+ GParamSpecInt parent_instance;
+};
+
+GType gimp_param_int32_get_type (void) G_GNUC_CONST;
+
+GParamSpec * gimp_param_spec_int32 (const gchar *name,
+ const gchar *nick,
+ const gchar *blurb,
+ gint minimum,
+ gint maximum,
+ gint default_value,
+ GParamFlags flags);
+
+
+/*
+ * GIMP_TYPE_INT16
+ */
+
+#define GIMP_TYPE_INT16 (gimp_int16_get_type ())
+#define GIMP_VALUE_HOLDS_INT16(value) (G_TYPE_CHECK_VALUE_TYPE ((value),\
+ GIMP_TYPE_INT16))
+
+GType gimp_int16_get_type (void) G_GNUC_CONST;
+
+
+/*
+ * GIMP_TYPE_PARAM_INT16
+ */
+
+#define GIMP_TYPE_PARAM_INT16 (gimp_param_int16_get_type ())
+#define GIMP_PARAM_SPEC_INT16(pspec) (G_TYPE_CHECK_INSTANCE_CAST ((pspec), GIMP_TYPE_PARAM_INT16, GimpParamSpecInt16))
+#define GIMP_IS_PARAM_SPEC_INT16(pspec) (G_TYPE_CHECK_INSTANCE_TYPE ((pspec), GIMP_TYPE_PARAM_INT16))
+
+typedef struct _GimpParamSpecInt16 GimpParamSpecInt16;
+
+struct _GimpParamSpecInt16
+{
+ GParamSpecInt parent_instance;
+};
+
+GType gimp_param_int16_get_type (void) G_GNUC_CONST;
+
+GParamSpec * gimp_param_spec_int16 (const gchar *name,
+ const gchar *nick,
+ const gchar *blurb,
+ gint minimum,
+ gint maximum,
+ gint default_value,
+ GParamFlags flags);
+
+
+/*
+ * GIMP_TYPE_INT8
+ */
+
+#define GIMP_TYPE_INT8 (gimp_int8_get_type ())
+#define GIMP_VALUE_HOLDS_INT8(value) (G_TYPE_CHECK_VALUE_TYPE ((value),\
+ GIMP_TYPE_INT8))
+
+GType gimp_int8_get_type (void) G_GNUC_CONST;
+
+
+/*
+ * GIMP_TYPE_PARAM_INT8
+ */
+
+#define GIMP_TYPE_PARAM_INT8 (gimp_param_int8_get_type ())
+#define GIMP_PARAM_SPEC_INT8(pspec) (G_TYPE_CHECK_INSTANCE_CAST ((pspec), GIMP_TYPE_PARAM_INT8, GimpParamSpecInt8))
+#define GIMP_IS_PARAM_SPEC_INT8(pspec) (G_TYPE_CHECK_INSTANCE_TYPE ((pspec), GIMP_TYPE_PARAM_INT8))
+
+typedef struct _GimpParamSpecInt8 GimpParamSpecInt8;
+
+struct _GimpParamSpecInt8
+{
+ GParamSpecUInt parent_instance;
+};
+
+GType gimp_param_int8_get_type (void) G_GNUC_CONST;
+
+GParamSpec * gimp_param_spec_int8 (const gchar *name,
+ const gchar *nick,
+ const gchar *blurb,
+ guint minimum,
+ guint maximum,
+ guint default_value,
+ GParamFlags flags);
+
+
+/*
+ * GIMP_TYPE_PARAM_STRING
+ */
+
+#define GIMP_TYPE_PARAM_STRING (gimp_param_string_get_type ())
+#define GIMP_PARAM_SPEC_STRING(pspec) (G_TYPE_CHECK_INSTANCE_CAST ((pspec), GIMP_TYPE_PARAM_STRING, GimpParamSpecString))
+#define GIMP_IS_PARAM_SPEC_STRING(pspec) (G_TYPE_CHECK_INSTANCE_TYPE ((pspec), GIMP_TYPE_PARAM_STRING))
+
+typedef struct _GimpParamSpecString GimpParamSpecString;
+
+struct _GimpParamSpecString
+{
+ GParamSpecString parent_instance;
+
+ guint allow_non_utf8 : 1;
+ guint non_empty : 1;
+};
+
+GType gimp_param_string_get_type (void) G_GNUC_CONST;
+
+GParamSpec * gimp_param_spec_string (const gchar *name,
+ const gchar *nick,
+ const gchar *blurb,
+ gboolean allow_non_utf8,
+ gboolean null_ok,
+ gboolean non_empty,
+ const gchar *default_value,
+ GParamFlags flags);
+
+
+/*
+ * GIMP_TYPE_PARAM_ENUM
+ */
+
+#define GIMP_TYPE_PARAM_ENUM (gimp_param_enum_get_type ())
+#define GIMP_PARAM_SPEC_ENUM(pspec) (G_TYPE_CHECK_INSTANCE_CAST ((pspec), GIMP_TYPE_PARAM_ENUM, GimpParamSpecEnum))
+
+#define GIMP_IS_PARAM_SPEC_ENUM(pspec) (G_TYPE_CHECK_INSTANCE_TYPE ((pspec), GIMP_TYPE_PARAM_ENUM))
+
+typedef struct _GimpParamSpecEnum GimpParamSpecEnum;
+
+struct _GimpParamSpecEnum
+{
+ GParamSpecEnum parent_instance;
+
+ GSList *excluded_values;
+};
+
+GType gimp_param_enum_get_type (void) G_GNUC_CONST;
+
+GParamSpec * gimp_param_spec_enum (const gchar *name,
+ const gchar *nick,
+ const gchar *blurb,
+ GType enum_type,
+ gint default_value,
+ GParamFlags flags);
+
+void gimp_param_spec_enum_exclude_value (GimpParamSpecEnum *espec,
+ gint value);
+
+
+/*
+ * GIMP_TYPE_IMAGE_ID
+ */
+
+#define GIMP_TYPE_IMAGE_ID (gimp_image_id_get_type ())
+#define GIMP_VALUE_HOLDS_IMAGE_ID(value) (G_TYPE_CHECK_VALUE_TYPE ((value),\
+ GIMP_TYPE_IMAGE_ID))
+
+GType gimp_image_id_get_type (void) G_GNUC_CONST;
+
+
+/*
+ * GIMP_TYPE_PARAM_IMAGE_ID
+ */
+
+#define GIMP_TYPE_PARAM_IMAGE_ID (gimp_param_image_id_get_type ())
+#define GIMP_PARAM_SPEC_IMAGE_ID(pspec) (G_TYPE_CHECK_INSTANCE_CAST ((pspec), GIMP_TYPE_PARAM_IMAGE_ID, GimpParamSpecImageID))
+#define GIMP_IS_PARAM_SPEC_IMAGE_ID(pspec) (G_TYPE_CHECK_INSTANCE_TYPE ((pspec), GIMP_TYPE_PARAM_IMAGE_ID))
+
+typedef struct _GimpParamSpecImageID GimpParamSpecImageID;
+
+struct _GimpParamSpecImageID
+{
+ GParamSpecInt parent_instance;
+
+ Gimp *gimp;
+ gboolean none_ok;
+};
+
+GType gimp_param_image_id_get_type (void) G_GNUC_CONST;
+
+GParamSpec * gimp_param_spec_image_id (const gchar *name,
+ const gchar *nick,
+ const gchar *blurb,
+ Gimp *gimp,
+ gboolean none_ok,
+ GParamFlags flags);
+
+GimpImage * gimp_value_get_image (const GValue *value,
+ Gimp *gimp);
+void gimp_value_set_image (GValue *value,
+ GimpImage *image);
+
+
+
+/*
+ * GIMP_TYPE_ITEM_ID
+ */
+
+#define GIMP_TYPE_ITEM_ID (gimp_item_id_get_type ())
+#define GIMP_VALUE_HOLDS_ITEM_ID(value) (G_TYPE_CHECK_VALUE_TYPE ((value),\
+ GIMP_TYPE_ITEM_ID))
+
+GType gimp_item_id_get_type (void) G_GNUC_CONST;
+
+
+/*
+ * GIMP_TYPE_PARAM_ITEM_ID
+ */
+
+#define GIMP_TYPE_PARAM_ITEM_ID (gimp_param_item_id_get_type ())
+#define GIMP_PARAM_SPEC_ITEM_ID(pspec) (G_TYPE_CHECK_INSTANCE_CAST ((pspec), GIMP_TYPE_PARAM_ITEM_ID, GimpParamSpecItemID))
+#define GIMP_IS_PARAM_SPEC_ITEM_ID(pspec) (G_TYPE_CHECK_INSTANCE_TYPE ((pspec), GIMP_TYPE_PARAM_ITEM_ID))
+
+typedef struct _GimpParamSpecItemID GimpParamSpecItemID;
+
+struct _GimpParamSpecItemID
+{
+ GParamSpecInt parent_instance;
+
+ Gimp *gimp;
+ GType item_type;
+ gboolean none_ok;
+};
+
+GType gimp_param_item_id_get_type (void) G_GNUC_CONST;
+
+GParamSpec * gimp_param_spec_item_id (const gchar *name,
+ const gchar *nick,
+ const gchar *blurb,
+ Gimp *gimp,
+ gboolean none_ok,
+ GParamFlags flags);
+
+GimpItem * gimp_value_get_item (const GValue *value,
+ Gimp *gimp);
+void gimp_value_set_item (GValue *value,
+ GimpItem *item);
+
+
+/*
+ * GIMP_TYPE_DRAWABLE_ID
+ */
+
+#define GIMP_TYPE_DRAWABLE_ID (gimp_drawable_id_get_type ())
+#define GIMP_VALUE_HOLDS_DRAWABLE_ID(value) (G_TYPE_CHECK_VALUE_TYPE ((value),\
+ GIMP_TYPE_DRAWABLE_ID))
+
+GType gimp_drawable_id_get_type (void) G_GNUC_CONST;
+
+
+/*
+ * GIMP_TYPE_PARAM_DRAWABLE_ID
+ */
+
+#define GIMP_TYPE_PARAM_DRAWABLE_ID (gimp_param_drawable_id_get_type ())
+#define GIMP_PARAM_SPEC_DRAWABLE_ID(pspec) (G_TYPE_CHECK_INSTANCE_CAST ((pspec), GIMP_TYPE_PARAM_DRAWABLE_ID, GimpParamSpecDrawableID))
+#define GIMP_IS_PARAM_SPEC_DRAWABLE_ID(pspec) (G_TYPE_CHECK_INSTANCE_TYPE ((pspec), GIMP_TYPE_PARAM_DRAWABLE_ID))
+
+typedef struct _GimpParamSpecDrawableID GimpParamSpecDrawableID;
+
+struct _GimpParamSpecDrawableID
+{
+ GimpParamSpecItemID parent_instance;
+};
+
+GType gimp_param_drawable_id_get_type (void) G_GNUC_CONST;
+
+GParamSpec * gimp_param_spec_drawable_id (const gchar *name,
+ const gchar *nick,
+ const gchar *blurb,
+ Gimp *gimp,
+ gboolean none_ok,
+ GParamFlags flags);
+
+GimpDrawable * gimp_value_get_drawable (const GValue *value,
+ Gimp *gimp);
+void gimp_value_set_drawable (GValue *value,
+ GimpDrawable *drawable);
+
+
+/*
+ * GIMP_TYPE_LAYER_ID
+ */
+
+#define GIMP_TYPE_LAYER_ID (gimp_layer_id_get_type ())
+#define GIMP_VALUE_HOLDS_LAYER_ID(value) (G_TYPE_CHECK_VALUE_TYPE ((value),\
+ GIMP_TYPE_LAYER_ID))
+
+GType gimp_layer_id_get_type (void) G_GNUC_CONST;
+
+
+/*
+ * GIMP_TYPE_PARAM_LAYER_ID
+ */
+
+#define GIMP_TYPE_PARAM_LAYER_ID (gimp_param_layer_id_get_type ())
+#define GIMP_PARAM_SPEC_LAYER_ID(pspec) (G_TYPE_CHECK_INSTANCE_CAST ((pspec), GIMP_TYPE_PARAM_LAYER_ID, GimpParamSpecLayerID))
+#define GIMP_IS_PARAM_SPEC_LAYER_ID(pspec) (G_TYPE_CHECK_INSTANCE_TYPE ((pspec), GIMP_TYPE_PARAM_LAYER_ID))
+
+typedef struct _GimpParamSpecLayerID GimpParamSpecLayerID;
+
+struct _GimpParamSpecLayerID
+{
+ GimpParamSpecDrawableID parent_instance;
+};
+
+GType gimp_param_layer_id_get_type (void) G_GNUC_CONST;
+
+GParamSpec * gimp_param_spec_layer_id (const gchar *name,
+ const gchar *nick,
+ const gchar *blurb,
+ Gimp *gimp,
+ gboolean none_ok,
+ GParamFlags flags);
+
+GimpLayer * gimp_value_get_layer (const GValue *value,
+ Gimp *gimp);
+void gimp_value_set_layer (GValue *value,
+ GimpLayer *layer);
+
+
+/*
+ * GIMP_TYPE_CHANNEL_ID
+ */
+
+#define GIMP_TYPE_CHANNEL_ID (gimp_channel_id_get_type ())
+#define GIMP_VALUE_HOLDS_CHANNEL_ID(value) (G_TYPE_CHECK_VALUE_TYPE ((value),\
+ GIMP_TYPE_CHANNEL_ID))
+
+GType gimp_channel_id_get_type (void) G_GNUC_CONST;
+
+
+/*
+ * GIMP_TYPE_PARAM_CHANNEL_ID
+ */
+
+#define GIMP_TYPE_PARAM_CHANNEL_ID (gimp_param_channel_id_get_type ())
+#define GIMP_PARAM_SPEC_CHANNEL_ID(pspec) (G_TYPE_CHECK_INSTANCE_CAST ((pspec), GIMP_TYPE_PARAM_CHANNEL_ID, GimpParamSpecChannelID))
+#define GIMP_IS_PARAM_SPEC_CHANNEL_ID(pspec) (G_TYPE_CHECK_INSTANCE_TYPE ((pspec), GIMP_TYPE_PARAM_CHANNEL_ID))
+
+typedef struct _GimpParamSpecChannelID GimpParamSpecChannelID;
+
+struct _GimpParamSpecChannelID
+{
+ GimpParamSpecDrawableID parent_instance;
+};
+
+GType gimp_param_channel_id_get_type (void) G_GNUC_CONST;
+
+GParamSpec * gimp_param_spec_channel_id (const gchar *name,
+ const gchar *nick,
+ const gchar *blurb,
+ Gimp *gimp,
+ gboolean none_ok,
+ GParamFlags flags);
+
+GimpChannel * gimp_value_get_channel (const GValue *value,
+ Gimp *gimp);
+void gimp_value_set_channel (GValue *value,
+ GimpChannel *channel);
+
+
+/*
+ * GIMP_TYPE_LAYER_MASK_ID
+ */
+
+#define GIMP_TYPE_LAYER_MASK_ID (gimp_layer_mask_id_get_type ())
+#define GIMP_VALUE_HOLDS_LAYER_MASK_ID(value) (G_TYPE_CHECK_VALUE_TYPE ((value),\
+ GIMP_TYPE_LAYER_MASK_ID))
+
+GType gimp_layer_mask_id_get_type (void) G_GNUC_CONST;
+
+
+/*
+ * GIMP_TYPE_PARAM_LAYER_MASK_ID
+ */
+
+#define GIMP_TYPE_PARAM_LAYER_MASK_ID (gimp_param_layer_mask_id_get_type ())
+#define GIMP_PARAM_SPEC_LAYER_MASK_ID(pspec) (G_TYPE_CHECK_INSTANCE_CAST ((pspec), GIMP_TYPE_PARAM_LAYER_MASK_ID, GimpParamSpecLayerMaskID))
+#define GIMP_IS_PARAM_SPEC_LAYER_MASK_ID(pspec) (G_TYPE_CHECK_INSTANCE_TYPE ((pspec), GIMP_TYPE_PARAM_LAYER_MASK_ID))
+
+typedef struct _GimpParamSpecLayerMaskID GimpParamSpecLayerMaskID;
+
+struct _GimpParamSpecLayerMaskID
+{
+ GimpParamSpecChannelID parent_instance;
+};
+
+GType gimp_param_layer_mask_id_get_type (void) G_GNUC_CONST;
+
+GParamSpec * gimp_param_spec_layer_mask_id (const gchar *name,
+ const gchar *nick,
+ const gchar *blurb,
+ Gimp *gimp,
+ gboolean none_ok,
+ GParamFlags flags);
+
+GimpLayerMask * gimp_value_get_layer_mask (const GValue *value,
+ Gimp *gimp);
+void gimp_value_set_layer_mask (GValue *value,
+ GimpLayerMask *layer_mask);
+
+
+/*
+ * GIMP_TYPE_SELECTION_ID
+ */
+
+#define GIMP_TYPE_SELECTION_ID (gimp_selection_id_get_type ())
+#define GIMP_VALUE_HOLDS_SELECTION_ID(value) (G_TYPE_CHECK_VALUE_TYPE ((value),\
+ GIMP_TYPE_SELECTION_ID))
+
+GType gimp_selection_id_get_type (void) G_GNUC_CONST;
+
+
+/*
+ * GIMP_TYPE_PARAM_SELECTION_ID
+ */
+
+#define GIMP_TYPE_PARAM_SELECTION_ID (gimp_param_selection_id_get_type ())
+#define GIMP_PARAM_SPEC_SELECTION_ID(pspec) (G_TYPE_CHECK_INSTANCE_CAST ((pspec), GIMP_TYPE_PARAM_SELECTION_ID, GimpParamSpecSelectionID))
+#define GIMP_IS_PARAM_SPEC_SELECTION_ID(pspec) (G_TYPE_CHECK_INSTANCE_TYPE ((pspec), GIMP_TYPE_PARAM_SELECTION_ID))
+
+typedef struct _GimpParamSpecSelectionID GimpParamSpecSelectionID;
+
+struct _GimpParamSpecSelectionID
+{
+ GimpParamSpecChannelID parent_instance;
+};
+
+GType gimp_param_selection_id_get_type (void) G_GNUC_CONST;
+
+GParamSpec * gimp_param_spec_selection_id (const gchar *name,
+ const gchar *nick,
+ const gchar *blurb,
+ Gimp *gimp,
+ gboolean none_ok,
+ GParamFlags flags);
+
+GimpSelection * gimp_value_get_selection (const GValue *value,
+ Gimp *gimp);
+void gimp_value_set_selection (GValue *value,
+ GimpSelection *selection);
+
+
+/*
+ * GIMP_TYPE_VECTORS_ID
+ */
+
+#define GIMP_TYPE_VECTORS_ID (gimp_vectors_id_get_type ())
+#define GIMP_VALUE_HOLDS_VECTORS_ID(value) (G_TYPE_CHECK_VALUE_TYPE ((value),\
+ GIMP_TYPE_VECTORS_ID))
+
+GType gimp_vectors_id_get_type (void) G_GNUC_CONST;
+
+
+/*
+ * GIMP_TYPE_PARAM_VECTORS_ID
+ */
+
+#define GIMP_TYPE_PARAM_VECTORS_ID (gimp_param_vectors_id_get_type ())
+#define GIMP_PARAM_SPEC_VECTORS_ID(pspec) (G_TYPE_CHECK_INSTANCE_CAST ((pspec), GIMP_TYPE_PARAM_VECTORS_ID, GimpParamSpecVectorsID))
+#define GIMP_IS_PARAM_SPEC_VECTORS_ID(pspec) (G_TYPE_CHECK_INSTANCE_TYPE ((pspec), GIMP_TYPE_PARAM_VECTORS_ID))
+
+typedef struct _GimpParamSpecVectorsID GimpParamSpecVectorsID;
+
+struct _GimpParamSpecVectorsID
+{
+ GimpParamSpecItemID parent_instance;
+};
+
+GType gimp_param_vectors_id_get_type (void) G_GNUC_CONST;
+
+GParamSpec * gimp_param_spec_vectors_id (const gchar *name,
+ const gchar *nick,
+ const gchar *blurb,
+ Gimp *gimp,
+ gboolean none_ok,
+ GParamFlags flags);
+
+GimpVectors * gimp_value_get_vectors (const GValue *value,
+ Gimp *gimp);
+void gimp_value_set_vectors (GValue *value,
+ GimpVectors *vectors);
+
+
+/*
+ * GIMP_TYPE_DISPLAY_ID
+ */
+
+#define GIMP_TYPE_DISPLAY_ID (gimp_display_id_get_type ())
+#define GIMP_VALUE_HOLDS_DISPLAY_ID(value) (G_TYPE_CHECK_VALUE_TYPE ((value),\
+ GIMP_TYPE_DISPLAY_ID))
+
+GType gimp_display_id_get_type (void) G_GNUC_CONST;
+
+
+/*
+ * GIMP_TYPE_PARAM_DISPLAY_ID
+ */
+
+#define GIMP_TYPE_PARAM_DISPLAY_ID (gimp_param_display_id_get_type ())
+#define GIMP_PARAM_SPEC_DISPLAY_ID(pspec) (G_TYPE_CHECK_INSTANCE_CAST ((pspec), GIMP_TYPE_PARAM_DISPLAY_ID, GimpParamSpecDisplayID))
+#define GIMP_IS_PARAM_SPEC_DISPLAY_ID(pspec) (G_TYPE_CHECK_INSTANCE_TYPE ((pspec), GIMP_TYPE_PARAM_DISPLAY_ID))
+
+typedef struct _GimpParamSpecDisplayID GimpParamSpecDisplayID;
+
+struct _GimpParamSpecDisplayID
+{
+ GParamSpecInt parent_instance;
+
+ Gimp *gimp;
+ gboolean none_ok;
+};
+
+GType gimp_param_display_id_get_type (void) G_GNUC_CONST;
+
+GParamSpec * gimp_param_spec_display_id (const gchar *name,
+ const gchar *nick,
+ const gchar *blurb,
+ Gimp *gimp,
+ gboolean none_ok,
+ GParamFlags flags);
+
+GimpObject * gimp_value_get_display (const GValue *value,
+ Gimp *gimp);
+void gimp_value_set_display (GValue *value,
+ GimpObject *display);
+
+
+/*
+ * GIMP_TYPE_ARRAY
+ */
+
+typedef struct _GimpArray GimpArray;
+
+struct _GimpArray
+{
+ guint8 *data;
+ gsize length;
+ gboolean static_data;
+};
+
+GimpArray * gimp_array_new (const guint8 *data,
+ gsize length,
+ gboolean static_data);
+GimpArray * gimp_array_copy (const GimpArray *array);
+void gimp_array_free (GimpArray *array);
+
+#define GIMP_TYPE_ARRAY (gimp_array_get_type ())
+#define GIMP_VALUE_HOLDS_ARRAY(value) (G_TYPE_CHECK_VALUE_TYPE ((value), GIMP_TYPE_ARRAY))
+
+GType gimp_array_get_type (void) G_GNUC_CONST;
+
+
+/*
+ * GIMP_TYPE_PARAM_ARRAY
+ */
+
+#define GIMP_TYPE_PARAM_ARRAY (gimp_param_array_get_type ())
+#define GIMP_PARAM_SPEC_ARRAY(pspec) (G_TYPE_CHECK_INSTANCE_CAST ((pspec), GIMP_TYPE_PARAM_ARRAY, GimpParamSpecArray))
+#define GIMP_IS_PARAM_SPEC_ARRAY(pspec) (G_TYPE_CHECK_INSTANCE_TYPE ((pspec), GIMP_TYPE_PARAM_ARRAY))
+
+typedef struct _GimpParamSpecArray GimpParamSpecArray;
+
+struct _GimpParamSpecArray
+{
+ GParamSpecBoxed parent_instance;
+};
+
+GType gimp_param_array_get_type (void) G_GNUC_CONST;
+
+GParamSpec * gimp_param_spec_array (const gchar *name,
+ const gchar *nick,
+ const gchar *blurb,
+ GParamFlags flags);
+
+
+/*
+ * GIMP_TYPE_INT8_ARRAY
+ */
+
+#define GIMP_TYPE_INT8_ARRAY (gimp_int8_array_get_type ())
+#define GIMP_VALUE_HOLDS_INT8_ARRAY(value) (G_TYPE_CHECK_VALUE_TYPE ((value), GIMP_TYPE_INT8_ARRAY))
+
+GType gimp_int8_array_get_type (void) G_GNUC_CONST;
+
+
+/*
+ * GIMP_TYPE_PARAM_INT8_ARRAY
+ */
+
+#define GIMP_TYPE_PARAM_INT8_ARRAY (gimp_param_int8_array_get_type ())
+#define GIMP_PARAM_SPEC_INT8_ARRAY(pspec) (G_TYPE_CHECK_INSTANCE_CAST ((pspec), GIMP_TYPE_PARAM_INT8_ARRAY, GimpParamSpecInt8Array))
+#define GIMP_IS_PARAM_SPEC_INT8_ARRAY(pspec) (G_TYPE_CHECK_INSTANCE_TYPE ((pspec), GIMP_TYPE_PARAM_INT8_ARRAY))
+
+typedef struct _GimpParamSpecInt8Array GimpParamSpecInt8Array;
+
+struct _GimpParamSpecInt8Array
+{
+ GimpParamSpecArray parent_instance;
+};
+
+GType gimp_param_int8_array_get_type (void) G_GNUC_CONST;
+
+GParamSpec * gimp_param_spec_int8_array (const gchar *name,
+ const gchar *nick,
+ const gchar *blurb,
+ GParamFlags flags);
+
+const guint8 * gimp_value_get_int8array (const GValue *value);
+guint8 * gimp_value_dup_int8array (const GValue *value);
+void gimp_value_set_int8array (GValue *value,
+ const guint8 *array,
+ gsize length);
+void gimp_value_set_static_int8array (GValue *value,
+ const guint8 *array,
+ gsize length);
+void gimp_value_take_int8array (GValue *value,
+ guint8 *array,
+ gsize length);
+
+
+/*
+ * GIMP_TYPE_INT16_ARRAY
+ */
+
+#define GIMP_TYPE_INT16_ARRAY (gimp_int16_array_get_type ())
+#define GIMP_VALUE_HOLDS_INT16_ARRAY(value) (G_TYPE_CHECK_VALUE_TYPE ((value), GIMP_TYPE_INT16_ARRAY))
+
+GType gimp_int16_array_get_type (void) G_GNUC_CONST;
+
+
+/*
+ * GIMP_TYPE_PARAM_INT16_ARRAY
+ */
+
+#define GIMP_TYPE_PARAM_INT16_ARRAY (gimp_param_int16_array_get_type ())
+#define GIMP_PARAM_SPEC_INT16_ARRAY(pspec) (G_TYPE_CHECK_INSTANCE_CAST ((pspec), GIMP_TYPE_PARAM_INT16_ARRAY, GimpParamSpecInt16Array))
+#define GIMP_IS_PARAM_SPEC_INT16_ARRAY(pspec) (G_TYPE_CHECK_INSTANCE_TYPE ((pspec), GIMP_TYPE_PARAM_INT16_ARRAY))
+
+typedef struct _GimpParamSpecInt16Array GimpParamSpecInt16Array;
+
+struct _GimpParamSpecInt16Array
+{
+ GimpParamSpecArray parent_instance;
+};
+
+GType gimp_param_int16_array_get_type (void) G_GNUC_CONST;
+
+GParamSpec * gimp_param_spec_int16_array (const gchar *name,
+ const gchar *nick,
+ const gchar *blurb,
+ GParamFlags flags);
+
+const gint16 * gimp_value_get_int16array (const GValue *value);
+gint16 * gimp_value_dup_int16array (const GValue *value);
+void gimp_value_set_int16array (GValue *value,
+ const gint16 *array,
+ gsize length);
+void gimp_value_set_static_int16array (GValue *value,
+ const gint16 *array,
+ gsize length);
+void gimp_value_take_int16array (GValue *value,
+ gint16 *array,
+ gsize length);
+
+
+/*
+ * GIMP_TYPE_INT32_ARRAY
+ */
+
+#define GIMP_TYPE_INT32_ARRAY (gimp_int32_array_get_type ())
+#define GIMP_VALUE_HOLDS_INT32_ARRAY(value) (G_TYPE_CHECK_VALUE_TYPE ((value), GIMP_TYPE_INT32_ARRAY))
+
+GType gimp_int32_array_get_type (void) G_GNUC_CONST;
+
+
+/*
+ * GIMP_TYPE_PARAM_INT32_ARRAY
+ */
+
+#define GIMP_TYPE_PARAM_INT32_ARRAY (gimp_param_int32_array_get_type ())
+#define GIMP_PARAM_SPEC_INT32_ARRAY(pspec) (G_TYPE_CHECK_INSTANCE_CAST ((pspec), GIMP_TYPE_PARAM_INT32_ARRAY, GimpParamSpecInt32Array))
+#define GIMP_IS_PARAM_SPEC_INT32_ARRAY(pspec) (G_TYPE_CHECK_INSTANCE_TYPE ((pspec), GIMP_TYPE_PARAM_INT32_ARRAY))
+
+typedef struct _GimpParamSpecInt32Array GimpParamSpecInt32Array;
+
+struct _GimpParamSpecInt32Array
+{
+ GimpParamSpecArray parent_instance;
+};
+
+GType gimp_param_int32_array_get_type (void) G_GNUC_CONST;
+
+GParamSpec * gimp_param_spec_int32_array (const gchar *name,
+ const gchar *nick,
+ const gchar *blurb,
+ GParamFlags flags);
+
+const gint32 * gimp_value_get_int32array (const GValue *value);
+gint32 * gimp_value_dup_int32array (const GValue *value);
+void gimp_value_set_int32array (GValue *value,
+ const gint32 *array,
+ gsize length);
+void gimp_value_set_static_int32array (GValue *value,
+ const gint32 *array,
+ gsize length);
+void gimp_value_take_int32array (GValue *value,
+ gint32 *array,
+ gsize length);
+
+
+/*
+ * GIMP_TYPE_FLOAT_ARRAY
+ */
+
+#define GIMP_TYPE_FLOAT_ARRAY (gimp_float_array_get_type ())
+#define GIMP_VALUE_HOLDS_FLOAT_ARRAY(value) (G_TYPE_CHECK_VALUE_TYPE ((value), GIMP_TYPE_FLOAT_ARRAY))
+
+GType gimp_float_array_get_type (void) G_GNUC_CONST;
+
+
+/*
+ * GIMP_TYPE_PARAM_FLOAT_ARRAY
+ */
+
+#define GIMP_TYPE_PARAM_FLOAT_ARRAY (gimp_param_float_array_get_type ())
+#define GIMP_PARAM_SPEC_FLOAT_ARRAY(pspec) (G_TYPE_CHECK_INSTANCE_CAST ((pspec), GIMP_TYPE_PARAM_FLOAT_ARRAY, GimpParamSpecFloatArray))
+#define GIMP_IS_PARAM_SPEC_FLOAT_ARRAY(pspec) (G_TYPE_CHECK_INSTANCE_TYPE ((pspec), GIMP_TYPE_PARAM_FLOAT_ARRAY))
+
+typedef struct _GimpParamSpecFloatArray GimpParamSpecFloatArray;
+
+struct _GimpParamSpecFloatArray
+{
+ GimpParamSpecArray parent_instance;
+};
+
+GType gimp_param_float_array_get_type (void) G_GNUC_CONST;
+
+GParamSpec * gimp_param_spec_float_array (const gchar *name,
+ const gchar *nick,
+ const gchar *blurb,
+ GParamFlags flags);
+
+const gdouble * gimp_value_get_floatarray (const GValue *value);
+gdouble * gimp_value_dup_floatarray (const GValue *value);
+void gimp_value_set_floatarray (GValue *value,
+ const gdouble *array,
+ gsize length);
+void gimp_value_set_static_floatarray (GValue *value,
+ const gdouble *array,
+ gsize length);
+void gimp_value_take_floatarray (GValue *value,
+ gdouble *array,
+ gsize length);
+
+
+/*
+ * GIMP_TYPE_STRING_ARRAY
+ */
+
+GimpArray * gimp_string_array_new (const gchar **data,
+ gsize length,
+ gboolean static_data);
+GimpArray * gimp_string_array_copy (const GimpArray *array);
+void gimp_string_array_free (GimpArray *array);
+
+#define GIMP_TYPE_STRING_ARRAY (gimp_string_array_get_type ())
+#define GIMP_VALUE_HOLDS_STRING_ARRAY(value) (G_TYPE_CHECK_VALUE_TYPE ((value), GIMP_TYPE_STRING_ARRAY))
+
+GType gimp_string_array_get_type (void) G_GNUC_CONST;
+
+
+/*
+ * GIMP_TYPE_PARAM_STRING_ARRAY
+ */
+
+#define GIMP_TYPE_PARAM_STRING_ARRAY (gimp_param_string_array_get_type ())
+#define GIMP_PARAM_SPEC_STRING_ARRAY(pspec) (G_TYPE_CHECK_INSTANCE_CAST ((pspec), GIMP_TYPE_PARAM_STRING_ARRAY, GimpParamSpecStringArray))
+#define GIMP_IS_PARAM_SPEC_STRING_ARRAY(pspec) (G_TYPE_CHECK_INSTANCE_TYPE ((pspec), GIMP_TYPE_PARAM_STRING_ARRAY))
+
+typedef struct _GimpParamSpecStringArray GimpParamSpecStringArray;
+
+struct _GimpParamSpecStringArray
+{
+ GParamSpecBoxed parent_instance;
+};
+
+GType gimp_param_string_array_get_type (void) G_GNUC_CONST;
+
+GParamSpec * gimp_param_spec_string_array (const gchar *name,
+ const gchar *nick,
+ const gchar *blurb,
+ GParamFlags flags);
+
+const gchar ** gimp_value_get_stringarray (const GValue *value);
+gchar ** gimp_value_dup_stringarray (const GValue *value);
+void gimp_value_set_stringarray (GValue *value,
+ const gchar **array,
+ gsize length);
+void gimp_value_set_static_stringarray (GValue *value,
+ const gchar **array,
+ gsize length);
+void gimp_value_take_stringarray (GValue *value,
+ gchar **array,
+ gsize length);
+
+
+/*
+ * GIMP_TYPE_COLOR_ARRAY
+ */
+
+#define GIMP_TYPE_COLOR_ARRAY (gimp_color_array_get_type ())
+#define GIMP_VALUE_HOLDS_COLOR_ARRAY(value) (G_TYPE_CHECK_VALUE_TYPE ((value), GIMP_TYPE_COLOR_ARRAY))
+
+GType gimp_color_array_get_type (void) G_GNUC_CONST;
+
+
+/*
+ * GIMP_TYPE_PARAM_COLOR_ARRAY
+ */
+
+#define GIMP_TYPE_PARAM_COLOR_ARRAY (gimp_param_color_array_get_type ())
+#define GIMP_PARAM_SPEC_COLOR_ARRAY(pspec) (G_TYPE_CHECK_INSTANCE_CAST ((pspec), GIMP_TYPE_PARAM_COLOR_ARRAY, GimpParamSpecColorArray))
+#define GIMP_IS_PARAM_SPEC_COLOR_ARRAY(pspec) (G_TYPE_CHECK_INSTANCE_TYPE ((pspec), GIMP_TYPE_PARAM_COLOR_ARRAY))
+
+typedef struct _GimpParamSpecColorArray GimpParamSpecColorArray;
+
+struct _GimpParamSpecColorArray
+{
+ GParamSpecBoxed parent_instance;
+};
+
+GType gimp_param_color_array_get_type (void) G_GNUC_CONST;
+
+GParamSpec * gimp_param_spec_color_array (const gchar *name,
+ const gchar *nick,
+ const gchar *blurb,
+ GParamFlags flags);
+
+const GimpRGB * gimp_value_get_colorarray (const GValue *value);
+GimpRGB * gimp_value_dup_colorarray (const GValue *value);
+void gimp_value_set_colorarray (GValue *value,
+ const GimpRGB *array,
+ gsize length);
+void gimp_value_set_static_colorarray (GValue *value,
+ const GimpRGB *array,
+ gsize length);
+void gimp_value_take_colorarray (GValue *value,
+ GimpRGB *array,
+ gsize length);
+
+
+#endif /* __GIMP_PARAM_SPECS_H__ */
diff --git a/app/core/gimpparasitelist.c b/app/core/gimpparasitelist.c
new file mode 100644
index 0000000..70a81fa
--- /dev/null
+++ b/app/core/gimpparasitelist.c
@@ -0,0 +1,453 @@
+/* parasitelist.c: Copyright 1998 Jay Cox <jaycox@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gio/gio.h>
+#include <gegl.h>
+
+#include "libgimpbase/gimpbase.h"
+#include "libgimpconfig/gimpconfig.h"
+
+#include "core-types.h"
+
+#include "gimp-memsize.h"
+#include "gimpmarshal.h"
+#include "gimpparasitelist.h"
+
+
+enum
+{
+ ADD,
+ REMOVE,
+ LAST_SIGNAL
+};
+
+
+static void gimp_parasite_list_finalize (GObject *object);
+static gint64 gimp_parasite_list_get_memsize (GimpObject *object,
+ gint64 *gui_size);
+
+static void gimp_parasite_list_config_iface_init (gpointer iface,
+ gpointer iface_data);
+static gboolean gimp_parasite_list_serialize (GimpConfig *list,
+ GimpConfigWriter *writer,
+ gpointer data);
+static gboolean gimp_parasite_list_deserialize (GimpConfig *list,
+ GScanner *scanner,
+ gint nest_level,
+ gpointer data);
+
+static void parasite_serialize (const gchar *key,
+ GimpParasite *parasite,
+ GimpConfigWriter *writer);
+static void parasite_copy (const gchar *key,
+ GimpParasite *parasite,
+ GimpParasiteList *list);
+static gboolean parasite_free (const gchar *key,
+ GimpParasite *parasite,
+ gpointer unused);
+static void parasite_count_if_persistent (const gchar *key,
+ GimpParasite *parasite,
+ gint *count);
+
+
+G_DEFINE_TYPE_WITH_CODE (GimpParasiteList, gimp_parasite_list, GIMP_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (GIMP_TYPE_CONFIG,
+ gimp_parasite_list_config_iface_init))
+
+#define parent_class gimp_parasite_list_parent_class
+
+static guint parasite_list_signals[LAST_SIGNAL] = { 0 };
+static const gchar parasite_symbol[] = "parasite";
+
+
+static void
+gimp_parasite_list_class_init (GimpParasiteListClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpObjectClass *gimp_object_class = GIMP_OBJECT_CLASS (klass);
+
+ parasite_list_signals[ADD] =
+ g_signal_new ("add",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpParasiteListClass, add),
+ NULL, NULL,
+ gimp_marshal_VOID__POINTER,
+ G_TYPE_NONE, 1,
+ G_TYPE_POINTER);
+
+ parasite_list_signals[REMOVE] =
+ g_signal_new ("remove",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpParasiteListClass, remove),
+ NULL, NULL,
+ gimp_marshal_VOID__POINTER,
+ G_TYPE_NONE, 1,
+ G_TYPE_POINTER);
+
+ object_class->finalize = gimp_parasite_list_finalize;
+
+ gimp_object_class->get_memsize = gimp_parasite_list_get_memsize;
+
+ klass->add = NULL;
+ klass->remove = NULL;
+}
+
+static void
+gimp_parasite_list_config_iface_init (gpointer iface,
+ gpointer iface_data)
+{
+ GimpConfigInterface *config_iface = (GimpConfigInterface *) iface;
+
+ config_iface->serialize = gimp_parasite_list_serialize;
+ config_iface->deserialize = gimp_parasite_list_deserialize;
+}
+
+static void
+gimp_parasite_list_init (GimpParasiteList *list)
+{
+ list->table = NULL;
+}
+
+static void
+gimp_parasite_list_finalize (GObject *object)
+{
+ GimpParasiteList *list = GIMP_PARASITE_LIST (object);
+
+ if (list->table)
+ {
+ g_hash_table_foreach_remove (list->table, (GHRFunc) parasite_free, NULL);
+ g_hash_table_destroy (list->table);
+ list->table = NULL;
+ }
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static gint64
+gimp_parasite_list_get_memsize (GimpObject *object,
+ gint64 *gui_size)
+{
+ GimpParasiteList *list = GIMP_PARASITE_LIST (object);
+ gint64 memsize = 0;
+
+ memsize += gimp_g_hash_table_get_memsize_foreach (list->table,
+ (GimpMemsizeFunc)
+ gimp_parasite_get_memsize,
+ gui_size);
+
+ return memsize + GIMP_OBJECT_CLASS (parent_class)->get_memsize (object,
+ gui_size);
+}
+
+static gboolean
+gimp_parasite_list_serialize (GimpConfig *list,
+ GimpConfigWriter *writer,
+ gpointer data)
+{
+ if (GIMP_PARASITE_LIST (list)->table)
+ g_hash_table_foreach (GIMP_PARASITE_LIST (list)->table,
+ (GHFunc) parasite_serialize,
+ writer);
+
+ return TRUE;
+}
+
+static gboolean
+gimp_parasite_list_deserialize (GimpConfig *list,
+ GScanner *scanner,
+ gint nest_level,
+ gpointer data)
+{
+ GTokenType token;
+
+ g_scanner_scope_add_symbol (scanner, 0,
+ parasite_symbol, (gpointer) parasite_symbol);
+
+ token = G_TOKEN_LEFT_PAREN;
+
+ while (g_scanner_peek_next_token (scanner) == token)
+ {
+ token = g_scanner_get_next_token (scanner);
+
+ switch (token)
+ {
+ case G_TOKEN_LEFT_PAREN:
+ token = G_TOKEN_SYMBOL;
+ break;
+
+ case G_TOKEN_SYMBOL:
+ if (scanner->value.v_symbol == parasite_symbol)
+ {
+ gchar *parasite_name = NULL;
+ gint parasite_flags = 0;
+ guint8 *parasite_data = NULL;
+ gint parasite_data_size = 0;
+ GimpParasite *parasite;
+
+ token = G_TOKEN_STRING;
+
+ if (g_scanner_peek_next_token (scanner) != token)
+ break;
+
+ if (! gimp_scanner_parse_string (scanner, &parasite_name))
+ break;
+
+ token = G_TOKEN_INT;
+
+ if (g_scanner_peek_next_token (scanner) != token)
+ goto cleanup;
+
+ if (! gimp_scanner_parse_int (scanner, &parasite_flags))
+ goto cleanup;
+
+ token = G_TOKEN_INT;
+
+ if (g_scanner_peek_next_token (scanner) != token)
+ {
+ /* old format -- plain string */
+
+ gchar *str;
+
+ if (g_scanner_peek_next_token (scanner) != G_TOKEN_STRING)
+ goto cleanup;
+
+ if (! gimp_scanner_parse_string (scanner, &str))
+ goto cleanup;
+
+ parasite_data_size = strlen (str);
+ parasite_data = (guint8 *) str;
+ }
+ else
+ {
+ /* new format -- properly encoded binary data */
+
+ if (! gimp_scanner_parse_int (scanner, &parasite_data_size))
+ goto cleanup;
+
+ token = G_TOKEN_STRING;
+
+ if (g_scanner_peek_next_token (scanner) != token)
+ goto cleanup;
+
+ if (! gimp_scanner_parse_data (scanner, parasite_data_size,
+ &parasite_data))
+ goto cleanup;
+ }
+
+ parasite = gimp_parasite_new (parasite_name,
+ parasite_flags,
+ parasite_data_size,
+ parasite_data);
+ gimp_parasite_list_add (GIMP_PARASITE_LIST (list),
+ parasite); /* adds a copy */
+ gimp_parasite_free (parasite);
+
+ token = G_TOKEN_RIGHT_PAREN;
+
+ g_free (parasite_data);
+ cleanup:
+ g_free (parasite_name);
+ }
+ break;
+
+ case G_TOKEN_RIGHT_PAREN:
+ token = G_TOKEN_LEFT_PAREN;
+ break;
+
+ default: /* do nothing */
+ break;
+ }
+ }
+
+ return gimp_config_deserialize_return (scanner, token, nest_level);
+}
+
+GimpParasiteList *
+gimp_parasite_list_new (void)
+{
+ GimpParasiteList *list;
+
+ list = g_object_new (GIMP_TYPE_PARASITE_LIST, NULL);
+
+ return list;
+}
+
+GimpParasiteList *
+gimp_parasite_list_copy (GimpParasiteList *list)
+{
+ GimpParasiteList *newlist;
+
+ g_return_val_if_fail (GIMP_IS_PARASITE_LIST (list), NULL);
+
+ newlist = gimp_parasite_list_new ();
+
+ if (list->table)
+ g_hash_table_foreach (list->table, (GHFunc) parasite_copy, newlist);
+
+ return newlist;
+}
+
+void
+gimp_parasite_list_add (GimpParasiteList *list,
+ const GimpParasite *parasite)
+{
+ GimpParasite *copy;
+
+ g_return_if_fail (GIMP_IS_PARASITE_LIST (list));
+ g_return_if_fail (parasite != NULL);
+ g_return_if_fail (parasite->name != NULL);
+
+ if (list->table == NULL)
+ list->table = g_hash_table_new (g_str_hash, g_str_equal);
+
+ gimp_parasite_list_remove (list, parasite->name);
+ copy = gimp_parasite_copy (parasite);
+ g_hash_table_insert (list->table, copy->name, copy);
+
+ g_signal_emit (list, parasite_list_signals[ADD], 0, copy);
+}
+
+void
+gimp_parasite_list_remove (GimpParasiteList *list,
+ const gchar *name)
+{
+ g_return_if_fail (GIMP_IS_PARASITE_LIST (list));
+
+ if (list->table)
+ {
+ GimpParasite *parasite;
+
+ parasite = (GimpParasite *) gimp_parasite_list_find (list, name);
+
+ if (parasite)
+ {
+ g_hash_table_remove (list->table, name);
+
+ g_signal_emit (list, parasite_list_signals[REMOVE], 0, parasite);
+
+ gimp_parasite_free (parasite);
+ }
+ }
+}
+
+gint
+gimp_parasite_list_length (GimpParasiteList *list)
+{
+ g_return_val_if_fail (GIMP_IS_PARASITE_LIST (list), 0);
+
+ if (! list->table)
+ return 0;
+
+ return g_hash_table_size (list->table);
+}
+
+gint
+gimp_parasite_list_persistent_length (GimpParasiteList *list)
+{
+ gint len = 0;
+
+ g_return_val_if_fail (GIMP_IS_PARASITE_LIST (list), 0);
+
+ if (! list->table)
+ return 0;
+
+ gimp_parasite_list_foreach (list,
+ (GHFunc) parasite_count_if_persistent, &len);
+
+ return len;
+}
+
+void
+gimp_parasite_list_foreach (GimpParasiteList *list,
+ GHFunc function,
+ gpointer user_data)
+{
+ g_return_if_fail (GIMP_IS_PARASITE_LIST (list));
+
+ if (! list->table)
+ return;
+
+ g_hash_table_foreach (list->table, function, user_data);
+}
+
+const GimpParasite *
+gimp_parasite_list_find (GimpParasiteList *list,
+ const gchar *name)
+{
+ g_return_val_if_fail (GIMP_IS_PARASITE_LIST (list), NULL);
+
+ if (list->table)
+ return (GimpParasite *) g_hash_table_lookup (list->table, name);
+
+ return NULL;
+}
+
+
+static void
+parasite_serialize (const gchar *key,
+ GimpParasite *parasite,
+ GimpConfigWriter *writer)
+{
+ if (! gimp_parasite_is_persistent (parasite))
+ return;
+
+ gimp_config_writer_open (writer, parasite_symbol);
+
+ gimp_config_writer_printf (writer, "\"%s\" %lu %lu",
+ gimp_parasite_name (parasite),
+ gimp_parasite_flags (parasite),
+ gimp_parasite_data_size (parasite));
+
+ gimp_config_writer_data (writer,
+ gimp_parasite_data_size (parasite),
+ gimp_parasite_data (parasite));
+
+ gimp_config_writer_close (writer);
+ gimp_config_writer_linefeed (writer);
+}
+
+static void
+parasite_copy (const gchar *key,
+ GimpParasite *parasite,
+ GimpParasiteList *list)
+{
+ gimp_parasite_list_add (list, parasite);
+}
+
+static gboolean
+parasite_free (const gchar *key,
+ GimpParasite *parasite,
+ gpointer unused)
+{
+ gimp_parasite_free (parasite);
+
+ return TRUE;
+}
+
+static void
+parasite_count_if_persistent (const gchar *key,
+ GimpParasite *parasite,
+ gint *count)
+{
+ if (gimp_parasite_is_persistent (parasite))
+ *count = *count + 1;
+}
diff --git a/app/core/gimpparasitelist.h b/app/core/gimpparasitelist.h
new file mode 100644
index 0000000..1762753
--- /dev/null
+++ b/app/core/gimpparasitelist.h
@@ -0,0 +1,69 @@
+/* parasitelist.h: Copyright 1998 Jay Cox <jaycox@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_PARASITE_LIST_H__
+#define __GIMP_PARASITE_LIST_H__
+
+
+#include "gimpobject.h"
+
+
+#define GIMP_TYPE_PARASITE_LIST (gimp_parasite_list_get_type ())
+#define GIMP_PARASITE_LIST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_PARASITE_LIST, GimpParasiteList))
+#define GIMP_PARASITE_LIST_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_PARASITE_LIST, GimpParasiteListClass))
+#define GIMP_IS_PARASITE_LIST(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_PARASITE_LIST))
+#define GIMP_IS_PARASITE_LIST_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_PARASITE_LIST))
+#define GIMP_PARASITE_LIST_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_PARASITE_LIST, GimpParasiteListClass))
+
+
+typedef struct _GimpParasiteListClass GimpParasiteListClass;
+
+struct _GimpParasiteList
+{
+ GimpObject object;
+
+ GHashTable *table;
+};
+
+struct _GimpParasiteListClass
+{
+ GimpObjectClass parent_class;
+
+ void (* add) (GimpParasiteList *list,
+ GimpParasite *parasite);
+ void (* remove) (GimpParasiteList *list,
+ GimpParasite *parasite);
+};
+
+
+GType gimp_parasite_list_get_type (void) G_GNUC_CONST;
+
+GimpParasiteList * gimp_parasite_list_new (void);
+GimpParasiteList * gimp_parasite_list_copy (GimpParasiteList *list);
+void gimp_parasite_list_add (GimpParasiteList *list,
+ const GimpParasite *parasite);
+void gimp_parasite_list_remove (GimpParasiteList *list,
+ const gchar *name);
+gint gimp_parasite_list_length (GimpParasiteList *list);
+gint gimp_parasite_list_persistent_length (GimpParasiteList *list);
+void gimp_parasite_list_foreach (GimpParasiteList *list,
+ GHFunc function,
+ gpointer user_data);
+const GimpParasite * gimp_parasite_list_find (GimpParasiteList *list,
+ const gchar *name);
+
+
+#endif /* __GIMP_PARASITE_LIST_H__ */
diff --git a/app/core/gimppattern-header.h b/app/core/gimppattern-header.h
new file mode 100644
index 0000000..e11b3c9
--- /dev/null
+++ b/app/core/gimppattern-header.h
@@ -0,0 +1,48 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_PATTERN_HEADER_H__
+#define __GIMP_PATTERN_HEADER_H__
+
+
+#define GIMP_PATTERN_MAGIC (('G' << 24) + ('P' << 16) + \
+ ('A' << 8) + ('T' << 0))
+#define GIMP_PATTERN_MAX_SIZE 10000 /* Max size in either dimension in px */
+#define GIMP_PATTERN_MAX_NAME 256 /* Max length of the pattern's name */
+
+
+/* All field entries are MSB */
+
+typedef struct _GimpPatternHeader GimpPatternHeader;
+
+struct _GimpPatternHeader
+{
+ guint32 header_size; /* = sizeof (GimpPatternHeader) + pattern name */
+ guint32 version; /* pattern file version # */
+ guint32 width; /* width of pattern */
+ guint32 height; /* height of pattern */
+ guint32 bytes; /* depth of pattern in bytes */
+ guint32 magic_number; /* GIMP pattern magic number */
+};
+
+/* In a pattern file, next comes the pattern name, null-terminated.
+ * After that comes the pattern data -- width * height * bytes bytes
+ * of it...
+ */
+
+
+#endif /* __GIMP_PATTERN_HEADER_H__ */
diff --git a/app/core/gimppattern-load.c b/app/core/gimppattern-load.c
new file mode 100644
index 0000000..27f057f
--- /dev/null
+++ b/app/core/gimppattern-load.c
@@ -0,0 +1,220 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <cairo.h>
+#include <gegl.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+#include "libgimpbase/gimpbase.h"
+#include "libgimpcolor/gimpcolor.h"
+
+#include "core-types.h"
+
+#include "gimppattern.h"
+#include "gimppattern-header.h"
+#include "gimppattern-load.h"
+#include "gimptempbuf.h"
+
+#include "gimp-intl.h"
+
+
+GList *
+gimp_pattern_load (GimpContext *context,
+ GFile *file,
+ GInputStream *input,
+ GError **error)
+{
+ GimpPattern *pattern = NULL;
+ const Babl *format = NULL;
+ GimpPatternHeader header;
+ gsize size;
+ gsize bytes_read;
+ gsize bn_size;
+ gchar *name = NULL;
+
+ g_return_val_if_fail (G_IS_FILE (file), NULL);
+ g_return_val_if_fail (G_IS_INPUT_STREAM (input), NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ /* read the size */
+ if (! g_input_stream_read_all (input, &header, sizeof (header),
+ &bytes_read, NULL, error) ||
+ bytes_read != sizeof (header))
+ {
+ g_prefix_error (error, _("File appears truncated: "));
+ goto error;
+ }
+
+ /* rearrange the bytes in each unsigned int */
+ header.header_size = g_ntohl (header.header_size);
+ header.version = g_ntohl (header.version);
+ header.width = g_ntohl (header.width);
+ header.height = g_ntohl (header.height);
+ header.bytes = g_ntohl (header.bytes);
+ header.magic_number = g_ntohl (header.magic_number);
+
+ /* Check for correct file format */
+ if (header.magic_number != GIMP_PATTERN_MAGIC ||
+ header.version != 1 ||
+ header.header_size <= sizeof (header))
+ {
+ g_set_error (error, GIMP_DATA_ERROR, GIMP_DATA_ERROR_READ,
+ _("Unknown pattern format version %d."),
+ header.version);
+ goto error;
+ }
+
+ /* Check for supported bit depths */
+ if (header.bytes < 1 || header.bytes > 4)
+ {
+ g_set_error (error, GIMP_DATA_ERROR, GIMP_DATA_ERROR_READ,
+ _("Unsupported pattern depth %d.\n"
+ "GIMP Patterns must be GRAY or RGB."),
+ header.bytes);
+ goto error;
+ }
+
+ /* Validate dimensions */
+ if ((header.width == 0) || (header.width > GIMP_PATTERN_MAX_SIZE) ||
+ (header.height == 0) || (header.height > GIMP_PATTERN_MAX_SIZE) ||
+ (G_MAXSIZE / header.width / header.height / header.bytes < 1))
+ {
+ g_set_error (error, GIMP_DATA_ERROR, GIMP_DATA_ERROR_READ,
+ _("Invalid header data in '%s': width=%lu (maximum %lu), "
+ "height=%lu (maximum %lu), bytes=%lu"),
+ gimp_file_get_utf8_name (file),
+ (gulong) header.width, (gulong) GIMP_PATTERN_MAX_SIZE,
+ (gulong) header.height, (gulong) GIMP_PATTERN_MAX_SIZE,
+ (gulong) header.bytes);
+ goto error;
+ }
+
+ /* Read in the pattern name */
+ if ((bn_size = (header.header_size - sizeof (header))))
+ {
+ gchar *utf8;
+
+ if (bn_size > GIMP_PATTERN_MAX_NAME)
+ {
+ g_set_error (error, GIMP_DATA_ERROR, GIMP_DATA_ERROR_READ,
+ _("Invalid header data in '%s': "
+ "Pattern name is too long: %lu"),
+ gimp_file_get_utf8_name (file),
+ (gulong) bn_size);
+ goto error;
+ }
+
+ name = g_new0 (gchar, bn_size + 1);
+
+ if (! g_input_stream_read_all (input, name, bn_size,
+ &bytes_read, NULL, error) ||
+ bytes_read != bn_size)
+ {
+ g_prefix_error (error, _("File appears truncated."));
+ g_free (name);
+ goto error;
+ }
+
+ utf8 = gimp_any_to_utf8 (name, bn_size - 1,
+ _("Invalid UTF-8 string in pattern file '%s'."),
+ gimp_file_get_utf8_name (file));
+ g_free (name);
+ name = utf8;
+ }
+
+ if (! name)
+ name = g_strdup (_("Unnamed"));
+
+ pattern = g_object_new (GIMP_TYPE_PATTERN,
+ "name", name,
+ "mime-type", "image/x-gimp-pat",
+ NULL);
+
+ g_free (name);
+
+ switch (header.bytes)
+ {
+ case 1: format = babl_format ("Y' u8"); break;
+ case 2: format = babl_format ("Y'A u8"); break;
+ case 3: format = babl_format ("R'G'B' u8"); break;
+ case 4: format = babl_format ("R'G'B'A u8"); break;
+ }
+
+ pattern->mask = gimp_temp_buf_new (header.width, header.height, format);
+ size = (gsize) header.width * header.height * header.bytes;
+
+ if (! g_input_stream_read_all (input,
+ gimp_temp_buf_get_data (pattern->mask), size,
+ &bytes_read, NULL, error) ||
+ bytes_read != size)
+ {
+ g_prefix_error (error, _("File appears truncated."));
+ goto error;
+ }
+
+ return g_list_prepend (NULL, pattern);
+
+ error:
+
+ if (pattern)
+ g_object_unref (pattern);
+
+ g_prefix_error (error, _("Fatal parse error in pattern file: "));
+
+ return NULL;
+}
+
+GList *
+gimp_pattern_load_pixbuf (GimpContext *context,
+ GFile *file,
+ GInputStream *input,
+ GError **error)
+{
+ GimpPattern *pattern;
+ GdkPixbuf *pixbuf;
+ gchar *name;
+
+ g_return_val_if_fail (G_IS_FILE (file), NULL);
+ g_return_val_if_fail (G_IS_INPUT_STREAM (input), NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ pixbuf = gdk_pixbuf_new_from_stream (input, NULL, error);
+ if (! pixbuf)
+ return NULL;
+
+ name = g_strdup (gdk_pixbuf_get_option (pixbuf, "tEXt::Title"));
+
+ if (! name)
+ name = g_strdup (gdk_pixbuf_get_option (pixbuf, "tEXt::Comment"));
+
+ if (! name)
+ name = g_path_get_basename (gimp_file_get_utf8_name (file));
+
+ pattern = g_object_new (GIMP_TYPE_PATTERN,
+ "name", name,
+ "mime-type", NULL, /* FIXME!! */
+ NULL);
+ g_free (name);
+
+ pattern->mask = gimp_temp_buf_new_from_pixbuf (pixbuf, NULL);
+
+ g_object_unref (pixbuf);
+
+ return g_list_prepend (NULL, pattern);
+}
diff --git a/app/core/gimppattern-load.h b/app/core/gimppattern-load.h
new file mode 100644
index 0000000..8e7fc8a
--- /dev/null
+++ b/app/core/gimppattern-load.h
@@ -0,0 +1,35 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_PATTERN_LOAD_H__
+#define __GIMP_PATTERN_LOAD_H__
+
+
+#define GIMP_PATTERN_FILE_EXTENSION ".pat"
+
+
+GList * gimp_pattern_load (GimpContext *context,
+ GFile *file,
+ GInputStream *input,
+ GError **error);
+GList * gimp_pattern_load_pixbuf (GimpContext *context,
+ GFile *file,
+ GInputStream *input,
+ GError **error);
+
+
+#endif /* __GIMP_PATTERN_LOAD_H__ */
diff --git a/app/core/gimppattern-save.c b/app/core/gimppattern-save.c
new file mode 100644
index 0000000..b9ded3e
--- /dev/null
+++ b/app/core/gimppattern-save.c
@@ -0,0 +1,87 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+#include "core-types.h"
+
+#include "gimppattern.h"
+#include "gimppattern-header.h"
+#include "gimppattern-save.h"
+#include "gimptempbuf.h"
+
+#include "gimp-intl.h"
+
+
+gboolean
+gimp_pattern_save (GimpData *data,
+ GOutputStream *output,
+ GError **error)
+{
+ GimpPattern *pattern = GIMP_PATTERN (data);
+ GimpTempBuf *mask = gimp_pattern_get_mask (pattern);
+ const Babl *format = gimp_temp_buf_get_format (mask);
+ GimpPatternHeader header;
+ const gchar *name;
+ gint width;
+ gint height;
+
+ name = gimp_object_get_name (pattern);
+ width = gimp_temp_buf_get_width (mask);
+ height = gimp_temp_buf_get_height (mask);
+
+ if (width > GIMP_PATTERN_MAX_SIZE || height > GIMP_PATTERN_MAX_SIZE)
+ {
+ g_set_error (error, GIMP_DATA_ERROR, GIMP_DATA_ERROR_READ,
+ _("Unsupported pattern dimensions %d x %d.\n"
+ "GIMP Patterns have a maximum size of %d x %d."),
+ width, height,
+ GIMP_PATTERN_MAX_SIZE, GIMP_PATTERN_MAX_SIZE);
+ return FALSE;
+ }
+ header.header_size = g_htonl (sizeof (GimpPatternHeader) +
+ strlen (name) + 1);
+ header.version = g_htonl (1);
+ header.width = g_htonl (width);
+ header.height = g_htonl (height);
+ header.bytes = g_htonl (babl_format_get_bytes_per_pixel (format));
+ header.magic_number = g_htonl (GIMP_PATTERN_MAGIC);
+
+ if (! g_output_stream_write_all (output, &header, sizeof (header),
+ NULL, NULL, error))
+ {
+ return FALSE;
+ }
+
+ if (! g_output_stream_write_all (output, name, strlen (name) + 1,
+ NULL, NULL, error))
+ {
+ return FALSE;
+ }
+
+ if (! g_output_stream_write_all (output,
+ gimp_temp_buf_get_data (mask),
+ gimp_temp_buf_get_data_size (mask),
+ NULL, NULL, error))
+ {
+ return FALSE;
+ }
+
+ return TRUE;
+}
diff --git a/app/core/gimppattern-save.h b/app/core/gimppattern-save.h
new file mode 100644
index 0000000..d3c657c
--- /dev/null
+++ b/app/core/gimppattern-save.h
@@ -0,0 +1,28 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_PATTERN_SAVE_H__
+#define __GIMP_PATTERN_SAVE_H__
+
+
+/* don't call this function directly, use gimp_data_save() instead */
+gboolean gimp_pattern_save (GimpData *data,
+ GOutputStream *output,
+ GError **error);
+
+
+#endif /* __GIMP_PATTERN_SAVE_H__ */
diff --git a/app/core/gimppattern.c b/app/core/gimppattern.c
new file mode 100644
index 0000000..3177211
--- /dev/null
+++ b/app/core/gimppattern.c
@@ -0,0 +1,287 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpbase/gimpbase.h"
+
+#include "core-types.h"
+
+#include "gegl/gimp-gegl-loops.h"
+
+#include "gimppattern.h"
+#include "gimppattern-load.h"
+#include "gimppattern-save.h"
+#include "gimptagged.h"
+#include "gimptempbuf.h"
+
+#include "gimp-intl.h"
+
+
+static void gimp_pattern_tagged_iface_init (GimpTaggedInterface *iface);
+static void gimp_pattern_finalize (GObject *object);
+
+static gint64 gimp_pattern_get_memsize (GimpObject *object,
+ gint64 *gui_size);
+
+static gboolean gimp_pattern_get_size (GimpViewable *viewable,
+ gint *width,
+ gint *height);
+static GimpTempBuf * gimp_pattern_get_new_preview (GimpViewable *viewable,
+ GimpContext *context,
+ gint width,
+ gint height);
+static gchar * gimp_pattern_get_description (GimpViewable *viewable,
+ gchar **tooltip);
+
+static const gchar * gimp_pattern_get_extension (GimpData *data);
+static void gimp_pattern_copy (GimpData *data,
+ GimpData *src_data);
+
+static gchar * gimp_pattern_get_checksum (GimpTagged *tagged);
+
+
+G_DEFINE_TYPE_WITH_CODE (GimpPattern, gimp_pattern, GIMP_TYPE_DATA,
+ G_IMPLEMENT_INTERFACE (GIMP_TYPE_TAGGED,
+ gimp_pattern_tagged_iface_init))
+
+#define parent_class gimp_pattern_parent_class
+
+
+static void
+gimp_pattern_class_init (GimpPatternClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpObjectClass *gimp_object_class = GIMP_OBJECT_CLASS (klass);
+ GimpViewableClass *viewable_class = GIMP_VIEWABLE_CLASS (klass);
+ GimpDataClass *data_class = GIMP_DATA_CLASS (klass);
+
+ object_class->finalize = gimp_pattern_finalize;
+
+ gimp_object_class->get_memsize = gimp_pattern_get_memsize;
+
+ viewable_class->default_icon_name = "gimp-tool-bucket-fill";
+ viewable_class->get_size = gimp_pattern_get_size;
+ viewable_class->get_new_preview = gimp_pattern_get_new_preview;
+ viewable_class->get_description = gimp_pattern_get_description;
+
+ data_class->save = gimp_pattern_save;
+ data_class->get_extension = gimp_pattern_get_extension;
+ data_class->copy = gimp_pattern_copy;
+}
+
+static void
+gimp_pattern_tagged_iface_init (GimpTaggedInterface *iface)
+{
+ iface->get_checksum = gimp_pattern_get_checksum;
+}
+
+static void
+gimp_pattern_init (GimpPattern *pattern)
+{
+ pattern->mask = NULL;
+}
+
+static void
+gimp_pattern_finalize (GObject *object)
+{
+ GimpPattern *pattern = GIMP_PATTERN (object);
+
+ g_clear_pointer (&pattern->mask, gimp_temp_buf_unref);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static gint64
+gimp_pattern_get_memsize (GimpObject *object,
+ gint64 *gui_size)
+{
+ GimpPattern *pattern = GIMP_PATTERN (object);
+ gint64 memsize = 0;
+
+ memsize += gimp_temp_buf_get_memsize (pattern->mask);
+
+ return memsize + GIMP_OBJECT_CLASS (parent_class)->get_memsize (object,
+ gui_size);
+}
+
+static gboolean
+gimp_pattern_get_size (GimpViewable *viewable,
+ gint *width,
+ gint *height)
+{
+ GimpPattern *pattern = GIMP_PATTERN (viewable);
+
+ *width = gimp_temp_buf_get_width (pattern->mask);
+ *height = gimp_temp_buf_get_height (pattern->mask);
+
+ return TRUE;
+}
+
+static GimpTempBuf *
+gimp_pattern_get_new_preview (GimpViewable *viewable,
+ GimpContext *context,
+ gint width,
+ gint height)
+{
+ GimpPattern *pattern = GIMP_PATTERN (viewable);
+ GimpTempBuf *temp_buf;
+ GeglBuffer *src_buffer;
+ GeglBuffer *dest_buffer;
+ gint copy_width;
+ gint copy_height;
+
+ copy_width = MIN (width, gimp_temp_buf_get_width (pattern->mask));
+ copy_height = MIN (height, gimp_temp_buf_get_height (pattern->mask));
+
+ temp_buf = gimp_temp_buf_new (copy_width, copy_height,
+ gimp_temp_buf_get_format (pattern->mask));
+
+ src_buffer = gimp_temp_buf_create_buffer (pattern->mask);
+ dest_buffer = gimp_temp_buf_create_buffer (temp_buf);
+
+ gimp_gegl_buffer_copy (src_buffer,
+ GEGL_RECTANGLE (0, 0, copy_width, copy_height),
+ GEGL_ABYSS_NONE,
+ dest_buffer, GEGL_RECTANGLE (0, 0, 0, 0));
+
+ g_object_unref (src_buffer);
+ g_object_unref (dest_buffer);
+
+ return temp_buf;
+}
+
+static gchar *
+gimp_pattern_get_description (GimpViewable *viewable,
+ gchar **tooltip)
+{
+ GimpPattern *pattern = GIMP_PATTERN (viewable);
+
+ return g_strdup_printf ("%s (%d × %d)",
+ gimp_object_get_name (pattern),
+ gimp_temp_buf_get_width (pattern->mask),
+ gimp_temp_buf_get_height (pattern->mask));
+}
+
+static const gchar *
+gimp_pattern_get_extension (GimpData *data)
+{
+ return GIMP_PATTERN_FILE_EXTENSION;
+}
+
+static void
+gimp_pattern_copy (GimpData *data,
+ GimpData *src_data)
+{
+ GimpPattern *pattern = GIMP_PATTERN (data);
+ GimpPattern *src_pattern = GIMP_PATTERN (src_data);
+
+ g_clear_pointer (&pattern->mask, gimp_temp_buf_unref);
+ pattern->mask = gimp_temp_buf_copy (src_pattern->mask);
+
+ gimp_data_dirty (data);
+}
+
+static gchar *
+gimp_pattern_get_checksum (GimpTagged *tagged)
+{
+ GimpPattern *pattern = GIMP_PATTERN (tagged);
+ gchar *checksum_string = NULL;
+
+ if (pattern->mask)
+ {
+ GChecksum *checksum = g_checksum_new (G_CHECKSUM_MD5);
+
+ g_checksum_update (checksum, gimp_temp_buf_get_data (pattern->mask),
+ gimp_temp_buf_get_data_size (pattern->mask));
+
+ checksum_string = g_strdup (g_checksum_get_string (checksum));
+
+ g_checksum_free (checksum);
+ }
+
+ return checksum_string;
+}
+
+GimpData *
+gimp_pattern_new (GimpContext *context,
+ const gchar *name)
+{
+ GimpPattern *pattern;
+ guchar *data;
+ gint row, col;
+
+ g_return_val_if_fail (name != NULL, NULL);
+ g_return_val_if_fail (name[0] != '\n', NULL);
+
+ pattern = g_object_new (GIMP_TYPE_PATTERN,
+ "name", name,
+ NULL);
+
+ pattern->mask = gimp_temp_buf_new (32, 32, babl_format ("R'G'B' u8"));
+
+ data = gimp_temp_buf_get_data (pattern->mask);
+
+ for (row = 0; row < gimp_temp_buf_get_height (pattern->mask); row++)
+ for (col = 0; col < gimp_temp_buf_get_width (pattern->mask); col++)
+ {
+ memset (data, (col % 2) && (row % 2) ? 255 : 0, 3);
+ data += 3;
+ }
+
+ return GIMP_DATA (pattern);
+}
+
+GimpData *
+gimp_pattern_get_standard (GimpContext *context)
+{
+ static GimpData *standard_pattern = NULL;
+
+ if (! standard_pattern)
+ {
+ standard_pattern = gimp_pattern_new (context, "Standard");
+
+ gimp_data_clean (standard_pattern);
+ gimp_data_make_internal (standard_pattern, "gimp-pattern-standard");
+
+ g_object_add_weak_pointer (G_OBJECT (standard_pattern),
+ (gpointer *) &standard_pattern);
+ }
+
+ return standard_pattern;
+}
+
+GimpTempBuf *
+gimp_pattern_get_mask (GimpPattern *pattern)
+{
+ g_return_val_if_fail (GIMP_IS_PATTERN (pattern), NULL);
+
+ return pattern->mask;
+}
+
+GeglBuffer *
+gimp_pattern_create_buffer (GimpPattern *pattern)
+{
+ g_return_val_if_fail (GIMP_IS_PATTERN (pattern), NULL);
+
+ return gimp_temp_buf_create_buffer (pattern->mask);
+}
diff --git a/app/core/gimppattern.h b/app/core/gimppattern.h
new file mode 100644
index 0000000..14cc099
--- /dev/null
+++ b/app/core/gimppattern.h
@@ -0,0 +1,58 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_PATTERN_H__
+#define __GIMP_PATTERN_H__
+
+
+#include "gimpdata.h"
+
+
+#define GIMP_TYPE_PATTERN (gimp_pattern_get_type ())
+#define GIMP_PATTERN(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_PATTERN, GimpPattern))
+#define GIMP_PATTERN_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_PATTERN, GimpPatternClass))
+#define GIMP_IS_PATTERN(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_PATTERN))
+#define GIMP_IS_PATTERN_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_PATTERN))
+#define GIMP_PATTERN_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_PATTERN, GimpPatternClass))
+
+
+typedef struct _GimpPatternClass GimpPatternClass;
+
+struct _GimpPattern
+{
+ GimpData parent_instance;
+
+ GimpTempBuf *mask;
+};
+
+struct _GimpPatternClass
+{
+ GimpDataClass parent_class;
+};
+
+
+GType gimp_pattern_get_type (void) G_GNUC_CONST;
+
+GimpData * gimp_pattern_new (GimpContext *context,
+ const gchar *name);
+GimpData * gimp_pattern_get_standard (GimpContext *context);
+
+GimpTempBuf * gimp_pattern_get_mask (GimpPattern *pattern);
+GeglBuffer * gimp_pattern_create_buffer (GimpPattern *pattern);
+
+
+#endif /* __GIMP_PATTERN_H__ */
diff --git a/app/core/gimppatternclipboard.c b/app/core/gimppatternclipboard.c
new file mode 100644
index 0000000..9dd6a1f
--- /dev/null
+++ b/app/core/gimppatternclipboard.c
@@ -0,0 +1,213 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimppatternclipboard.c
+ * Copyright (C) 2006 Michael Natterer <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "core-types.h"
+
+#include "gimp.h"
+#include "gimpbuffer.h"
+#include "gimppatternclipboard.h"
+#include "gimpimage.h"
+#include "gimppickable.h"
+#include "gimptempbuf.h"
+
+#include "gimp-intl.h"
+
+
+#define PATTERN_MAX_SIZE 1024
+
+enum
+{
+ PROP_0,
+ PROP_GIMP
+};
+
+
+/* local function prototypes */
+
+static void gimp_pattern_clipboard_constructed (GObject *object);
+static void gimp_pattern_clipboard_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_pattern_clipboard_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+static GimpData * gimp_pattern_clipboard_duplicate (GimpData *data);
+
+static void gimp_pattern_clipboard_changed (Gimp *gimp,
+ GimpPattern *pattern);
+
+
+G_DEFINE_TYPE (GimpPatternClipboard, gimp_pattern_clipboard, GIMP_TYPE_PATTERN)
+
+#define parent_class gimp_pattern_clipboard_parent_class
+
+
+static void
+gimp_pattern_clipboard_class_init (GimpPatternClipboardClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpDataClass *data_class = GIMP_DATA_CLASS (klass);
+
+ object_class->constructed = gimp_pattern_clipboard_constructed;
+ object_class->set_property = gimp_pattern_clipboard_set_property;
+ object_class->get_property = gimp_pattern_clipboard_get_property;
+
+ data_class->duplicate = gimp_pattern_clipboard_duplicate;
+
+ g_object_class_install_property (object_class, PROP_GIMP,
+ g_param_spec_object ("gimp", NULL, NULL,
+ GIMP_TYPE_GIMP,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+}
+
+static void
+gimp_pattern_clipboard_init (GimpPatternClipboard *pattern)
+{
+}
+
+static void
+gimp_pattern_clipboard_constructed (GObject *object)
+{
+ GimpPatternClipboard *pattern = GIMP_PATTERN_CLIPBOARD (object);
+
+ G_OBJECT_CLASS (parent_class)->constructed (object);
+
+ gimp_assert (GIMP_IS_GIMP (pattern->gimp));
+
+ g_signal_connect_object (pattern->gimp, "clipboard-changed",
+ G_CALLBACK (gimp_pattern_clipboard_changed),
+ pattern, 0);
+
+ gimp_pattern_clipboard_changed (pattern->gimp, GIMP_PATTERN (pattern));
+}
+
+static void
+gimp_pattern_clipboard_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpPatternClipboard *pattern = GIMP_PATTERN_CLIPBOARD (object);
+
+ switch (property_id)
+ {
+ case PROP_GIMP:
+ pattern->gimp = g_value_get_object (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_pattern_clipboard_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpPatternClipboard *pattern = GIMP_PATTERN_CLIPBOARD (object);
+
+ switch (property_id)
+ {
+ case PROP_GIMP:
+ g_value_set_object (value, pattern->gimp);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static GimpData *
+gimp_pattern_clipboard_duplicate (GimpData *data)
+{
+ GimpData *new = g_object_new (GIMP_TYPE_PATTERN, NULL);
+
+ gimp_data_copy (new, data);
+
+ return new;
+}
+
+GimpData *
+gimp_pattern_clipboard_new (Gimp *gimp)
+{
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
+
+ return g_object_new (GIMP_TYPE_PATTERN_CLIPBOARD,
+ "name", _("Clipboard Image"),
+ "gimp", gimp,
+ NULL);
+}
+
+
+/* private functions */
+
+static void
+gimp_pattern_clipboard_changed (Gimp *gimp,
+ GimpPattern *pattern)
+{
+ GimpObject *paste;
+ GeglBuffer *buffer = NULL;
+
+ g_clear_pointer (&pattern->mask, gimp_temp_buf_unref);
+
+ paste = gimp_get_clipboard_object (gimp);
+
+ if (GIMP_IS_IMAGE (paste))
+ {
+ gimp_pickable_flush (GIMP_PICKABLE (paste));
+ buffer = gimp_pickable_get_buffer (GIMP_PICKABLE (paste));
+ }
+ else if (GIMP_IS_BUFFER (paste))
+ {
+ buffer = gimp_buffer_get_buffer (GIMP_BUFFER (paste));
+ }
+
+ if (buffer)
+ {
+ gint width = MIN (gegl_buffer_get_width (buffer), PATTERN_MAX_SIZE);
+ gint height = MIN (gegl_buffer_get_height (buffer), PATTERN_MAX_SIZE);
+
+ pattern->mask = gimp_temp_buf_new (width, height,
+ gegl_buffer_get_format (buffer));
+
+ gegl_buffer_get (buffer,
+ GEGL_RECTANGLE (0, 0, width, height), 1.0,
+ NULL,
+ gimp_temp_buf_get_data (pattern->mask),
+ GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
+ }
+ else
+ {
+ pattern->mask = gimp_temp_buf_new (16, 16, babl_format ("R'G'B' u8"));
+ memset (gimp_temp_buf_get_data (pattern->mask), 255, 16 * 16 * 3);
+ }
+
+ gimp_data_dirty (GIMP_DATA (pattern));
+}
diff --git a/app/core/gimppatternclipboard.h b/app/core/gimppatternclipboard.h
new file mode 100644
index 0000000..2707291
--- /dev/null
+++ b/app/core/gimppatternclipboard.h
@@ -0,0 +1,56 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimppatternclipboard.h
+ * Copyright (C) 2006 Michael Natterer <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_PATTERN_CLIPBOARD_H__
+#define __GIMP_PATTERN_CLIPBOARD_H__
+
+
+#include "gimppattern.h"
+
+
+#define GIMP_TYPE_PATTERN_CLIPBOARD (gimp_pattern_clipboard_get_type ())
+#define GIMP_PATTERN_CLIPBOARD(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_PATTERN_CLIPBOARD, GimpPatternClipboard))
+#define GIMP_PATTERN_CLIPBOARD_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_PATTERN_CLIPBOARD, GimpPatternClipboardClass))
+#define GIMP_IS_PATTERN_CLIPBOARD(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_PATTERN_CLIPBOARD))
+#define GIMP_IS_PATTERN_CLIPBOARD_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_PATTERN_CLIPBOARD))
+#define GIMP_PATTERN_CLIPBOARD_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_PATTERN_CLIPBOARD, GimpPatternClipboardClass))
+
+
+typedef struct _GimpPatternClipboardClass GimpPatternClipboardClass;
+
+struct _GimpPatternClipboard
+{
+ GimpPattern parent_instance;
+
+ Gimp *gimp;
+};
+
+struct _GimpPatternClipboardClass
+{
+ GimpPatternClass parent_class;
+};
+
+
+GType gimp_pattern_clipboard_get_type (void) G_GNUC_CONST;
+
+GimpData * gimp_pattern_clipboard_new (Gimp *gimp);
+
+
+#endif /* __GIMP_PATTERN_CLIPBOARD_H__ */
diff --git a/app/core/gimppdbprogress.c b/app/core/gimppdbprogress.c
new file mode 100644
index 0000000..85147e8
--- /dev/null
+++ b/app/core/gimppdbprogress.c
@@ -0,0 +1,408 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimppdbprogress.c
+ * Copyright (C) 2004 Michael Natterer <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpbase/gimpbase.h"
+
+#include "core-types.h"
+
+#include "core/gimpcontext.h"
+
+#include "pdb/gimppdb.h"
+
+#include "gimp.h"
+#include "gimpparamspecs.h"
+#include "gimppdbprogress.h"
+#include "gimpprogress.h"
+
+#include "gimp-intl.h"
+
+
+enum
+{
+ PROP_0,
+ PROP_PDB,
+ PROP_CONTEXT,
+ PROP_CALLBACK_NAME
+};
+
+
+static void gimp_pdb_progress_class_init (GimpPdbProgressClass *klass);
+static void gimp_pdb_progress_init (GimpPdbProgress *progress,
+ GimpPdbProgressClass *klass);
+static void gimp_pdb_progress_progress_iface_init (GimpProgressInterface *iface);
+
+static void gimp_pdb_progress_constructed (GObject *object);
+static void gimp_pdb_progress_dispose (GObject *object);
+static void gimp_pdb_progress_finalize (GObject *object);
+static void gimp_pdb_progress_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+
+static GimpProgress * gimp_pdb_progress_progress_start (GimpProgress *progress,
+ gboolean cancellable,
+ const gchar *message);
+static void gimp_pdb_progress_progress_end (GimpProgress *progress);
+static gboolean gimp_pdb_progress_progress_is_active (GimpProgress *progress);
+static void gimp_pdb_progress_progress_set_text (GimpProgress *progress,
+ const gchar *message);
+static void gimp_pdb_progress_progress_set_value (GimpProgress *progress,
+ gdouble percentage);
+static gdouble gimp_pdb_progress_progress_get_value (GimpProgress *progress);
+static void gimp_pdb_progress_progress_pulse (GimpProgress *progress);
+static guint32 gimp_pdb_progress_progress_get_window_id (GimpProgress *progress);
+
+
+static GObjectClass *parent_class = NULL;
+
+
+GType
+gimp_pdb_progress_get_type (void)
+{
+ static GType type = 0;
+
+ if (! type)
+ {
+ const GTypeInfo info =
+ {
+ sizeof (GimpPdbProgressClass),
+ (GBaseInitFunc) NULL,
+ (GBaseFinalizeFunc) NULL,
+ (GClassInitFunc) gimp_pdb_progress_class_init,
+ NULL, /* class_finalize */
+ NULL, /* class_data */
+ sizeof (GimpPdbProgress),
+ 0, /* n_preallocs */
+ (GInstanceInitFunc) gimp_pdb_progress_init,
+ };
+
+ const GInterfaceInfo progress_iface_info =
+ {
+ (GInterfaceInitFunc) gimp_pdb_progress_progress_iface_init,
+ NULL, /* iface_finalize */
+ NULL /* iface_data */
+ };
+
+ type = g_type_register_static (G_TYPE_OBJECT,
+ "GimpPdbProgress",
+ &info, 0);
+
+ g_type_add_interface_static (type, GIMP_TYPE_PROGRESS,
+ &progress_iface_info);
+ }
+
+ return type;
+}
+
+static void
+gimp_pdb_progress_class_init (GimpPdbProgressClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ parent_class = g_type_class_peek_parent (klass);
+
+ object_class->constructed = gimp_pdb_progress_constructed;
+ object_class->dispose = gimp_pdb_progress_dispose;
+ object_class->finalize = gimp_pdb_progress_finalize;
+ object_class->set_property = gimp_pdb_progress_set_property;
+
+ g_object_class_install_property (object_class, PROP_PDB,
+ g_param_spec_object ("pdb", NULL, NULL,
+ GIMP_TYPE_PDB,
+ GIMP_PARAM_WRITABLE |
+ G_PARAM_CONSTRUCT_ONLY));
+
+ g_object_class_install_property (object_class, PROP_CONTEXT,
+ g_param_spec_object ("context", NULL, NULL,
+ GIMP_TYPE_CONTEXT,
+ GIMP_PARAM_WRITABLE |
+ G_PARAM_CONSTRUCT_ONLY));
+
+ g_object_class_install_property (object_class, PROP_CALLBACK_NAME,
+ g_param_spec_string ("callback-name",
+ NULL, NULL,
+ NULL,
+ GIMP_PARAM_WRITABLE |
+ G_PARAM_CONSTRUCT_ONLY));
+}
+
+static void
+gimp_pdb_progress_init (GimpPdbProgress *progress,
+ GimpPdbProgressClass *klass)
+{
+ klass->progresses = g_list_prepend (klass->progresses, progress);
+}
+
+static void
+gimp_pdb_progress_progress_iface_init (GimpProgressInterface *iface)
+{
+ iface->start = gimp_pdb_progress_progress_start;
+ iface->end = gimp_pdb_progress_progress_end;
+ iface->is_active = gimp_pdb_progress_progress_is_active;
+ iface->set_text = gimp_pdb_progress_progress_set_text;
+ iface->set_value = gimp_pdb_progress_progress_set_value;
+ iface->get_value = gimp_pdb_progress_progress_get_value;
+ iface->pulse = gimp_pdb_progress_progress_pulse;
+ iface->get_window_id = gimp_pdb_progress_progress_get_window_id;
+}
+
+static void
+gimp_pdb_progress_constructed (GObject *object)
+{
+ GimpPdbProgress *progress = GIMP_PDB_PROGRESS (object);
+
+ G_OBJECT_CLASS (parent_class)->constructed (object);
+
+ gimp_assert (GIMP_IS_PDB (progress->pdb));
+ gimp_assert (GIMP_IS_CONTEXT (progress->context));
+}
+
+static void
+gimp_pdb_progress_dispose (GObject *object)
+{
+ GimpPdbProgressClass *klass = GIMP_PDB_PROGRESS_GET_CLASS (object);
+
+ klass->progresses = g_list_remove (klass->progresses, object);
+
+ G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+gimp_pdb_progress_finalize (GObject *object)
+{
+ GimpPdbProgress *progress = GIMP_PDB_PROGRESS (object);
+
+ g_clear_object (&progress->pdb);
+ g_clear_object (&progress->context);
+ g_clear_pointer (&progress->callback_name, g_free);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gimp_pdb_progress_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpPdbProgress *progress = GIMP_PDB_PROGRESS (object);
+
+ switch (property_id)
+ {
+ case PROP_PDB:
+ if (progress->pdb)
+ g_object_unref (progress->pdb);
+ progress->pdb = g_value_dup_object (value);
+ break;
+
+ case PROP_CONTEXT:
+ if (progress->context)
+ g_object_unref (progress->context);
+ progress->context = g_value_dup_object (value);
+ break;
+
+ case PROP_CALLBACK_NAME:
+ if (progress->callback_name)
+ g_free (progress->callback_name);
+ progress->callback_name = g_value_dup_string (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static gdouble
+gimp_pdb_progress_run_callback (GimpPdbProgress *progress,
+ GimpProgressCommand command,
+ const gchar *text,
+ gdouble value)
+{
+ gdouble retval = 0;
+
+ if (progress->callback_name && ! progress->callback_busy)
+ {
+ GimpValueArray *return_vals;
+
+ progress->callback_busy = TRUE;
+
+ return_vals =
+ gimp_pdb_execute_procedure_by_name (progress->pdb,
+ progress->context,
+ NULL, NULL,
+ progress->callback_name,
+ GIMP_TYPE_INT32, command,
+ G_TYPE_STRING, text,
+ G_TYPE_DOUBLE, value,
+ G_TYPE_NONE);
+
+ if (g_value_get_enum (gimp_value_array_index (return_vals, 0)) !=
+ GIMP_PDB_SUCCESS)
+ {
+ gimp_message (progress->context->gimp, NULL, GIMP_MESSAGE_ERROR,
+ _("Unable to run %s callback. "
+ "The corresponding plug-in may have crashed."),
+ g_type_name (G_TYPE_FROM_INSTANCE (progress)));
+ }
+ else if (gimp_value_array_length (return_vals) >= 2 &&
+ G_VALUE_HOLDS_DOUBLE (gimp_value_array_index (return_vals, 1)))
+ {
+ retval = g_value_get_double (gimp_value_array_index (return_vals, 1));
+ }
+
+ gimp_value_array_unref (return_vals);
+
+ progress->callback_busy = FALSE;
+ }
+
+ return retval;
+}
+
+static GimpProgress *
+gimp_pdb_progress_progress_start (GimpProgress *progress,
+ gboolean cancellable,
+ const gchar *message)
+{
+ GimpPdbProgress *pdb_progress = GIMP_PDB_PROGRESS (progress);
+
+ if (! pdb_progress->active)
+ {
+ gimp_pdb_progress_run_callback (pdb_progress,
+ GIMP_PROGRESS_COMMAND_START,
+ message, 0.0);
+
+ pdb_progress->active = TRUE;
+ pdb_progress->value = 0.0;
+
+ return progress;
+ }
+
+ return NULL;
+}
+
+static void
+gimp_pdb_progress_progress_end (GimpProgress *progress)
+{
+ GimpPdbProgress *pdb_progress = GIMP_PDB_PROGRESS (progress);
+
+ if (pdb_progress->active)
+ {
+ gimp_pdb_progress_run_callback (pdb_progress,
+ GIMP_PROGRESS_COMMAND_END,
+ NULL, 0.0);
+
+ pdb_progress->active = FALSE;
+ pdb_progress->value = 0.0;
+ }
+}
+
+static gboolean
+gimp_pdb_progress_progress_is_active (GimpProgress *progress)
+{
+ GimpPdbProgress *pdb_progress = GIMP_PDB_PROGRESS (progress);
+
+ return pdb_progress->active;
+}
+
+static void
+gimp_pdb_progress_progress_set_text (GimpProgress *progress,
+ const gchar *message)
+{
+ GimpPdbProgress *pdb_progress = GIMP_PDB_PROGRESS (progress);
+
+ if (pdb_progress->active)
+ gimp_pdb_progress_run_callback (pdb_progress,
+ GIMP_PROGRESS_COMMAND_SET_TEXT,
+ message, 0.0);
+}
+
+static void
+gimp_pdb_progress_progress_set_value (GimpProgress *progress,
+ gdouble percentage)
+{
+ GimpPdbProgress *pdb_progress = GIMP_PDB_PROGRESS (progress);
+
+ if (pdb_progress->active)
+ {
+ gimp_pdb_progress_run_callback (pdb_progress,
+ GIMP_PROGRESS_COMMAND_SET_VALUE,
+ NULL, percentage);
+ pdb_progress->value = percentage;
+ }
+}
+
+static gdouble
+gimp_pdb_progress_progress_get_value (GimpProgress *progress)
+{
+ GimpPdbProgress *pdb_progress = GIMP_PDB_PROGRESS (progress);
+
+ return pdb_progress->value;
+
+}
+
+static void
+gimp_pdb_progress_progress_pulse (GimpProgress *progress)
+{
+ GimpPdbProgress *pdb_progress = GIMP_PDB_PROGRESS (progress);
+
+ if (pdb_progress->active)
+ gimp_pdb_progress_run_callback (pdb_progress,
+ GIMP_PROGRESS_COMMAND_PULSE,
+ NULL, 0.0);
+}
+
+static guint32
+gimp_pdb_progress_progress_get_window_id (GimpProgress *progress)
+{
+ GimpPdbProgress *pdb_progress = GIMP_PDB_PROGRESS (progress);
+
+ return (guint32)
+ gimp_pdb_progress_run_callback (pdb_progress,
+ GIMP_PROGRESS_COMMAND_GET_WINDOW,
+ NULL, 0.0);
+}
+
+GimpPdbProgress *
+gimp_pdb_progress_get_by_callback (GimpPdbProgressClass *klass,
+ const gchar *callback_name)
+{
+ GList *list;
+
+ g_return_val_if_fail (GIMP_IS_PDB_PROGRESS_CLASS (klass), NULL);
+ g_return_val_if_fail (callback_name != NULL, NULL);
+
+ for (list = klass->progresses; list; list = g_list_next (list))
+ {
+ GimpPdbProgress *progress = list->data;
+
+ if (! g_strcmp0 (callback_name, progress->callback_name))
+ return progress;
+ }
+
+ return NULL;
+}
diff --git a/app/core/gimppdbprogress.h b/app/core/gimppdbprogress.h
new file mode 100644
index 0000000..4057332
--- /dev/null
+++ b/app/core/gimppdbprogress.h
@@ -0,0 +1,66 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimppdbprogress.h
+ * Copyright (C) 2004 Michael Natterer <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_PDB_PROGRESS_H__
+#define __GIMP_PDB_PROGRESS_H__
+
+G_BEGIN_DECLS
+
+
+#define GIMP_TYPE_PDB_PROGRESS (gimp_pdb_progress_get_type ())
+#define GIMP_PDB_PROGRESS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_PDB_PROGRESS, GimpPdbProgress))
+#define GIMP_PDB_PROGRESS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_PDB_PROGRESS, GimpPdbProgressClass))
+#define GIMP_IS_PDB_PROGRESS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_PDB_PROGRESS))
+#define GIMP_IS_PDB_PROGRESS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_PDB_PROGRESS))
+#define GIMP_PDB_PROGRESS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_PDB_PROGRESS, GimpPdbProgressClass))
+
+
+typedef struct _GimpPdbProgressClass GimpPdbProgressClass;
+
+struct _GimpPdbProgress
+{
+ GObject object;
+
+ gboolean active;
+ gdouble value;
+
+ GimpPDB *pdb;
+ GimpContext *context;
+ gchar *callback_name;
+ gboolean callback_busy;
+};
+
+struct _GimpPdbProgressClass
+{
+ GObjectClass parent_class;
+
+ GList *progresses;
+};
+
+
+GType gimp_pdb_progress_get_type (void) G_GNUC_CONST;
+
+GimpPdbProgress * gimp_pdb_progress_get_by_callback (GimpPdbProgressClass *klass,
+ const gchar *callback_name);
+
+
+G_END_DECLS
+
+#endif /* __GIMP_PDB_PROGRESS_H__ */
diff --git a/app/core/gimppickable-auto-shrink.c b/app/core/gimppickable-auto-shrink.c
new file mode 100644
index 0000000..7ddbee2
--- /dev/null
+++ b/app/core/gimppickable-auto-shrink.c
@@ -0,0 +1,312 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "core-types.h"
+
+#include "gimp.h"
+#include "gimpimage.h"
+#include "gimppickable.h"
+#include "gimppickable-auto-shrink.h"
+
+
+typedef enum
+{
+ AUTO_SHRINK_NOTHING = 0,
+ AUTO_SHRINK_ALPHA = 1,
+ AUTO_SHRINK_COLOR = 2
+} AutoShrinkType;
+
+
+typedef gboolean (* ColorsEqualFunc) (guchar *col1,
+ guchar *col2);
+
+
+/* local function prototypes */
+
+static AutoShrinkType gimp_pickable_guess_bgcolor (GimpPickable *pickable,
+ guchar *color,
+ gint x1,
+ gint x2,
+ gint y1,
+ gint y2);
+static gboolean gimp_pickable_colors_equal (guchar *col1,
+ guchar *col2);
+static gboolean gimp_pickable_colors_alpha (guchar *col1,
+ guchar *col2);
+
+
+/* public functions */
+
+GimpAutoShrink
+gimp_pickable_auto_shrink (GimpPickable *pickable,
+ gint start_x,
+ gint start_y,
+ gint start_width,
+ gint start_height,
+ gint *shrunk_x,
+ gint *shrunk_y,
+ gint *shrunk_width,
+ gint *shrunk_height)
+{
+ GeglBuffer *buffer;
+ GeglRectangle rect;
+ ColorsEqualFunc colors_equal_func;
+ guchar bgcolor[MAX_CHANNELS] = { 0, 0, 0, 0 };
+ guchar *buf = NULL;
+ gint x1, y1, x2, y2;
+ gint width, height;
+ const Babl *format;
+ gint x, y, abort;
+ GimpAutoShrink retval = GIMP_AUTO_SHRINK_UNSHRINKABLE;
+
+ g_return_val_if_fail (GIMP_IS_PICKABLE (pickable), FALSE);
+ g_return_val_if_fail (shrunk_x != NULL, FALSE);
+ g_return_val_if_fail (shrunk_y != NULL, FALSE);
+ g_return_val_if_fail (shrunk_width != NULL, FALSE);
+ g_return_val_if_fail (shrunk_height != NULL, FALSE);
+
+ gimp_set_busy (gimp_pickable_get_image (pickable)->gimp);
+
+ /* You should always keep in mind that x2 and y2 are the NOT the
+ * coordinates of the bottomright corner of the area to be
+ * cropped. They point at the pixel located one to the right and one
+ * to the bottom.
+ */
+
+ gimp_pickable_flush (pickable);
+
+ buffer = gimp_pickable_get_buffer (pickable);
+
+ x1 = MAX (start_x, 0);
+ y1 = MAX (start_y, 0);
+ x2 = MIN (start_x + start_width, gegl_buffer_get_width (buffer));
+ y2 = MIN (start_y + start_height, gegl_buffer_get_height (buffer));
+
+ /* By default, return the start values */
+ *shrunk_x = x1;
+ *shrunk_y = y1;
+ *shrunk_width = x2 - x1;
+ *shrunk_height = y2 - y1;
+
+ format = babl_format ("R'G'B'A u8");
+
+ switch (gimp_pickable_guess_bgcolor (pickable, bgcolor,
+ x1, x2 - 1, y1, y2 - 1))
+ {
+ case AUTO_SHRINK_ALPHA:
+ colors_equal_func = gimp_pickable_colors_alpha;
+ break;
+ case AUTO_SHRINK_COLOR:
+ colors_equal_func = gimp_pickable_colors_equal;
+ break;
+ default:
+ goto FINISH;
+ break;
+ }
+
+ width = x2 - x1;
+ height = y2 - y1;
+
+ /* The following could be optimized further by processing
+ * the smaller side first instead of defaulting to width --Sven
+ */
+
+ buf = g_malloc (MAX (width, height) * 4);
+
+ /* Check how many of the top lines are uniform/transparent. */
+ rect.x = x1;
+ rect.y = 0;
+ rect.width = width;
+ rect.height = 1;
+
+ abort = FALSE;
+ for (y = y1; y < y2 && !abort; y++)
+ {
+ rect.y = y;
+ gegl_buffer_get (buffer, &rect, 1.0, format, buf,
+ GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
+ for (x = 0; x < width && !abort; x++)
+ abort = ! colors_equal_func (bgcolor, buf + x * 4);
+ }
+ if (y == y2 && !abort)
+ {
+ retval = GIMP_AUTO_SHRINK_EMPTY;
+ goto FINISH;
+ }
+ y1 = y - 1;
+
+ /* Check how many of the bottom lines are uniform/transparent. */
+ rect.x = x1;
+ rect.y = 0;
+ rect.width = width;
+ rect.height = 1;
+
+ abort = FALSE;
+ for (y = y2; y > y1 && !abort; y--)
+ {
+ rect.y = y - 1;
+ gegl_buffer_get (buffer, &rect, 1.0, format, buf,
+ GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
+ for (x = 0; x < width && !abort; x++)
+ abort = ! colors_equal_func (bgcolor, buf + x * 4);
+ }
+ y2 = y + 1;
+
+ /* compute a new height for the next operations */
+ height = y2 - y1;
+
+ /* Check how many of the left lines are uniform/transparent. */
+ rect.x = 0;
+ rect.y = y1;
+ rect.width = 1;
+ rect.height = height;
+
+ abort = FALSE;
+ for (x = x1; x < x2 && !abort; x++)
+ {
+ rect.x = x;
+ gegl_buffer_get (buffer, &rect, 1.0, format, buf,
+ GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
+ for (y = 0; y < height && !abort; y++)
+ abort = ! colors_equal_func (bgcolor, buf + y * 4);
+ }
+ x1 = x - 1;
+
+ /* Check how many of the right lines are uniform/transparent. */
+ rect.x = 0;
+ rect.y = y1;
+ rect.width = 1;
+ rect.height = height;
+
+ abort = FALSE;
+ for (x = x2; x > x1 && !abort; x--)
+ {
+ rect.x = x - 1;
+ gegl_buffer_get (buffer, &rect, 1.0, format, buf,
+ GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
+ for (y = 0; y < height && !abort; y++)
+ abort = ! colors_equal_func (bgcolor, buf + y * 4);
+ }
+ x2 = x + 1;
+
+ if (x1 != start_x ||
+ y1 != start_y ||
+ x2 - x1 != start_width ||
+ y2 - y1 != start_height)
+ {
+ *shrunk_x = x1;
+ *shrunk_y = y1;
+ *shrunk_width = x2 - x1;
+ *shrunk_height = y2 - y1;
+
+ retval = GIMP_AUTO_SHRINK_SHRINK;
+ }
+
+ FINISH:
+
+ g_free (buf);
+ gimp_unset_busy (gimp_pickable_get_image (pickable)->gimp);
+
+ return retval;
+}
+
+
+/* private functions */
+
+static AutoShrinkType
+gimp_pickable_guess_bgcolor (GimpPickable *pickable,
+ guchar *color,
+ gint x1,
+ gint x2,
+ gint y1,
+ gint y2)
+{
+ const Babl *format = babl_format ("R'G'B'A u8");
+ guchar tl[4];
+ guchar tr[4];
+ guchar bl[4];
+ guchar br[4];
+ gint i;
+
+ for (i = 0; i < 4; i++)
+ color[i] = 0;
+
+ /* First check if there's transparency to crop. If not, guess the
+ * background-color to see if at least 2 corners are equal.
+ */
+
+ if (! gimp_pickable_get_pixel_at (pickable, x1, y1, format, tl) ||
+ ! gimp_pickable_get_pixel_at (pickable, x1, y2, format, tr) ||
+ ! gimp_pickable_get_pixel_at (pickable, x2, y1, format, bl) ||
+ ! gimp_pickable_get_pixel_at (pickable, x2, y2, format, br))
+ {
+ return AUTO_SHRINK_NOTHING;
+ }
+
+ if ((tl[ALPHA] == 0 && tr[ALPHA] == 0) ||
+ (tl[ALPHA] == 0 && bl[ALPHA] == 0) ||
+ (tr[ALPHA] == 0 && br[ALPHA] == 0) ||
+ (bl[ALPHA] == 0 && br[ALPHA] == 0))
+ {
+ return AUTO_SHRINK_ALPHA;
+ }
+
+ if (gimp_pickable_colors_equal (tl, tr) ||
+ gimp_pickable_colors_equal (tl, bl))
+ {
+ memcpy (color, tl, 4);
+ return AUTO_SHRINK_COLOR;
+ }
+
+ if (gimp_pickable_colors_equal (br, bl) ||
+ gimp_pickable_colors_equal (br, tr))
+ {
+ memcpy (color, br, 4);
+ return AUTO_SHRINK_COLOR;
+ }
+
+ return AUTO_SHRINK_NOTHING;
+}
+
+static gboolean
+gimp_pickable_colors_equal (guchar *col1,
+ guchar *col2)
+{
+ gint b;
+
+ for (b = 0; b < 4; b++)
+ {
+ if (col1[b] != col2[b])
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+gimp_pickable_colors_alpha (guchar *dummy,
+ guchar *col)
+{
+ return (col[ALPHA] == 0);
+}
diff --git a/app/core/gimppickable-auto-shrink.h b/app/core/gimppickable-auto-shrink.h
new file mode 100644
index 0000000..2f2eaa0
--- /dev/null
+++ b/app/core/gimppickable-auto-shrink.h
@@ -0,0 +1,41 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_PICKABLE_AUTO_SHRINK_H__
+#define __GIMP_PICKABLE_AUTO_SHRINK_H__
+
+
+typedef enum
+{
+ GIMP_AUTO_SHRINK_SHRINK,
+ GIMP_AUTO_SHRINK_EMPTY,
+ GIMP_AUTO_SHRINK_UNSHRINKABLE
+} GimpAutoShrink;
+
+
+GimpAutoShrink gimp_pickable_auto_shrink (GimpPickable *pickable,
+ gint x,
+ gint y,
+ gint width,
+ gint height,
+ gint *shrunk_x,
+ gint *shrunk_y,
+ gint *shrunk_width,
+ gint *shrunk_height);
+
+
+#endif /* __GIMP_PICKABLE_AUTO_SHRINK_H__ */
diff --git a/app/core/gimppickable-contiguous-region.cc b/app/core/gimppickable-contiguous-region.cc
new file mode 100644
index 0000000..ea30d0c
--- /dev/null
+++ b/app/core/gimppickable-contiguous-region.cc
@@ -0,0 +1,1123 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+
+#include <cairo.h>
+#include <gegl.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+#include "libgimpcolor/gimpcolor.h"
+#include "libgimpmath/gimpmath.h"
+
+extern "C"
+{
+
+#include "core-types.h"
+
+#include "gegl/gimp-babl.h"
+
+#include "gimp-parallel.h"
+#include "gimp-utils.h" /* GIMP_TIMER */
+#include "gimpasync.h"
+#include "gimplineart.h"
+#include "gimppickable.h"
+#include "gimppickable-contiguous-region.h"
+
+
+#define EPSILON 1e-6
+
+#define PIXELS_PER_THREAD \
+ (/* each thread costs as much as */ 64.0 * 64.0 /* pixels */)
+
+
+typedef struct
+{
+ gint x;
+ gint y;
+ gint level;
+} BorderPixel;
+
+
+/* local function prototypes */
+
+static const Babl * choose_format (GeglBuffer *buffer,
+ GimpSelectCriterion select_criterion,
+ gint *n_components,
+ gboolean *has_alpha);
+static gfloat pixel_difference (const gfloat *col1,
+ const gfloat *col2,
+ gboolean antialias,
+ gfloat threshold,
+ gint n_components,
+ gboolean has_alpha,
+ gboolean select_transparent,
+ GimpSelectCriterion select_criterion);
+static void push_segment (GQueue *segment_queue,
+ gint y,
+ gint old_y,
+ gint start,
+ gint end,
+ gint new_y,
+ gint new_start,
+ gint new_end);
+static void pop_segment (GQueue *segment_queue,
+ gint *y,
+ gint *old_y,
+ gint *start,
+ gint *end);
+static gboolean find_contiguous_segment (const gfloat *col,
+ GeglBuffer *src_buffer,
+ GeglSampler *src_sampler,
+ const GeglRectangle *src_extent,
+ GeglBuffer *mask_buffer,
+ const Babl *src_format,
+ const Babl *mask_format,
+ gint n_components,
+ gboolean has_alpha,
+ gboolean select_transparent,
+ GimpSelectCriterion select_criterion,
+ gboolean antialias,
+ gfloat threshold,
+ gint initial_x,
+ gint initial_y,
+ gint *start,
+ gint *end,
+ gfloat *row);
+static void find_contiguous_region (GeglBuffer *src_buffer,
+ GeglBuffer *mask_buffer,
+ const Babl *format,
+ gint n_components,
+ gboolean has_alpha,
+ gboolean select_transparent,
+ GimpSelectCriterion select_criterion,
+ gboolean antialias,
+ gfloat threshold,
+ gboolean diagonal_neighbors,
+ gint x,
+ gint y,
+ const gfloat *col);
+
+static void line_art_queue_pixel (GQueue *queue,
+ gint x,
+ gint y,
+ gint level);
+
+
+/* public functions */
+
+GeglBuffer *
+gimp_pickable_contiguous_region_by_seed (GimpPickable *pickable,
+ gboolean antialias,
+ gfloat threshold,
+ gboolean select_transparent,
+ GimpSelectCriterion select_criterion,
+ gboolean diagonal_neighbors,
+ gint x,
+ gint y)
+{
+ GeglBuffer *src_buffer;
+ GeglBuffer *mask_buffer;
+ const Babl *format;
+ GeglRectangle extent;
+ gint n_components;
+ gboolean has_alpha;
+ gfloat start_col[MAX_CHANNELS];
+
+ g_return_val_if_fail (GIMP_IS_PICKABLE (pickable), NULL);
+
+ gimp_pickable_flush (pickable);
+ src_buffer = gimp_pickable_get_buffer (pickable);
+
+ format = choose_format (src_buffer, select_criterion,
+ &n_components, &has_alpha);
+ gegl_buffer_sample (src_buffer, x, y, NULL, start_col, format,
+ GEGL_SAMPLER_NEAREST, GEGL_ABYSS_NONE);
+
+ if (has_alpha)
+ {
+ if (select_transparent)
+ {
+ /* don't select transparent regions if the start pixel isn't
+ * fully transparent
+ */
+ if (start_col[n_components - 1] > 0)
+ select_transparent = FALSE;
+ }
+ }
+ else
+ {
+ select_transparent = FALSE;
+ }
+
+ extent = *gegl_buffer_get_extent (src_buffer);
+
+ mask_buffer = gegl_buffer_new (&extent, babl_format ("Y float"));
+
+ if (x >= extent.x && x < (extent.x + extent.width) &&
+ y >= extent.y && y < (extent.y + extent.height))
+ {
+ GIMP_TIMER_START();
+
+ find_contiguous_region (src_buffer, mask_buffer,
+ format, n_components, has_alpha,
+ select_transparent, select_criterion,
+ antialias, threshold, diagonal_neighbors,
+ x, y, start_col);
+
+ GIMP_TIMER_END("foo");
+ }
+
+ return mask_buffer;
+}
+
+GeglBuffer *
+gimp_pickable_contiguous_region_by_color (GimpPickable *pickable,
+ gboolean antialias,
+ gfloat threshold,
+ gboolean select_transparent,
+ GimpSelectCriterion select_criterion,
+ const GimpRGB *color)
+{
+ /* Scan over the pickable's active layer, finding pixels within the
+ * specified threshold from the given R, G, & B values. If
+ * antialiasing is on, use the same antialiasing scheme as in
+ * fuzzy_select. Modify the pickable's mask to reflect the
+ * additional selection
+ */
+ GeglBuffer *src_buffer;
+ GeglBuffer *mask_buffer;
+ const Babl *format;
+ gint n_components;
+ gboolean has_alpha;
+ gfloat start_col[MAX_CHANNELS];
+
+ g_return_val_if_fail (GIMP_IS_PICKABLE (pickable), NULL);
+ g_return_val_if_fail (color != NULL, NULL);
+
+ /* increase the threshold by EPSILON, to allow for conversion errors,
+ * especially when threshold == 0 (see issue #1554.) we need to do this
+ * here, but not in the other functions, since the input color gets converted
+ * to the format in which we perform the comparison through a different path
+ * than the pickable's pixels, which can introduce error.
+ */
+ threshold += EPSILON;
+
+ gimp_pickable_flush (pickable);
+
+ src_buffer = gimp_pickable_get_buffer (pickable);
+
+ format = choose_format (src_buffer, select_criterion,
+ &n_components, &has_alpha);
+
+ gimp_rgba_get_pixel (color, format, start_col);
+
+ if (has_alpha)
+ {
+ if (select_transparent)
+ {
+ /* don't select transparency if "color" isn't fully transparent
+ */
+ if (start_col[n_components - 1] > 0.0)
+ select_transparent = FALSE;
+ }
+ }
+ else
+ {
+ select_transparent = FALSE;
+ }
+
+ mask_buffer = gegl_buffer_new (gegl_buffer_get_extent (src_buffer),
+ babl_format ("Y float"));
+
+ gegl_parallel_distribute_area (
+ gegl_buffer_get_extent (src_buffer), PIXELS_PER_THREAD,
+ [=] (const GeglRectangle *area)
+ {
+ GeglBufferIterator *iter;
+
+ iter = gegl_buffer_iterator_new (src_buffer,
+ area, 0, format,
+ GEGL_ACCESS_READ, GEGL_ABYSS_NONE, 2);
+
+ gegl_buffer_iterator_add (iter, mask_buffer,
+ area, 0, babl_format ("Y float"),
+ GEGL_ACCESS_WRITE, GEGL_ABYSS_NONE);
+
+ while (gegl_buffer_iterator_next (iter))
+ {
+ const gfloat *src = (const gfloat *) iter->items[0].data;
+ gfloat *dest = ( gfloat *) iter->items[1].data;
+ gint count = iter->length;
+
+ while (count--)
+ {
+ /* Find how closely the colors match */
+ *dest = pixel_difference (start_col, src,
+ antialias,
+ threshold,
+ n_components,
+ has_alpha,
+ select_transparent,
+ select_criterion);
+
+ src += n_components;
+ dest += 1;
+ }
+ }
+ });
+
+ return mask_buffer;
+}
+
+GeglBuffer *
+gimp_pickable_contiguous_region_by_line_art (GimpPickable *pickable,
+ GimpLineArt *line_art,
+ gint x,
+ gint y)
+{
+ GeglBuffer *src_buffer;
+ GeglBuffer *mask_buffer;
+ const Babl *format = babl_format ("Y float");
+ gfloat *distmap = NULL;
+ GeglRectangle extent;
+ gboolean free_line_art = FALSE;
+ gboolean filled = FALSE;
+ guchar start_col;
+
+ g_return_val_if_fail (GIMP_IS_PICKABLE (pickable) || GIMP_IS_LINE_ART (line_art), NULL);
+
+ if (! line_art)
+ {
+ /* It is much better experience to pre-compute the line art,
+ * but it may not be always possible (for instance when
+ * selecting/filling through a PDB call).
+ */
+ line_art = gimp_line_art_new ();
+ gimp_line_art_set_input (line_art, pickable);
+ free_line_art = TRUE;
+ }
+
+ src_buffer = gimp_line_art_get (line_art, &distmap);
+ g_return_val_if_fail (src_buffer && distmap, NULL);
+
+ gegl_buffer_sample (src_buffer, x, y, NULL, &start_col, NULL,
+ GEGL_SAMPLER_NEAREST, GEGL_ABYSS_NONE);
+
+ extent = *gegl_buffer_get_extent (src_buffer);
+
+ mask_buffer = gegl_buffer_new (&extent, format);
+
+ if (start_col)
+ {
+ if (start_col == 1)
+ {
+ /* As a special exception, if you fill over a line art pixel, only
+ * fill the pixel and exit
+ */
+ gfloat col = 1.0;
+
+ gegl_buffer_set (mask_buffer, GEGL_RECTANGLE (x, y, 1, 1),
+ 0, format, &col, GEGL_AUTO_ROWSTRIDE);
+ }
+ else /* start_col == 2 */
+ {
+ /* If you fill over a closure pixel, let's fill on all sides
+ * of the start point. Otherwise we get a very weird result
+ * with only a single pixel filled in the middle of an empty
+ * region (since closure pixels are invisible by nature).
+ */
+ gfloat col = 0.0;
+
+ if (x - 1 >= extent.x && x - 1 < extent.x + extent.width &&
+ y - 1 >= extent.y && y - 1 < (extent.y + extent.height))
+ find_contiguous_region (src_buffer, mask_buffer,
+ format, 1, FALSE,
+ FALSE, GIMP_SELECT_CRITERION_COMPOSITE,
+ FALSE, 0.0, FALSE,
+ x - 1, y - 1, &col);
+ if (x - 1 >= extent.x && x - 1 < extent.x + extent.width &&
+ y >= extent.y && y < (extent.y + extent.height))
+ find_contiguous_region (src_buffer, mask_buffer,
+ format, 1, FALSE,
+ FALSE, GIMP_SELECT_CRITERION_COMPOSITE,
+ FALSE, 0.0, FALSE,
+ x - 1, y, &col);
+ if (x - 1 >= extent.x && x - 1 < extent.x + extent.width &&
+ y + 1 >= extent.y && y + 1 < (extent.y + extent.height))
+ find_contiguous_region (src_buffer, mask_buffer,
+ format, 1, FALSE,
+ FALSE, GIMP_SELECT_CRITERION_COMPOSITE,
+ FALSE, 0.0, FALSE,
+ x - 1, y + 1, &col);
+ if (x >= extent.x && x < extent.x + extent.width &&
+ y - 1 >= extent.y && y - 1 < (extent.y + extent.height))
+ find_contiguous_region (src_buffer, mask_buffer,
+ format, 1, FALSE,
+ FALSE, GIMP_SELECT_CRITERION_COMPOSITE,
+ FALSE, 0.0, FALSE,
+ x, y - 1, &col);
+ if (x >= extent.x && x < extent.x + extent.width &&
+ y + 1 >= extent.y && y + 1 < (extent.y + extent.height))
+ find_contiguous_region (src_buffer, mask_buffer,
+ format, 1, FALSE,
+ FALSE, GIMP_SELECT_CRITERION_COMPOSITE,
+ FALSE, 0.0, FALSE,
+ x, y + 1, &col);
+ if (x + 1 >= extent.x && x + 1 < extent.x + extent.width &&
+ y - 1 >= extent.y && y - 1 < (extent.y + extent.height))
+ find_contiguous_region (src_buffer, mask_buffer,
+ format, 1, FALSE,
+ FALSE, GIMP_SELECT_CRITERION_COMPOSITE,
+ FALSE, 0.0, FALSE,
+ x + 1, y - 1, &col);
+ if (x + 1 >= extent.x && x + 1 < extent.x + extent.width &&
+ y >= extent.y && y < (extent.y + extent.height))
+ find_contiguous_region (src_buffer, mask_buffer,
+ format, 1, FALSE,
+ FALSE, GIMP_SELECT_CRITERION_COMPOSITE,
+ FALSE, 0.0, FALSE,
+ x + 1, y, &col);
+ if (x + 1 >= extent.x && x + 1 < extent.x + extent.width &&
+ y + 1 >= extent.y && y + 1 < (extent.y + extent.height))
+ find_contiguous_region (src_buffer, mask_buffer,
+ format, 1, FALSE,
+ FALSE, GIMP_SELECT_CRITERION_COMPOSITE,
+ FALSE, 0.0, FALSE,
+ x + 1, y + 1, &col);
+ filled = TRUE;
+ }
+ }
+ else if (x >= extent.x && x < (extent.x + extent.width) &&
+ y >= extent.y && y < (extent.y + extent.height))
+ {
+ gfloat col = 0.0;
+
+ find_contiguous_region (src_buffer, mask_buffer,
+ format, 1, FALSE,
+ FALSE, GIMP_SELECT_CRITERION_COMPOSITE,
+ FALSE, 0.0, FALSE,
+ x, y, &col);
+ filled = TRUE;
+ }
+
+ if (filled)
+ {
+ GQueue *queue = g_queue_new ();
+ gfloat *mask;
+ gint width = gegl_buffer_get_width (src_buffer);
+ gint height = gegl_buffer_get_height (src_buffer);
+ gint line_art_max_grow;
+ gint nx, ny;
+
+ GIMP_TIMER_START();
+ /* The last step of the line art algorithm is to make sure that
+ * selections does not leave "holes" between its borders and the
+ * line arts, while not stepping over as well.
+ * I used to run the "gegl:watershed-transform" operation to flood
+ * the stroke pixels, but for such simple need, this simple code
+ * is so much faster while producing better results.
+ */
+ mask = g_new (gfloat, width * height);
+ gegl_buffer_get (mask_buffer, NULL, 1.0, NULL,
+ mask, GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
+
+ for (y = 0; y < height; y++)
+ for (x = 0; x < width; x++)
+ {
+ if (distmap[x + y * width] == 1.0)
+ {
+ if (x > 0)
+ {
+ nx = x - 1;
+ if (y > 0)
+ {
+ ny = y - 1;
+ if (mask[nx + ny * width] != 0.0)
+ {
+ line_art_queue_pixel (queue, x, y, 1);
+ continue;
+ }
+ }
+ ny = y;
+ if (mask[nx + ny * width] != 0.0)
+ {
+ line_art_queue_pixel (queue, x, y, 1);
+ continue;
+ }
+ if (y < height - 1)
+ {
+ ny = y + 1;
+ if (mask[nx + ny * width] != 0.0)
+ {
+ line_art_queue_pixel (queue, x, y, 1);
+ continue;
+ }
+ }
+ }
+ if (x < width - 1)
+ {
+ nx = x + 1;
+ if (y > 0)
+ {
+ ny = y - 1;
+ if (mask[nx + ny * width] != 0.0)
+ {
+ line_art_queue_pixel (queue, x, y, 1);
+ continue;
+ }
+ }
+ ny = y;
+ if (mask[nx + ny * width] != 0.0)
+ {
+ line_art_queue_pixel (queue, x, y, 1);
+ continue;
+ }
+ if (y < height - 1)
+ {
+ ny = y + 1;
+ if (mask[nx + ny * width] != 0.0)
+ {
+ line_art_queue_pixel (queue, x, y, 1);
+ continue;
+ }
+ }
+ }
+ nx = x;
+ if (y > 0)
+ {
+ ny = y - 1;
+ if (mask[nx + ny * width] != 0.0)
+ {
+ line_art_queue_pixel (queue, x, y, 1);
+ continue;
+ }
+ }
+ if (y < height - 1)
+ {
+ ny = y + 1;
+ if (mask[nx + ny * width] != 0.0)
+ {
+ line_art_queue_pixel (queue, x, y, 1);
+ continue;
+ }
+ }
+ }
+ }
+
+ g_object_get (line_art,
+ "max-grow", &line_art_max_grow,
+ NULL);
+ while (! g_queue_is_empty (queue))
+ {
+ BorderPixel *c = (BorderPixel *) g_queue_pop_head (queue);
+
+ if (mask[c->x + c->y * width] != 1.0)
+ {
+ mask[c->x + c->y * width] = 1.0;
+ if (c->level >= line_art_max_grow)
+ /* Do not overflood under line arts. */
+ continue;
+ if (c->x > 0)
+ {
+ nx = c->x - 1;
+ if (c->y > 0)
+ {
+ ny = c->y - 1;
+ if (mask[nx + ny * width] == 0.0 &&
+ distmap[nx + ny * width] > distmap[c->x + c->y * width])
+ line_art_queue_pixel (queue, nx, ny, c->level + 1);
+ }
+ ny = c->y;
+ if (mask[nx + ny * width] == 0.0 &&
+ distmap[nx + ny * width] > distmap[c->x + c->y * width])
+ line_art_queue_pixel (queue, nx, ny, c->level + 1);
+ if (c->y < height - 1)
+ {
+ ny = c->y + 1;
+ if (mask[nx + ny * width] == 0.0 &&
+ distmap[nx + ny * width] > distmap[c->x + c->y * width])
+ line_art_queue_pixel (queue, nx, ny, c->level + 1);
+ }
+ }
+ if (c->x < width - 1)
+ {
+ nx = c->x + 1;
+ if (c->y > 0)
+ {
+ ny = c->y - 1;
+ if (mask[nx + ny * width] == 0.0 &&
+ distmap[nx + ny * width] > distmap[c->x + c->y * width])
+ line_art_queue_pixel (queue, nx, ny, c->level + 1);
+ }
+ ny = c->y;
+ if (mask[nx + ny * width] == 0.0 &&
+ distmap[nx + ny * width] > distmap[c->x + c->y * width])
+ line_art_queue_pixel (queue, nx, ny, c->level + 1);
+ if (c->y < height - 1)
+ {
+ ny = c->y + 1;
+ if (mask[nx + ny * width] == 0.0 &&
+ distmap[nx + ny * width] > distmap[c->x + c->y * width])
+ line_art_queue_pixel (queue, nx, ny, c->level + 1);
+ }
+ }
+ nx = c->x;
+ if (c->y > 0)
+ {
+ ny = c->y - 1;
+ if (mask[nx + ny * width] == 0.0 &&
+ distmap[nx + ny * width] > distmap[c->x + c->y * width])
+ line_art_queue_pixel (queue, nx, ny, c->level + 1);
+ }
+ if (c->y < height - 1)
+ {
+ ny = c->y + 1;
+ if (mask[nx + ny * width] == 0.0 &&
+ distmap[nx + ny * width] > distmap[c->x + c->y * width])
+ line_art_queue_pixel (queue, nx, ny, c->level + 1);
+ }
+ }
+ g_free (c);
+ }
+ g_queue_free (queue);
+ gegl_buffer_set (mask_buffer, gegl_buffer_get_extent (mask_buffer),
+ 0, NULL, mask, GEGL_AUTO_ROWSTRIDE);
+ g_free (mask);
+
+ GIMP_TIMER_END("watershed line art");
+ }
+ if (free_line_art)
+ g_clear_object (&line_art);
+
+ return mask_buffer;
+}
+
+/* private functions */
+
+static const Babl *
+choose_format (GeglBuffer *buffer,
+ GimpSelectCriterion select_criterion,
+ gint *n_components,
+ gboolean *has_alpha)
+{
+ const Babl *format = gegl_buffer_get_format (buffer);
+
+ *has_alpha = babl_format_has_alpha (format);
+
+ switch (select_criterion)
+ {
+ case GIMP_SELECT_CRITERION_COMPOSITE:
+ if (babl_format_is_palette (format))
+ format = babl_format ("R'G'B'A float");
+ else
+ format = gimp_babl_format (gimp_babl_format_get_base_type (format),
+ GIMP_PRECISION_FLOAT_GAMMA,
+ *has_alpha);
+ break;
+
+ case GIMP_SELECT_CRITERION_R:
+ case GIMP_SELECT_CRITERION_G:
+ case GIMP_SELECT_CRITERION_B:
+ case GIMP_SELECT_CRITERION_A:
+ format = babl_format ("R'G'B'A float");
+ break;
+
+ case GIMP_SELECT_CRITERION_H:
+ case GIMP_SELECT_CRITERION_S:
+ case GIMP_SELECT_CRITERION_V:
+ format = babl_format ("HSVA float");
+ break;
+
+ case GIMP_SELECT_CRITERION_LCH_L:
+ format = babl_format ("CIE L alpha float");
+ break;
+
+ case GIMP_SELECT_CRITERION_LCH_C:
+ case GIMP_SELECT_CRITERION_LCH_H:
+ format = babl_format ("CIE LCH(ab) alpha float");
+ break;
+
+ default:
+ g_return_val_if_reached (NULL);
+ break;
+ }
+
+ *n_components = babl_format_get_n_components (format);
+
+ return format;
+}
+
+static gfloat
+pixel_difference (const gfloat *col1,
+ const gfloat *col2,
+ gboolean antialias,
+ gfloat threshold,
+ gint n_components,
+ gboolean has_alpha,
+ gboolean select_transparent,
+ GimpSelectCriterion select_criterion)
+{
+ gfloat max = 0.0;
+
+ /* if there is an alpha channel, never select transparent regions */
+ if (! select_transparent && has_alpha && col2[n_components - 1] == 0.0)
+ return 0.0;
+
+ if (select_transparent && has_alpha)
+ {
+ max = fabs (col1[n_components - 1] - col2[n_components - 1]);
+ }
+ else
+ {
+ gfloat diff;
+ gint b;
+
+ if (has_alpha)
+ n_components--;
+
+ switch (select_criterion)
+ {
+ case GIMP_SELECT_CRITERION_COMPOSITE:
+ for (b = 0; b < n_components; b++)
+ {
+ diff = fabs (col1[b] - col2[b]);
+ if (diff > max)
+ max = diff;
+ }
+ break;
+
+ case GIMP_SELECT_CRITERION_R:
+ max = fabs (col1[0] - col2[0]);
+ break;
+
+ case GIMP_SELECT_CRITERION_G:
+ max = fabs (col1[1] - col2[1]);
+ break;
+
+ case GIMP_SELECT_CRITERION_B:
+ max = fabs (col1[2] - col2[2]);
+ break;
+
+ case GIMP_SELECT_CRITERION_A:
+ max = fabs (col1[3] - col2[3]);
+ break;
+
+ case GIMP_SELECT_CRITERION_H:
+ if (col1[1] > EPSILON)
+ {
+ if (col2[1] > EPSILON)
+ {
+ max = fabs (col1[0] - col2[0]);
+ max = MIN (max, 1.0 - max);
+ }
+ else
+ {
+ /* "infinite" difference. anything >> 1 will do. */
+ max = 10.0;
+ }
+ }
+ else
+ {
+ if (col2[1] > EPSILON)
+ {
+ /* "infinite" difference. anything >> 1 will do. */
+ max = 10.0;
+ }
+ else
+ {
+ max = 0.0;
+ }
+ }
+ break;
+
+ case GIMP_SELECT_CRITERION_S:
+ max = fabs (col1[1] - col2[1]);
+ break;
+
+ case GIMP_SELECT_CRITERION_V:
+ max = fabs (col1[2] - col2[2]);
+ break;
+
+ case GIMP_SELECT_CRITERION_LCH_L:
+ max = fabs (col1[0] - col2[0]) / 100.0;
+ break;
+
+ case GIMP_SELECT_CRITERION_LCH_C:
+ max = fabs (col1[1] - col2[1]) / 100.0;
+ break;
+
+ case GIMP_SELECT_CRITERION_LCH_H:
+ if (col1[1] > 100.0 * EPSILON)
+ {
+ if (col2[1] > 100.0 * EPSILON)
+ {
+ max = fabs (col1[2] - col2[2]) / 360.0;
+ max = MIN (max, 1.0 - max);
+ }
+ else
+ {
+ /* "infinite" difference. anything >> 1 will do. */
+ max = 10.0;
+ }
+ }
+ else
+ {
+ if (col2[1] > 100.0 * EPSILON)
+ {
+ /* "infinite" difference. anything >> 1 will do. */
+ max = 10.0;
+ }
+ else
+ {
+ max = 0.0;
+ }
+ }
+ break;
+ }
+ }
+
+ if (antialias && threshold > 0.0)
+ {
+ gfloat aa = 1.5 - (max / threshold);
+
+ if (aa <= 0.0)
+ return 0.0;
+ else if (aa < 0.5)
+ return aa * 2.0;
+ else
+ return 1.0;
+ }
+ else
+ {
+ if (max > threshold)
+ return 0.0;
+ else
+ return 1.0;
+ }
+}
+
+static void
+push_segment (GQueue *segment_queue,
+ gint y,
+ gint old_y,
+ gint start,
+ gint end,
+ gint new_y,
+ gint new_start,
+ gint new_end)
+{
+ /* To avoid excessive memory allocation (y, old_y, start, end) tuples are
+ * stored in interleaved format:
+ *
+ * [y1] [old_y1] [start1] [end1] [y2] [old_y2] [start2] [end2]
+ */
+
+ if (new_y != old_y)
+ {
+ /* If the new segment's y-coordinate is different than the old (source)
+ * segment's y-coordinate, push the entire segment.
+ */
+ g_queue_push_tail (segment_queue, GINT_TO_POINTER (new_y));
+ g_queue_push_tail (segment_queue, GINT_TO_POINTER (y));
+ g_queue_push_tail (segment_queue, GINT_TO_POINTER (new_start));
+ g_queue_push_tail (segment_queue, GINT_TO_POINTER (new_end));
+ }
+ else
+ {
+ /* Otherwise, only push the set-difference between the new segment and
+ * the source segment (since we've already scanned the source segment.)
+ * Note that the `+ 1` and `- 1` terms of the end/start coordinates below
+ * are only necessary when `diagonal_neighbors` is on (and otherwise make
+ * the segments slightly larger than necessary), but, meh...
+ */
+ if (new_start < start)
+ {
+ g_queue_push_tail (segment_queue, GINT_TO_POINTER (new_y));
+ g_queue_push_tail (segment_queue, GINT_TO_POINTER (y));
+ g_queue_push_tail (segment_queue, GINT_TO_POINTER (new_start));
+ g_queue_push_tail (segment_queue, GINT_TO_POINTER (start + 1));
+ }
+
+ if (new_end > end)
+ {
+ g_queue_push_tail (segment_queue, GINT_TO_POINTER (new_y));
+ g_queue_push_tail (segment_queue, GINT_TO_POINTER (y));
+ g_queue_push_tail (segment_queue, GINT_TO_POINTER (end - 1));
+ g_queue_push_tail (segment_queue, GINT_TO_POINTER (new_end));
+ }
+ }
+}
+
+static void
+pop_segment (GQueue *segment_queue,
+ gint *y,
+ gint *old_y,
+ gint *start,
+ gint *end)
+{
+ *y = GPOINTER_TO_INT (g_queue_pop_head (segment_queue));
+ *old_y = GPOINTER_TO_INT (g_queue_pop_head (segment_queue));
+ *start = GPOINTER_TO_INT (g_queue_pop_head (segment_queue));
+ *end = GPOINTER_TO_INT (g_queue_pop_head (segment_queue));
+}
+
+/* #define FETCH_ROW 1 */
+
+static gboolean
+find_contiguous_segment (const gfloat *col,
+ GeglBuffer *src_buffer,
+ GeglSampler *src_sampler,
+ const GeglRectangle *src_extent,
+ GeglBuffer *mask_buffer,
+ const Babl *src_format,
+ const Babl *mask_format,
+ gint n_components,
+ gboolean has_alpha,
+ gboolean select_transparent,
+ GimpSelectCriterion select_criterion,
+ gboolean antialias,
+ gfloat threshold,
+ gint initial_x,
+ gint initial_y,
+ gint *start,
+ gint *end,
+ gfloat *row)
+{
+ gfloat *s;
+ gfloat mask_row_buf[src_extent->width];
+ gfloat *mask_row = mask_row_buf - src_extent->x;
+ gfloat diff;
+
+#ifdef FETCH_ROW
+ gegl_buffer_get (src_buffer, GEGL_RECTANGLE (0, initial_y, width, 1), 1.0,
+ src_format,
+ row, GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
+ s = row + initial_x * n_components;
+#else
+ s = (gfloat *) g_alloca (n_components * sizeof (gfloat));
+
+ gegl_sampler_get (src_sampler,
+ initial_x, initial_y, NULL, s, GEGL_ABYSS_NONE);
+#endif
+
+ diff = pixel_difference (col, s, antialias, threshold,
+ n_components, has_alpha, select_transparent,
+ select_criterion);
+
+ /* check the starting pixel */
+ if (! diff)
+ return FALSE;
+
+ mask_row[initial_x] = diff;
+
+ *start = initial_x - 1;
+#ifdef FETCH_ROW
+ s = row + *start * n_components;
+#endif
+
+ while (*start >= src_extent->x)
+ {
+#ifndef FETCH_ROW
+ gegl_sampler_get (src_sampler,
+ *start, initial_y, NULL, s, GEGL_ABYSS_NONE);
+#endif
+
+ diff = pixel_difference (col, s, antialias, threshold,
+ n_components, has_alpha, select_transparent,
+ select_criterion);
+ if (diff == 0.0)
+ break;
+
+ mask_row[*start] = diff;
+
+ (*start)--;
+#ifdef FETCH_ROW
+ s -= n_components;
+#endif
+ }
+
+ *end = initial_x + 1;
+#ifdef FETCH_ROW
+ s = row + *end * n_components;
+#endif
+
+ while (*end < src_extent->x + src_extent->width)
+ {
+#ifndef FETCH_ROW
+ gegl_sampler_get (src_sampler,
+ *end, initial_y, NULL, s, GEGL_ABYSS_NONE);
+#endif
+
+ diff = pixel_difference (col, s, antialias, threshold,
+ n_components, has_alpha, select_transparent,
+ select_criterion);
+ if (diff == 0.0)
+ break;
+
+ mask_row[*end] = diff;
+
+ (*end)++;
+#ifdef FETCH_ROW
+ s += n_components;
+#endif
+ }
+
+ gegl_buffer_set (mask_buffer, GEGL_RECTANGLE (*start + 1, initial_y,
+ *end - *start - 1, 1),
+ 0, mask_format, &mask_row[*start + 1],
+ GEGL_AUTO_ROWSTRIDE);
+
+ return TRUE;
+}
+
+static void
+find_contiguous_region (GeglBuffer *src_buffer,
+ GeglBuffer *mask_buffer,
+ const Babl *format,
+ gint n_components,
+ gboolean has_alpha,
+ gboolean select_transparent,
+ GimpSelectCriterion select_criterion,
+ gboolean antialias,
+ gfloat threshold,
+ gboolean diagonal_neighbors,
+ gint x,
+ gint y,
+ const gfloat *col)
+{
+ const Babl *mask_format = babl_format ("Y float");
+ GeglSampler *src_sampler;
+ const GeglRectangle *src_extent;
+ gint old_y;
+ gint start, end;
+ gint new_start, new_end;
+ GQueue *segment_queue;
+ gfloat *row = NULL;
+
+ src_extent = gegl_buffer_get_extent (src_buffer);
+
+#ifdef FETCH_ROW
+ row = g_new (gfloat, src_extent->width * n_components);
+#endif
+
+ src_sampler = gegl_buffer_sampler_new (src_buffer,
+ format, GEGL_SAMPLER_NEAREST);
+
+ segment_queue = g_queue_new ();
+
+ push_segment (segment_queue,
+ y, /* dummy values: */ -1, 0, 0,
+ y, x - 1, x + 1);
+
+ do
+ {
+ pop_segment (segment_queue,
+ &y, &old_y, &start, &end);
+
+ for (x = start + 1; x < end; x++)
+ {
+ gfloat val;
+
+ gegl_buffer_get (mask_buffer, GEGL_RECTANGLE (x, y, 1, 1), 1.0,
+ mask_format, &val, GEGL_AUTO_ROWSTRIDE,
+ GEGL_ABYSS_NONE);
+
+ if (val != 0.0)
+ {
+ /* If the current pixel is selected, then we've already visited
+ * the next pixel. (Note that we assume that the maximal image
+ * width is sufficiently low that `x` won't overflow.)
+ */
+ x++;
+ continue;
+ }
+
+ if (! find_contiguous_segment (col,
+ src_buffer, src_sampler, src_extent,
+ mask_buffer,
+ format, mask_format,
+ n_components,
+ has_alpha,
+ select_transparent, select_criterion,
+ antialias, threshold, x, y,
+ &new_start, &new_end,
+ row))
+ continue;
+
+ /* We can skip directly to `new_end + 1` on the next iteration, since
+ * we've just selected all pixels in the range `[x, new_end)`, and
+ * the pixel at `new_end` is above threshold. (Note that we assume
+ * that the maximal image width is sufficiently low that `x` won't
+ * overflow.)
+ */
+ x = new_end;
+
+ if (diagonal_neighbors)
+ {
+ if (new_start >= src_extent->x)
+ new_start--;
+
+ if (new_end < src_extent->x + src_extent->width)
+ new_end++;
+ }
+
+ if (y + 1 < src_extent->y + src_extent->height)
+ {
+ push_segment (segment_queue,
+ y, old_y, start, end,
+ y + 1, new_start, new_end);
+ }
+
+ if (y - 1 >= src_extent->y)
+ {
+ push_segment (segment_queue,
+ y, old_y, start, end,
+ y - 1, new_start, new_end);
+ }
+
+ }
+ }
+ while (! g_queue_is_empty (segment_queue));
+
+ g_queue_free (segment_queue);
+
+ g_object_unref (src_sampler);
+
+#ifdef FETCH_ROW
+ g_free (row);
+#endif
+}
+
+static void
+line_art_queue_pixel (GQueue *queue,
+ gint x,
+ gint y,
+ gint level)
+{
+ BorderPixel *p = g_new (BorderPixel, 1);
+
+ p->x = x;
+ p->y = y;
+ p->level = level;
+
+ g_queue_push_head (queue, p);
+}
+
+} /* extern "C" */
diff --git a/app/core/gimppickable-contiguous-region.h b/app/core/gimppickable-contiguous-region.h
new file mode 100644
index 0000000..26cbe74
--- /dev/null
+++ b/app/core/gimppickable-contiguous-region.h
@@ -0,0 +1,43 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_PICKABLE_CONTIGUOUS_REGION_H__
+#define __GIMP_PICKABLE_CONTIGUOUS_REGION_H__
+
+
+GeglBuffer * gimp_pickable_contiguous_region_by_seed (GimpPickable *pickable,
+ gboolean antialias,
+ gfloat threshold,
+ gboolean select_transparent,
+ GimpSelectCriterion select_criterion,
+ gboolean diagonal_neighbors,
+ gint x,
+ gint y);
+
+GeglBuffer * gimp_pickable_contiguous_region_by_color (GimpPickable *pickable,
+ gboolean antialias,
+ gfloat threshold,
+ gboolean select_transparent,
+ GimpSelectCriterion select_criterion,
+ const GimpRGB *color);
+
+GeglBuffer * gimp_pickable_contiguous_region_by_line_art (GimpPickable *pickable,
+ GimpLineArt *line_art,
+ gint x,
+ gint y);
+
+#endif /* __GIMP_PICKABLE_CONTIGUOUS_REGION_H__ */
diff --git a/app/core/gimppickable.c b/app/core/gimppickable.c
new file mode 100644
index 0000000..2949def
--- /dev/null
+++ b/app/core/gimppickable.c
@@ -0,0 +1,378 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimppickable.c
+ * Copyright (C) 2004 Michael Natterer <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+/* This file contains an interface for pixel objects that their color at
+ * a given position can be picked. Also included is a utility for
+ * sampling an average area (which uses the implemented picking
+ * functions).
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <cairo.h>
+#include <gegl.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+#include "libgimpcolor/gimpcolor.h"
+#include "libgimpmath/gimpmath.h"
+
+#include "core-types.h"
+
+#include "gimpobject.h"
+#include "gimpimage.h"
+#include "gimppickable.h"
+
+
+/* local function prototypes */
+
+static void gimp_pickable_real_get_pixel_average (GimpPickable *pickable,
+ const GeglRectangle *rect,
+ const Babl *format,
+ gpointer pixel);
+
+
+G_DEFINE_INTERFACE (GimpPickable, gimp_pickable, GIMP_TYPE_OBJECT)
+
+
+/* private functions */
+
+
+static void
+gimp_pickable_default_init (GimpPickableInterface *iface)
+{
+ iface->get_pixel_average = gimp_pickable_real_get_pixel_average;
+
+ g_object_interface_install_property (iface,
+ g_param_spec_object ("buffer",
+ NULL, NULL,
+ GEGL_TYPE_BUFFER,
+ GIMP_PARAM_READABLE));
+}
+
+static void
+gimp_pickable_real_get_pixel_average (GimpPickable *pickable,
+ const GeglRectangle *rect,
+ const Babl *format,
+ gpointer pixel)
+{
+ const Babl *average_format = babl_format ("RaGaBaA double");
+ gdouble average[4] = {};
+ gint n = 0;
+ gint x;
+ gint y;
+ gint c;
+
+ for (y = rect->y; y < rect->y + rect->height; y++)
+ {
+ for (x = rect->x; x < rect->x + rect->width; x++)
+ {
+ gdouble sample[4];
+
+ if (gimp_pickable_get_pixel_at (pickable,
+ x, y, average_format, sample))
+ {
+ for (c = 0; c < 4; c++)
+ average[c] += sample[c];
+
+ n++;
+ }
+ }
+ }
+
+ if (n > 0)
+ {
+ for (c = 0; c < 4; c++)
+ average[c] /= n;
+ }
+
+ babl_process (babl_fish (average_format, format), average, pixel, 1);
+}
+
+
+/* public functions */
+
+
+void
+gimp_pickable_flush (GimpPickable *pickable)
+{
+ GimpPickableInterface *pickable_iface;
+
+ g_return_if_fail (GIMP_IS_PICKABLE (pickable));
+
+ pickable_iface = GIMP_PICKABLE_GET_INTERFACE (pickable);
+
+ if (pickable_iface->flush)
+ pickable_iface->flush (pickable);
+}
+
+GimpImage *
+gimp_pickable_get_image (GimpPickable *pickable)
+{
+ GimpPickableInterface *pickable_iface;
+
+ g_return_val_if_fail (GIMP_IS_PICKABLE (pickable), NULL);
+
+ pickable_iface = GIMP_PICKABLE_GET_INTERFACE (pickable);
+
+ if (pickable_iface->get_image)
+ return pickable_iface->get_image (pickable);
+
+ return NULL;
+}
+
+const Babl *
+gimp_pickable_get_format (GimpPickable *pickable)
+{
+ GimpPickableInterface *pickable_iface;
+
+ g_return_val_if_fail (GIMP_IS_PICKABLE (pickable), NULL);
+
+ pickable_iface = GIMP_PICKABLE_GET_INTERFACE (pickable);
+
+ if (pickable_iface->get_format)
+ return pickable_iface->get_format (pickable);
+
+ return NULL;
+}
+
+const Babl *
+gimp_pickable_get_format_with_alpha (GimpPickable *pickable)
+{
+ GimpPickableInterface *pickable_iface;
+
+ g_return_val_if_fail (GIMP_IS_PICKABLE (pickable), NULL);
+
+ pickable_iface = GIMP_PICKABLE_GET_INTERFACE (pickable);
+
+ if (pickable_iface->get_format_with_alpha)
+ return pickable_iface->get_format_with_alpha (pickable);
+
+ return NULL;
+}
+
+GeglBuffer *
+gimp_pickable_get_buffer (GimpPickable *pickable)
+{
+ GimpPickableInterface *pickable_iface;
+
+ g_return_val_if_fail (GIMP_IS_PICKABLE (pickable), NULL);
+
+ pickable_iface = GIMP_PICKABLE_GET_INTERFACE (pickable);
+
+ if (pickable_iface->get_buffer)
+ return pickable_iface->get_buffer (pickable);
+
+ return NULL;
+}
+
+gboolean
+gimp_pickable_get_pixel_at (GimpPickable *pickable,
+ gint x,
+ gint y,
+ const Babl *format,
+ gpointer pixel)
+{
+ GimpPickableInterface *pickable_iface;
+
+ g_return_val_if_fail (GIMP_IS_PICKABLE (pickable), FALSE);
+ g_return_val_if_fail (pixel != NULL, FALSE);
+
+ if (! format)
+ format = gimp_pickable_get_format (pickable);
+
+ pickable_iface = GIMP_PICKABLE_GET_INTERFACE (pickable);
+
+ if (pickable_iface->get_pixel_at)
+ return pickable_iface->get_pixel_at (pickable, x, y, format, pixel);
+
+ return FALSE;
+}
+
+void
+gimp_pickable_get_pixel_average (GimpPickable *pickable,
+ const GeglRectangle *rect,
+ const Babl *format,
+ gpointer pixel)
+{
+ GimpPickableInterface *pickable_iface;
+
+ g_return_if_fail (GIMP_IS_PICKABLE (pickable));
+ g_return_if_fail (rect != NULL);
+ g_return_if_fail (pixel != NULL);
+
+ if (! format)
+ format = gimp_pickable_get_format (pickable);
+
+ pickable_iface = GIMP_PICKABLE_GET_INTERFACE (pickable);
+
+ if (pickable_iface->get_pixel_average)
+ pickable_iface->get_pixel_average (pickable, rect, format, pixel);
+ else
+ memset (pixel, 0, babl_format_get_bytes_per_pixel (format));
+}
+
+gboolean
+gimp_pickable_get_color_at (GimpPickable *pickable,
+ gint x,
+ gint y,
+ GimpRGB *color)
+{
+ gdouble pixel[4];
+
+ g_return_val_if_fail (GIMP_IS_PICKABLE (pickable), FALSE);
+ g_return_val_if_fail (color != NULL, FALSE);
+
+ if (! gimp_pickable_get_pixel_at (pickable, x, y, NULL, pixel))
+ return FALSE;
+
+ gimp_pickable_pixel_to_srgb (pickable, NULL, pixel, color);
+
+ return TRUE;
+}
+
+gdouble
+gimp_pickable_get_opacity_at (GimpPickable *pickable,
+ gint x,
+ gint y)
+{
+ GimpPickableInterface *pickable_iface;
+
+ g_return_val_if_fail (GIMP_IS_PICKABLE (pickable), GIMP_OPACITY_TRANSPARENT);
+
+ pickable_iface = GIMP_PICKABLE_GET_INTERFACE (pickable);
+
+ if (pickable_iface->get_opacity_at)
+ return pickable_iface->get_opacity_at (pickable, x, y);
+
+ return GIMP_OPACITY_TRANSPARENT;
+}
+
+void
+gimp_pickable_pixel_to_srgb (GimpPickable *pickable,
+ const Babl *format,
+ gpointer pixel,
+ GimpRGB *color)
+{
+ GimpPickableInterface *pickable_iface;
+
+ g_return_if_fail (GIMP_IS_PICKABLE (pickable));
+ g_return_if_fail (pixel != NULL);
+ g_return_if_fail (color != NULL);
+
+ if (! format)
+ format = gimp_pickable_get_format (pickable);
+
+ pickable_iface = GIMP_PICKABLE_GET_INTERFACE (pickable);
+
+ if (pickable_iface->pixel_to_srgb)
+ {
+ pickable_iface->pixel_to_srgb (pickable, format, pixel, color);
+ }
+ else
+ {
+ gimp_rgba_set_pixel (color, format, pixel);
+ }
+}
+
+void
+gimp_pickable_srgb_to_pixel (GimpPickable *pickable,
+ const GimpRGB *color,
+ const Babl *format,
+ gpointer pixel)
+{
+ GimpPickableInterface *pickable_iface;
+
+ g_return_if_fail (GIMP_IS_PICKABLE (pickable));
+ g_return_if_fail (color != NULL);
+ g_return_if_fail (pixel != NULL);
+
+ if (! format)
+ format = gimp_pickable_get_format (pickable);
+
+ pickable_iface = GIMP_PICKABLE_GET_INTERFACE (pickable);
+
+ if (pickable_iface->srgb_to_pixel)
+ {
+ pickable_iface->srgb_to_pixel (pickable, color, format, pixel);
+ }
+ else
+ {
+ gimp_rgba_get_pixel (color, format, pixel);
+ }
+}
+
+void
+gimp_pickable_srgb_to_image_color (GimpPickable *pickable,
+ const GimpRGB *color,
+ GimpRGB *image_color)
+{
+ g_return_if_fail (GIMP_IS_PICKABLE (pickable));
+ g_return_if_fail (color != NULL);
+ g_return_if_fail (image_color != NULL);
+
+ gimp_pickable_srgb_to_pixel (pickable,
+ color,
+ babl_format ("R'G'B'A double"),
+ image_color);
+}
+
+gboolean
+gimp_pickable_pick_color (GimpPickable *pickable,
+ gint x,
+ gint y,
+ gboolean sample_average,
+ gdouble average_radius,
+ gpointer pixel,
+ GimpRGB *color)
+{
+ const Babl *format;
+ gdouble sample[4];
+
+ g_return_val_if_fail (GIMP_IS_PICKABLE (pickable), FALSE);
+ g_return_val_if_fail (color != NULL, FALSE);
+
+ format = gimp_pickable_get_format (pickable);
+
+ if (! gimp_pickable_get_pixel_at (pickable, x, y, format, sample))
+ return FALSE;
+
+ if (pixel)
+ memcpy (pixel, sample, babl_format_get_bytes_per_pixel (format));
+
+ if (sample_average)
+ {
+ gint radius = floor (average_radius);
+
+ format = babl_format ("RaGaBaA double");
+
+ gimp_pickable_get_pixel_average (pickable,
+ GEGL_RECTANGLE (x - radius,
+ y - radius,
+ 2 * radius + 1,
+ 2 * radius + 1),
+ format, sample);
+ }
+
+ gimp_pickable_pixel_to_srgb (pickable, format, sample, color);
+
+ return TRUE;
+}
diff --git a/app/core/gimppickable.h b/app/core/gimppickable.h
new file mode 100644
index 0000000..c8c77c9
--- /dev/null
+++ b/app/core/gimppickable.h
@@ -0,0 +1,110 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-1997 Spencer Kimball and Peter Mattis
+ *
+ * gimppickable.h
+ * Copyright (C) 2004 Michael Natterer <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_PICKABLE_H__
+#define __GIMP_PICKABLE_H__
+
+
+#define GIMP_TYPE_PICKABLE (gimp_pickable_get_type ())
+#define GIMP_IS_PICKABLE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_PICKABLE))
+#define GIMP_PICKABLE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_PICKABLE, GimpPickable))
+#define GIMP_PICKABLE_GET_INTERFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), GIMP_TYPE_PICKABLE, GimpPickableInterface))
+
+
+typedef struct _GimpPickableInterface GimpPickableInterface;
+
+struct _GimpPickableInterface
+{
+ GTypeInterface base_iface;
+
+ /* virtual functions */
+ void (* flush) (GimpPickable *pickable);
+ GimpImage * (* get_image) (GimpPickable *pickable);
+ const Babl * (* get_format) (GimpPickable *pickable);
+ const Babl * (* get_format_with_alpha) (GimpPickable *pickable);
+ GeglBuffer * (* get_buffer) (GimpPickable *pickable);
+ gboolean (* get_pixel_at) (GimpPickable *pickable,
+ gint x,
+ gint y,
+ const Babl *format,
+ gpointer pixel);
+ gdouble (* get_opacity_at) (GimpPickable *pickable,
+ gint x,
+ gint y);
+ void (* get_pixel_average) (GimpPickable *pickable,
+ const GeglRectangle *rect,
+ const Babl *format,
+ gpointer pixel);
+ void (* pixel_to_srgb) (GimpPickable *pickable,
+ const Babl *format,
+ gpointer pixel,
+ GimpRGB *color);
+ void (* srgb_to_pixel) (GimpPickable *pickable,
+ const GimpRGB *color,
+ const Babl *format,
+ gpointer pixel);
+};
+
+
+GType gimp_pickable_get_type (void) G_GNUC_CONST;
+
+void gimp_pickable_flush (GimpPickable *pickable);
+GimpImage * gimp_pickable_get_image (GimpPickable *pickable);
+const Babl * gimp_pickable_get_format (GimpPickable *pickable);
+const Babl * gimp_pickable_get_format_with_alpha (GimpPickable *pickable);
+GeglBuffer * gimp_pickable_get_buffer (GimpPickable *pickable);
+gboolean gimp_pickable_get_pixel_at (GimpPickable *pickable,
+ gint x,
+ gint y,
+ const Babl *format,
+ gpointer pixel);
+gboolean gimp_pickable_get_color_at (GimpPickable *pickable,
+ gint x,
+ gint y,
+ GimpRGB *color);
+gdouble gimp_pickable_get_opacity_at (GimpPickable *pickable,
+ gint x,
+ gint y);
+void gimp_pickable_get_pixel_average (GimpPickable *pickable,
+ const GeglRectangle *rect,
+ const Babl *format,
+ gpointer pixel);
+void gimp_pickable_pixel_to_srgb (GimpPickable *pickable,
+ const Babl *format,
+ gpointer pixel,
+ GimpRGB *color);
+void gimp_pickable_srgb_to_pixel (GimpPickable *pickable,
+ const GimpRGB *color,
+ const Babl *format,
+ gpointer pixel);
+void gimp_pickable_srgb_to_image_color (GimpPickable *pickable,
+ const GimpRGB *color,
+ GimpRGB *image_color);
+
+gboolean gimp_pickable_pick_color (GimpPickable *pickable,
+ gint x,
+ gint y,
+ gboolean sample_average,
+ gdouble average_radius,
+ gpointer pixel,
+ GimpRGB *color);
+
+
+#endif /* __GIMP_PICKABLE_H__ */
diff --git a/app/core/gimpprogress.c b/app/core/gimpprogress.c
new file mode 100644
index 0000000..6b8bf46
--- /dev/null
+++ b/app/core/gimpprogress.c
@@ -0,0 +1,266 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpprogress.c
+ * Copyright (C) 2004 Michael Natterer <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gio/gio.h>
+#include <gegl.h>
+
+#include "core-types.h"
+
+#include "gimp.h"
+#include "gimpmarshal.h"
+#include "gimpprogress.h"
+
+#include "gimp-intl.h"
+
+
+enum
+{
+ CANCEL,
+ LAST_SIGNAL
+};
+
+
+G_DEFINE_INTERFACE (GimpProgress, gimp_progress, G_TYPE_OBJECT)
+
+
+static guint progress_signals[LAST_SIGNAL] = { 0 };
+
+
+/* private functions */
+
+
+static void
+gimp_progress_default_init (GimpProgressInterface *progress_iface)
+{
+ progress_signals[CANCEL] =
+ g_signal_new ("cancel",
+ G_TYPE_FROM_INTERFACE (progress_iface),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpProgressInterface, cancel),
+ NULL, NULL,
+ gimp_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+}
+
+
+/* public functions */
+
+
+GimpProgress *
+gimp_progress_start (GimpProgress *progress,
+ gboolean cancellable,
+ const gchar *format,
+ ...)
+{
+ GimpProgressInterface *progress_iface;
+
+ g_return_val_if_fail (GIMP_IS_PROGRESS (progress), NULL);
+ g_return_val_if_fail (format != NULL, NULL);
+
+ progress_iface = GIMP_PROGRESS_GET_INTERFACE (progress);
+
+ if (progress_iface->start)
+ {
+ GimpProgress *ret;
+ va_list args;
+ gchar *text;
+
+ va_start (args, format);
+ text = g_strdup_vprintf (format, args);
+ va_end (args);
+
+ ret = progress_iface->start (progress, cancellable, text);
+
+ g_free (text);
+
+ return ret;
+ }
+
+ return NULL;
+}
+
+void
+gimp_progress_end (GimpProgress *progress)
+{
+ GimpProgressInterface *progress_iface;
+
+ g_return_if_fail (GIMP_IS_PROGRESS (progress));
+
+ progress_iface = GIMP_PROGRESS_GET_INTERFACE (progress);
+
+ if (progress_iface->end)
+ progress_iface->end (progress);
+}
+
+gboolean
+gimp_progress_is_active (GimpProgress *progress)
+{
+ GimpProgressInterface *progress_iface;
+
+ g_return_val_if_fail (GIMP_IS_PROGRESS (progress), FALSE);
+
+ progress_iface = GIMP_PROGRESS_GET_INTERFACE (progress);
+
+ if (progress_iface->is_active)
+ return progress_iface->is_active (progress);
+
+ return FALSE;
+}
+
+void
+gimp_progress_set_text (GimpProgress *progress,
+ const gchar *format,
+ ...)
+{
+ va_list args;
+ gchar *message;
+
+ g_return_if_fail (GIMP_IS_PROGRESS (progress));
+ g_return_if_fail (format != NULL);
+
+ va_start (args, format);
+ message = g_strdup_vprintf (format, args);
+ va_end (args);
+
+ gimp_progress_set_text_literal (progress, message);
+
+ g_free (message);
+}
+
+void
+gimp_progress_set_text_literal (GimpProgress *progress,
+ const gchar *message)
+{
+ GimpProgressInterface *progress_iface;
+
+ g_return_if_fail (GIMP_IS_PROGRESS (progress));
+ g_return_if_fail (message != NULL);
+
+ progress_iface = GIMP_PROGRESS_GET_INTERFACE (progress);
+
+ if (progress_iface->set_text)
+ progress_iface->set_text (progress, message);
+}
+
+void
+gimp_progress_set_value (GimpProgress *progress,
+ gdouble percentage)
+{
+ GimpProgressInterface *progress_iface;
+
+ g_return_if_fail (GIMP_IS_PROGRESS (progress));
+
+ percentage = CLAMP (percentage, 0.0, 1.0);
+
+ progress_iface = GIMP_PROGRESS_GET_INTERFACE (progress);
+
+ if (progress_iface->set_value)
+ progress_iface->set_value (progress, percentage);
+}
+
+gdouble
+gimp_progress_get_value (GimpProgress *progress)
+{
+ GimpProgressInterface *progress_iface;
+
+ g_return_val_if_fail (GIMP_IS_PROGRESS (progress), 0.0);
+
+ progress_iface = GIMP_PROGRESS_GET_INTERFACE (progress);
+
+ if (progress_iface->get_value)
+ return progress_iface->get_value (progress);
+
+ return 0.0;
+}
+
+void
+gimp_progress_pulse (GimpProgress *progress)
+{
+ GimpProgressInterface *progress_iface;
+
+ g_return_if_fail (GIMP_IS_PROGRESS (progress));
+
+ progress_iface = GIMP_PROGRESS_GET_INTERFACE (progress);
+
+ if (progress_iface->pulse)
+ progress_iface->pulse (progress);
+}
+
+guint32
+gimp_progress_get_window_id (GimpProgress *progress)
+{
+ GimpProgressInterface *progress_iface;
+
+ g_return_val_if_fail (GIMP_IS_PROGRESS (progress), 0);
+
+ progress_iface = GIMP_PROGRESS_GET_INTERFACE (progress);
+
+ if (progress_iface->get_window_id)
+ return progress_iface->get_window_id (progress);
+
+ return 0;
+}
+
+gboolean
+gimp_progress_message (GimpProgress *progress,
+ Gimp *gimp,
+ GimpMessageSeverity severity,
+ const gchar *domain,
+ const gchar *message)
+{
+ GimpProgressInterface *progress_iface;
+
+ g_return_val_if_fail (GIMP_IS_PROGRESS (progress), FALSE);
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), FALSE);
+ g_return_val_if_fail (domain != NULL, FALSE);
+ g_return_val_if_fail (message != NULL, FALSE);
+
+ progress_iface = GIMP_PROGRESS_GET_INTERFACE (progress);
+
+ if (progress_iface->message)
+ return progress_iface->message (progress, gimp, severity, domain, message);
+
+ return FALSE;
+}
+
+void
+gimp_progress_cancel (GimpProgress *progress)
+{
+ g_return_if_fail (GIMP_IS_PROGRESS (progress));
+
+ g_signal_emit (progress, progress_signals[CANCEL], 0);
+}
+
+void
+gimp_progress_update_and_flush (gint min,
+ gint max,
+ gint current,
+ gpointer data)
+{
+ gimp_progress_set_value (GIMP_PROGRESS (data),
+ (gdouble) (current - min) / (gdouble) (max - min));
+
+ while (g_main_context_pending (NULL))
+ g_main_context_iteration (NULL, TRUE);
+}
diff --git a/app/core/gimpprogress.h b/app/core/gimpprogress.h
new file mode 100644
index 0000000..488f050
--- /dev/null
+++ b/app/core/gimpprogress.h
@@ -0,0 +1,99 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpprogress.h
+ * Copyright (C) 2004 Michael Natterer <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_PROGRESS_H__
+#define __GIMP_PROGRESS_H__
+
+
+#define GIMP_TYPE_PROGRESS (gimp_progress_get_type ())
+#define GIMP_IS_PROGRESS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_PROGRESS))
+#define GIMP_PROGRESS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_PROGRESS, GimpProgress))
+#define GIMP_PROGRESS_GET_INTERFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), GIMP_TYPE_PROGRESS, GimpProgressInterface))
+
+
+typedef struct _GimpProgressInterface GimpProgressInterface;
+
+struct _GimpProgressInterface
+{
+ GTypeInterface base_iface;
+
+ /* virtual functions */
+ GimpProgress * (* start) (GimpProgress *progress,
+ gboolean cancellable,
+ const gchar *message);
+ void (* end) (GimpProgress *progress);
+ gboolean (* is_active) (GimpProgress *progress);
+
+ void (* set_text) (GimpProgress *progress,
+ const gchar *message);
+ void (* set_value) (GimpProgress *progress,
+ gdouble percentage);
+ gdouble (* get_value) (GimpProgress *progress);
+ void (* pulse) (GimpProgress *progress);
+
+ guint32 (* get_window_id) (GimpProgress *progress);
+
+ gboolean (* message) (GimpProgress *progress,
+ Gimp *gimp,
+ GimpMessageSeverity severity,
+ const gchar *domain,
+ const gchar *message);
+
+ /* signals */
+ void (* cancel) (GimpProgress *progress);
+};
+
+
+GType gimp_progress_get_type (void) G_GNUC_CONST;
+
+GimpProgress * gimp_progress_start (GimpProgress *progress,
+ gboolean cancellable,
+ const gchar *format,
+ ...) G_GNUC_PRINTF (3, 4);
+void gimp_progress_end (GimpProgress *progress);
+gboolean gimp_progress_is_active (GimpProgress *progress);
+
+void gimp_progress_set_text (GimpProgress *progress,
+ const gchar *format,
+ ...) G_GNUC_PRINTF (2, 3);
+void gimp_progress_set_text_literal (GimpProgress *progress,
+ const gchar *message);
+void gimp_progress_set_value (GimpProgress *progress,
+ gdouble percentage);
+gdouble gimp_progress_get_value (GimpProgress *progress);
+void gimp_progress_pulse (GimpProgress *progress);
+
+guint32 gimp_progress_get_window_id (GimpProgress *progress);
+
+gboolean gimp_progress_message (GimpProgress *progress,
+ Gimp *gimp,
+ GimpMessageSeverity severity,
+ const gchar *domain,
+ const gchar *message);
+
+void gimp_progress_cancel (GimpProgress *progress);
+
+void gimp_progress_update_and_flush (gint min,
+ gint max,
+ gint current,
+ gpointer data);
+
+
+#endif /* __GIMP_PROGRESS_H__ */
diff --git a/app/core/gimpprojectable.c b/app/core/gimpprojectable.c
new file mode 100644
index 0000000..8814c98
--- /dev/null
+++ b/app/core/gimpprojectable.c
@@ -0,0 +1,262 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpprojectable.c
+ * Copyright (C) 2008 Michael Natterer <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "core-types.h"
+
+#include "gimpmarshal.h"
+#include "gimpprojectable.h"
+#include "gimpviewable.h"
+
+
+enum
+{
+ INVALIDATE,
+ FLUSH,
+ STRUCTURE_CHANGED,
+ BOUNDS_CHANGED,
+ LAST_SIGNAL
+};
+
+
+G_DEFINE_INTERFACE (GimpProjectable, gimp_projectable, GIMP_TYPE_VIEWABLE)
+
+
+static guint projectable_signals[LAST_SIGNAL] = { 0 };
+
+
+/* private functions */
+
+
+static void
+gimp_projectable_default_init (GimpProjectableInterface *iface)
+{
+ projectable_signals[INVALIDATE] =
+ g_signal_new ("invalidate",
+ G_TYPE_FROM_CLASS (iface),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpProjectableInterface, invalidate),
+ NULL, NULL,
+ gimp_marshal_VOID__INT_INT_INT_INT,
+ G_TYPE_NONE, 4,
+ G_TYPE_INT,
+ G_TYPE_INT,
+ G_TYPE_INT,
+ G_TYPE_INT);
+
+ projectable_signals[FLUSH] =
+ g_signal_new ("flush",
+ G_TYPE_FROM_CLASS (iface),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpProjectableInterface, flush),
+ NULL, NULL,
+ gimp_marshal_VOID__BOOLEAN,
+ G_TYPE_NONE, 1,
+ G_TYPE_BOOLEAN);
+
+ projectable_signals[STRUCTURE_CHANGED] =
+ g_signal_new ("structure-changed",
+ G_TYPE_FROM_CLASS (iface),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpProjectableInterface, structure_changed),
+ NULL, NULL,
+ gimp_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ projectable_signals[BOUNDS_CHANGED] =
+ g_signal_new ("bounds-changed",
+ G_TYPE_FROM_CLASS (iface),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpProjectableInterface, bounds_changed),
+ NULL, NULL,
+ gimp_marshal_VOID__INT_INT,
+ G_TYPE_NONE, 2,
+ G_TYPE_INT,
+ G_TYPE_INT);
+}
+
+
+/* public functions */
+
+void
+gimp_projectable_invalidate (GimpProjectable *projectable,
+ gint x,
+ gint y,
+ gint width,
+ gint height)
+{
+ g_return_if_fail (GIMP_IS_PROJECTABLE (projectable));
+
+ g_signal_emit (projectable, projectable_signals[INVALIDATE], 0,
+ x, y, width, height);
+}
+
+void
+gimp_projectable_flush (GimpProjectable *projectable,
+ gboolean preview_invalidated)
+{
+ g_return_if_fail (GIMP_IS_PROJECTABLE (projectable));
+
+ g_signal_emit (projectable, projectable_signals[FLUSH], 0,
+ preview_invalidated);
+}
+
+void
+gimp_projectable_structure_changed (GimpProjectable *projectable)
+{
+ g_return_if_fail (GIMP_IS_PROJECTABLE (projectable));
+
+ g_signal_emit (projectable, projectable_signals[STRUCTURE_CHANGED], 0);
+}
+
+void
+gimp_projectable_bounds_changed (GimpProjectable *projectable,
+ gint old_x,
+ gint old_y)
+{
+ g_return_if_fail (GIMP_IS_PROJECTABLE (projectable));
+
+ g_signal_emit (projectable, projectable_signals[BOUNDS_CHANGED], 0,
+ old_x, old_y);
+}
+
+GimpImage *
+gimp_projectable_get_image (GimpProjectable *projectable)
+{
+ GimpProjectableInterface *iface;
+
+ g_return_val_if_fail (GIMP_IS_PROJECTABLE (projectable), NULL);
+
+ iface = GIMP_PROJECTABLE_GET_INTERFACE (projectable);
+
+ if (iface->get_image)
+ return iface->get_image (projectable);
+
+ return NULL;
+}
+
+const Babl *
+gimp_projectable_get_format (GimpProjectable *projectable)
+{
+ GimpProjectableInterface *iface;
+
+ g_return_val_if_fail (GIMP_IS_PROJECTABLE (projectable), NULL);
+
+ iface = GIMP_PROJECTABLE_GET_INTERFACE (projectable);
+
+ if (iface->get_format)
+ return iface->get_format (projectable);
+
+ return 0;
+}
+
+void
+gimp_projectable_get_offset (GimpProjectable *projectable,
+ gint *x,
+ gint *y)
+{
+ GimpProjectableInterface *iface;
+
+ g_return_if_fail (GIMP_IS_PROJECTABLE (projectable));
+ g_return_if_fail (x != NULL);
+ g_return_if_fail (y != NULL);
+
+ iface = GIMP_PROJECTABLE_GET_INTERFACE (projectable);
+
+ *x = 0;
+ *y = 0;
+
+ if (iface->get_offset)
+ iface->get_offset (projectable, x, y);
+}
+
+GeglRectangle
+gimp_projectable_get_bounding_box (GimpProjectable *projectable)
+{
+ GimpProjectableInterface *iface;
+ GeglRectangle result = {};
+
+ g_return_val_if_fail (GIMP_IS_PROJECTABLE (projectable), result);
+
+ iface = GIMP_PROJECTABLE_GET_INTERFACE (projectable);
+
+ if (iface->get_bounding_box)
+ result = iface->get_bounding_box (projectable);
+
+ return result;
+}
+
+GeglNode *
+gimp_projectable_get_graph (GimpProjectable *projectable)
+{
+ GimpProjectableInterface *iface;
+
+ g_return_val_if_fail (GIMP_IS_PROJECTABLE (projectable), NULL);
+
+ iface = GIMP_PROJECTABLE_GET_INTERFACE (projectable);
+
+ if (iface->get_graph)
+ return iface->get_graph (projectable);
+
+ return NULL;
+}
+
+void
+gimp_projectable_begin_render (GimpProjectable *projectable)
+{
+ GimpProjectableInterface *iface;
+
+ g_return_if_fail (GIMP_IS_PROJECTABLE (projectable));
+
+ iface = GIMP_PROJECTABLE_GET_INTERFACE (projectable);
+
+ if (iface->begin_render)
+ iface->begin_render (projectable);
+}
+
+void
+gimp_projectable_end_render (GimpProjectable *projectable)
+{
+ GimpProjectableInterface *iface;
+
+ g_return_if_fail (GIMP_IS_PROJECTABLE (projectable));
+
+ iface = GIMP_PROJECTABLE_GET_INTERFACE (projectable);
+
+ if (iface->end_render)
+ iface->end_render (projectable);
+}
+
+void
+gimp_projectable_invalidate_preview (GimpProjectable *projectable)
+{
+ GimpProjectableInterface *iface;
+
+ g_return_if_fail (GIMP_IS_PROJECTABLE (projectable));
+
+ iface = GIMP_PROJECTABLE_GET_INTERFACE (projectable);
+
+ if (iface->invalidate_preview)
+ iface->invalidate_preview (projectable);
+}
diff --git a/app/core/gimpprojectable.h b/app/core/gimpprojectable.h
new file mode 100644
index 0000000..955f3a4
--- /dev/null
+++ b/app/core/gimpprojectable.h
@@ -0,0 +1,90 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-1997 Spencer Kimball and Peter Mattis
+ *
+ * gimpprojectable.h
+ * Copyright (C) 2008 Michael Natterer <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_PROJECTABLE_H__
+#define __GIMP_PROJECTABLE_H__
+
+
+#define GIMP_TYPE_PROJECTABLE (gimp_projectable_get_type ())
+#define GIMP_IS_PROJECTABLE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_PROJECTABLE))
+#define GIMP_PROJECTABLE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_PROJECTABLE, GimpProjectable))
+#define GIMP_PROJECTABLE_GET_INTERFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), GIMP_TYPE_PROJECTABLE, GimpProjectableInterface))
+
+
+typedef struct _GimpProjectableInterface GimpProjectableInterface;
+
+struct _GimpProjectableInterface
+{
+ GTypeInterface base_iface;
+
+ /* signals */
+ void (* invalidate) (GimpProjectable *projectable,
+ gint x,
+ gint y,
+ gint width,
+ gint height);
+ void (* flush) (GimpProjectable *projectable,
+ gboolean invalidate_preview);
+ void (* structure_changed) (GimpProjectable *projectable);
+ void (* bounds_changed) (GimpProjectable *projectable,
+ gint old_x,
+ gint old_y);
+
+ /* virtual functions */
+ GimpImage * (* get_image) (GimpProjectable *projectable);
+ const Babl * (* get_format) (GimpProjectable *projectable);
+ void (* get_offset) (GimpProjectable *projectable,
+ gint *x,
+ gint *y);
+ GeglRectangle (* get_bounding_box) (GimpProjectable *projectable);
+ GeglNode * (* get_graph) (GimpProjectable *projectable);
+ void (* begin_render) (GimpProjectable *projectable);
+ void (* end_render) (GimpProjectable *projectable);
+ void (* invalidate_preview) (GimpProjectable *projectable);
+};
+
+
+GType gimp_projectable_get_type (void) G_GNUC_CONST;
+
+void gimp_projectable_invalidate (GimpProjectable *projectable,
+ gint x,
+ gint y,
+ gint width,
+ gint height);
+void gimp_projectable_flush (GimpProjectable *projectable,
+ gboolean preview_invalidated);
+void gimp_projectable_structure_changed (GimpProjectable *projectable);
+void gimp_projectable_bounds_changed (GimpProjectable *projectable,
+ gint old_x,
+ gint old_y);
+
+GimpImage * gimp_projectable_get_image (GimpProjectable *projectable);
+const Babl * gimp_projectable_get_format (GimpProjectable *projectable);
+void gimp_projectable_get_offset (GimpProjectable *projectable,
+ gint *x,
+ gint *y);
+GeglRectangle gimp_projectable_get_bounding_box (GimpProjectable *projectable);
+GeglNode * gimp_projectable_get_graph (GimpProjectable *projectable);
+void gimp_projectable_begin_render (GimpProjectable *projectable);
+void gimp_projectable_end_render (GimpProjectable *projectable);
+void gimp_projectable_invalidate_preview (GimpProjectable *projectable);
+
+
+#endif /* __GIMP_PROJECTABLE_H__ */
diff --git a/app/core/gimpprojection.c b/app/core/gimpprojection.c
new file mode 100644
index 0000000..c249b55
--- /dev/null
+++ b/app/core/gimpprojection.c
@@ -0,0 +1,1132 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <cairo.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpbase/gimpbase.h"
+#include "libgimpmath/gimpmath.h"
+
+#include "core-types.h"
+
+#include "gegl/gimp-babl.h"
+#include "gegl/gimp-gegl-loops.h"
+#include "gegl/gimp-gegl-utils.h"
+
+#include "gimp.h"
+#include "gimp-memsize.h"
+#include "gimpchunkiterator.h"
+#include "gimpimage.h"
+#include "gimpmarshal.h"
+#include "gimppickable.h"
+#include "gimpprojectable.h"
+#include "gimpprojection.h"
+#include "gimptilehandlerprojectable.h"
+
+#include "gimp-log.h"
+#include "gimp-priorities.h"
+
+
+/* chunk size for area updates */
+#define GIMP_PROJECTION_UPDATE_CHUNK_WIDTH 32
+#define GIMP_PROJECTION_UPDATE_CHUNK_HEIGHT 32
+
+
+enum
+{
+ UPDATE,
+ LAST_SIGNAL
+};
+
+enum
+{
+ PROP_0,
+ PROP_BUFFER
+};
+
+
+struct _GimpProjectionPrivate
+{
+ GimpProjectable *projectable;
+
+ GeglBuffer *buffer;
+ GimpTileHandlerValidate *validate_handler;
+
+ gint priority;
+
+ cairo_region_t *update_region;
+ GeglRectangle priority_rect;
+ GimpChunkIterator *iter;
+ guint idle_id;
+
+ gboolean invalidate_preview;
+};
+
+
+/* local function prototypes */
+
+static void gimp_projection_pickable_iface_init (GimpPickableInterface *iface);
+
+static void gimp_projection_finalize (GObject *object);
+static void gimp_projection_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_projection_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static gint64 gimp_projection_get_memsize (GimpObject *object,
+ gint64 *gui_size);
+
+static void gimp_projection_pickable_flush (GimpPickable *pickable);
+static GimpImage * gimp_projection_get_image (GimpPickable *pickable);
+static const Babl * gimp_projection_get_format (GimpPickable *pickable);
+static GeglBuffer * gimp_projection_get_buffer (GimpPickable *pickable);
+static gboolean gimp_projection_get_pixel_at (GimpPickable *pickable,
+ gint x,
+ gint y,
+ const Babl *format,
+ gpointer pixel);
+static gdouble gimp_projection_get_opacity_at (GimpPickable *pickable,
+ gint x,
+ gint y);
+static void gimp_projection_get_pixel_average (GimpPickable *pickable,
+ const GeglRectangle *rect,
+ const Babl *format,
+ gpointer pixel);
+static void gimp_projection_pixel_to_srgb (GimpPickable *pickable,
+ const Babl *format,
+ gpointer pixel,
+ GimpRGB *color);
+static void gimp_projection_srgb_to_pixel (GimpPickable *pickable,
+ const GimpRGB *color,
+ const Babl *format,
+ gpointer pixel);
+
+static void gimp_projection_allocate_buffer (GimpProjection *proj);
+static void gimp_projection_free_buffer (GimpProjection *proj);
+static void gimp_projection_add_update_area (GimpProjection *proj,
+ gint x,
+ gint y,
+ gint w,
+ gint h);
+static void gimp_projection_flush_whenever (GimpProjection *proj,
+ gboolean now,
+ gboolean direct);
+static void gimp_projection_update_priority_rect (GimpProjection *proj);
+static void gimp_projection_chunk_render_start (GimpProjection *proj);
+static void gimp_projection_chunk_render_stop (GimpProjection *proj,
+ gboolean merge);
+static gboolean gimp_projection_chunk_render_callback (GimpProjection *proj);
+static gboolean gimp_projection_chunk_render_iteration(GimpProjection *proj);
+static void gimp_projection_paint_area (GimpProjection *proj,
+ gboolean now,
+ gint x,
+ gint y,
+ gint w,
+ gint h);
+
+static void gimp_projection_projectable_invalidate(GimpProjectable *projectable,
+ gint x,
+ gint y,
+ gint w,
+ gint h,
+ GimpProjection *proj);
+static void gimp_projection_projectable_flush (GimpProjectable *projectable,
+ gboolean invalidate_preview,
+ GimpProjection *proj);
+static void
+ gimp_projection_projectable_structure_changed (GimpProjectable *projectable,
+ GimpProjection *proj);
+static void gimp_projection_projectable_bounds_changed (GimpProjectable *projectable,
+ gint old_x,
+ gint old_y,
+ GimpProjection *proj);
+
+
+G_DEFINE_TYPE_WITH_CODE (GimpProjection, gimp_projection, GIMP_TYPE_OBJECT,
+ G_ADD_PRIVATE (GimpProjection)
+ G_IMPLEMENT_INTERFACE (GIMP_TYPE_PICKABLE,
+ gimp_projection_pickable_iface_init))
+
+#define parent_class gimp_projection_parent_class
+
+static guint projection_signals[LAST_SIGNAL] = { 0 };
+
+
+static void
+gimp_projection_class_init (GimpProjectionClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpObjectClass *gimp_object_class = GIMP_OBJECT_CLASS (klass);
+
+ projection_signals[UPDATE] =
+ g_signal_new ("update",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpProjectionClass, update),
+ NULL, NULL,
+ gimp_marshal_VOID__BOOLEAN_INT_INT_INT_INT,
+ G_TYPE_NONE, 5,
+ G_TYPE_BOOLEAN,
+ G_TYPE_INT,
+ G_TYPE_INT,
+ G_TYPE_INT,
+ G_TYPE_INT);
+
+ object_class->finalize = gimp_projection_finalize;
+ object_class->set_property = gimp_projection_set_property;
+ object_class->get_property = gimp_projection_get_property;
+
+ gimp_object_class->get_memsize = gimp_projection_get_memsize;
+
+ g_object_class_override_property (object_class, PROP_BUFFER, "buffer");
+}
+
+static void
+gimp_projection_init (GimpProjection *proj)
+{
+ proj->priv = gimp_projection_get_instance_private (proj);
+}
+
+static void
+gimp_projection_pickable_iface_init (GimpPickableInterface *iface)
+{
+ iface->flush = gimp_projection_pickable_flush;
+ iface->get_image = gimp_projection_get_image;
+ iface->get_format = gimp_projection_get_format;
+ iface->get_format_with_alpha = gimp_projection_get_format; /* sic */
+ iface->get_buffer = gimp_projection_get_buffer;
+ iface->get_pixel_at = gimp_projection_get_pixel_at;
+ iface->get_opacity_at = gimp_projection_get_opacity_at;
+ iface->get_pixel_average = gimp_projection_get_pixel_average;
+ iface->pixel_to_srgb = gimp_projection_pixel_to_srgb;
+ iface->srgb_to_pixel = gimp_projection_srgb_to_pixel;
+}
+
+static void
+gimp_projection_finalize (GObject *object)
+{
+ GimpProjection *proj = GIMP_PROJECTION (object);
+
+ gimp_projection_free_buffer (proj);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gimp_projection_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id)
+ {
+ case PROP_BUFFER:
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_projection_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpProjection *projection = GIMP_PROJECTION (object);
+
+ switch (property_id)
+ {
+ case PROP_BUFFER:
+ g_value_set_object (value, projection->priv->buffer);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static gint64
+gimp_projection_get_memsize (GimpObject *object,
+ gint64 *gui_size)
+{
+ GimpProjection *projection = GIMP_PROJECTION (object);
+ gint64 memsize = 0;
+
+ memsize += gimp_gegl_pyramid_get_memsize (projection->priv->buffer);
+
+ return memsize + GIMP_OBJECT_CLASS (parent_class)->get_memsize (object,
+ gui_size);
+}
+
+/**
+ * gimp_projection_estimate_memsize:
+ * @type: the projectable's base type
+ * @component_type: the projectable's component type
+ * @width: projection width
+ * @height: projection height
+ *
+ * Calculates a rough estimate of the memory that is required for the
+ * projection of an image with the given @width and @height.
+ *
+ * Return value: a rough estimate of the memory requirements.
+ **/
+gint64
+gimp_projection_estimate_memsize (GimpImageBaseType type,
+ GimpComponentType component_type,
+ gint width,
+ gint height)
+{
+ const Babl *format;
+ gint64 bytes;
+
+ if (type == GIMP_INDEXED)
+ type = GIMP_RGB;
+
+ format = gimp_babl_format (type,
+ gimp_babl_precision (component_type, FALSE),
+ TRUE);
+ bytes = babl_format_get_bytes_per_pixel (format);
+
+ /* The pyramid levels constitute a geometric sum with a ratio of 1/4. */
+ return bytes * (gint64) width * (gint64) height * 1.33;
+}
+
+
+static void
+gimp_projection_pickable_flush (GimpPickable *pickable)
+{
+ GimpProjection *proj = GIMP_PROJECTION (pickable);
+
+ /* create the buffer if it doesn't exist */
+ gimp_projection_get_buffer (pickable);
+
+ gimp_projection_finish_draw (proj);
+ gimp_projection_flush_now (proj, FALSE);
+
+ if (proj->priv->invalidate_preview)
+ {
+ /* invalidate the preview here since it is constructed from
+ * the projection
+ */
+ proj->priv->invalidate_preview = FALSE;
+
+ gimp_projectable_invalidate_preview (proj->priv->projectable);
+ }
+}
+
+static GimpImage *
+gimp_projection_get_image (GimpPickable *pickable)
+{
+ GimpProjection *proj = GIMP_PROJECTION (pickable);
+
+ return gimp_projectable_get_image (proj->priv->projectable);
+}
+
+static const Babl *
+gimp_projection_get_format (GimpPickable *pickable)
+{
+ GimpProjection *proj = GIMP_PROJECTION (pickable);
+
+ return gimp_projectable_get_format (proj->priv->projectable);
+}
+
+static GeglBuffer *
+gimp_projection_get_buffer (GimpPickable *pickable)
+{
+ GimpProjection *proj = GIMP_PROJECTION (pickable);
+
+ if (! proj->priv->buffer)
+ {
+ GeglRectangle bounding_box;
+
+ bounding_box =
+ gimp_projectable_get_bounding_box (proj->priv->projectable);
+
+ gimp_projection_allocate_buffer (proj);
+
+ /* This used to call gimp_tile_handler_validate_invalidate()
+ * which forced the entire projection to be constructed in one
+ * go for new images, causing a potentially huge delay. Now we
+ * initially validate stuff the normal way, which makes the
+ * image appear incrementally, but it keeps everything
+ * responsive.
+ */
+ gimp_projection_add_update_area (proj,
+ bounding_box.x, bounding_box.y,
+ bounding_box.width, bounding_box.height);
+ proj->priv->invalidate_preview = TRUE;
+ gimp_projection_flush (proj);
+ }
+
+ return proj->priv->buffer;
+}
+
+static gboolean
+gimp_projection_get_pixel_at (GimpPickable *pickable,
+ gint x,
+ gint y,
+ const Babl *format,
+ gpointer pixel)
+{
+ GimpProjection *proj = GIMP_PROJECTION (pickable);
+ GeglBuffer *buffer = gimp_projection_get_buffer (pickable);
+ GeglRectangle bounding_box;
+
+ bounding_box = gimp_projectable_get_bounding_box (proj->priv->projectable);
+
+ if (x < bounding_box.x ||
+ y < bounding_box.y ||
+ x >= bounding_box.x + bounding_box.width ||
+ y >= bounding_box.y + bounding_box.height)
+ {
+ return FALSE;
+ }
+
+ gegl_buffer_sample (buffer, x, y, NULL, pixel, format,
+ GEGL_SAMPLER_NEAREST, GEGL_ABYSS_NONE);
+
+ return TRUE;
+}
+
+static gdouble
+gimp_projection_get_opacity_at (GimpPickable *pickable,
+ gint x,
+ gint y)
+{
+ return GIMP_OPACITY_OPAQUE;
+}
+
+static void
+gimp_projection_get_pixel_average (GimpPickable *pickable,
+ const GeglRectangle *rect,
+ const Babl *format,
+ gpointer pixel)
+{
+ GeglBuffer *buffer = gimp_projection_get_buffer (pickable);
+
+ return gimp_gegl_average_color (buffer, rect, TRUE, GEGL_ABYSS_NONE, format,
+ pixel);
+}
+
+static void
+gimp_projection_pixel_to_srgb (GimpPickable *pickable,
+ const Babl *format,
+ gpointer pixel,
+ GimpRGB *color)
+{
+ GimpProjection *proj = GIMP_PROJECTION (pickable);
+ GimpImage *image = gimp_projectable_get_image (proj->priv->projectable);
+
+ gimp_pickable_pixel_to_srgb (GIMP_PICKABLE (image), format, pixel, color);
+}
+
+static void
+gimp_projection_srgb_to_pixel (GimpPickable *pickable,
+ const GimpRGB *color,
+ const Babl *format,
+ gpointer pixel)
+{
+ GimpProjection *proj = GIMP_PROJECTION (pickable);
+ GimpImage *image = gimp_projectable_get_image (proj->priv->projectable);
+
+ gimp_pickable_srgb_to_pixel (GIMP_PICKABLE (image), color, format, pixel);
+}
+
+
+/* public functions */
+
+GimpProjection *
+gimp_projection_new (GimpProjectable *projectable)
+{
+ GimpProjection *proj;
+
+ g_return_val_if_fail (GIMP_IS_PROJECTABLE (projectable), NULL);
+
+ proj = g_object_new (GIMP_TYPE_PROJECTION, NULL);
+
+ proj->priv->projectable = projectable;
+
+ g_signal_connect_object (projectable, "invalidate",
+ G_CALLBACK (gimp_projection_projectable_invalidate),
+ proj, 0);
+ g_signal_connect_object (projectable, "flush",
+ G_CALLBACK (gimp_projection_projectable_flush),
+ proj, 0);
+ g_signal_connect_object (projectable, "structure-changed",
+ G_CALLBACK (gimp_projection_projectable_structure_changed),
+ proj, 0);
+ g_signal_connect_object (projectable, "bounds-changed",
+ G_CALLBACK (gimp_projection_projectable_bounds_changed),
+ proj, 0);
+
+ return proj;
+}
+
+void
+gimp_projection_set_priority (GimpProjection *proj,
+ gint priority)
+{
+ g_return_if_fail (GIMP_IS_PROJECTION (proj));
+
+ proj->priv->priority = priority;
+}
+
+gint
+gimp_projection_get_priority (GimpProjection *proj)
+{
+ g_return_val_if_fail (GIMP_IS_PROJECTION (proj), 0);
+
+ return proj->priv->priority;
+}
+
+void
+gimp_projection_set_priority_rect (GimpProjection *proj,
+ gint x,
+ gint y,
+ gint w,
+ gint h)
+{
+ g_return_if_fail (GIMP_IS_PROJECTION (proj));
+
+ proj->priv->priority_rect = *GEGL_RECTANGLE (x, y, w, h);
+
+ gimp_projection_update_priority_rect (proj);
+}
+
+void
+gimp_projection_stop_rendering (GimpProjection *proj)
+{
+ g_return_if_fail (GIMP_IS_PROJECTION (proj));
+
+ gimp_projection_chunk_render_stop (proj, TRUE);
+}
+
+void
+gimp_projection_flush (GimpProjection *proj)
+{
+ g_return_if_fail (GIMP_IS_PROJECTION (proj));
+
+ /* Construct in chunks */
+ gimp_projection_flush_whenever (proj, FALSE, FALSE);
+}
+
+void
+gimp_projection_flush_now (GimpProjection *proj,
+ gboolean direct)
+{
+ g_return_if_fail (GIMP_IS_PROJECTION (proj));
+
+ /* Construct NOW */
+ gimp_projection_flush_whenever (proj, TRUE, direct);
+}
+
+void
+gimp_projection_finish_draw (GimpProjection *proj)
+{
+ g_return_if_fail (GIMP_IS_PROJECTION (proj));
+
+ if (proj->priv->iter)
+ {
+ gimp_chunk_iterator_set_priority_rect (proj->priv->iter, NULL);
+
+ gimp_tile_handler_validate_begin_validate (proj->priv->validate_handler);
+
+ while (gimp_projection_chunk_render_iteration (proj));
+
+ gimp_tile_handler_validate_end_validate (proj->priv->validate_handler);
+
+ gimp_projection_chunk_render_stop (proj, FALSE);
+ }
+}
+
+
+/* private functions */
+
+static void
+gimp_projection_allocate_buffer (GimpProjection *proj)
+{
+ const Babl *format;
+ GeglRectangle bounding_box;
+
+ if (proj->priv->buffer)
+ return;
+
+ format = gimp_projection_get_format (GIMP_PICKABLE (proj));
+ bounding_box =
+ gimp_projectable_get_bounding_box (proj->priv->projectable);
+
+ proj->priv->buffer = gegl_buffer_new (&bounding_box, format);
+
+ proj->priv->validate_handler =
+ GIMP_TILE_HANDLER_VALIDATE (
+ gimp_tile_handler_projectable_new (proj->priv->projectable));
+
+ gimp_tile_handler_validate_assign (proj->priv->validate_handler,
+ proj->priv->buffer);
+
+ g_object_notify (G_OBJECT (proj), "buffer");
+}
+
+static void
+gimp_projection_free_buffer (GimpProjection *proj)
+{
+ gimp_projection_chunk_render_stop (proj, FALSE);
+
+ g_clear_pointer (&proj->priv->update_region, cairo_region_destroy);
+
+ if (proj->priv->buffer)
+ {
+ gimp_tile_handler_validate_unassign (proj->priv->validate_handler,
+ proj->priv->buffer);
+
+ g_clear_object (&proj->priv->buffer);
+ g_clear_object (&proj->priv->validate_handler);
+
+ g_object_notify (G_OBJECT (proj), "buffer");
+ }
+}
+
+static void
+gimp_projection_add_update_area (GimpProjection *proj,
+ gint x,
+ gint y,
+ gint w,
+ gint h)
+{
+ cairo_rectangle_int_t rect;
+ GeglRectangle bounding_box;
+
+ bounding_box = gimp_projectable_get_bounding_box (proj->priv->projectable);
+
+ /* align the rectangle to the UPDATE_CHUNK_WIDTH x UPDATE_CHUNK_HEIGHT grid,
+ * to decrease the complexity of the update area.
+ */
+ w = ceil ((gdouble) (x + w) / GIMP_PROJECTION_UPDATE_CHUNK_WIDTH ) * GIMP_PROJECTION_UPDATE_CHUNK_WIDTH;
+ h = ceil ((gdouble) (y + h) / GIMP_PROJECTION_UPDATE_CHUNK_HEIGHT) * GIMP_PROJECTION_UPDATE_CHUNK_HEIGHT;
+ x = floor ((gdouble) x / GIMP_PROJECTION_UPDATE_CHUNK_WIDTH ) * GIMP_PROJECTION_UPDATE_CHUNK_WIDTH;
+ y = floor ((gdouble) y / GIMP_PROJECTION_UPDATE_CHUNK_HEIGHT) * GIMP_PROJECTION_UPDATE_CHUNK_HEIGHT;
+
+ w -= x;
+ h -= y;
+
+ if (gegl_rectangle_intersect ((GeglRectangle *) &rect,
+ GEGL_RECTANGLE (x, y, w, h), &bounding_box))
+ {
+ if (proj->priv->update_region)
+ cairo_region_union_rectangle (proj->priv->update_region, &rect);
+ else
+ proj->priv->update_region = cairo_region_create_rectangle (&rect);
+ }
+}
+
+static void
+gimp_projection_flush_whenever (GimpProjection *proj,
+ gboolean now,
+ gboolean direct)
+{
+ if (proj->priv->update_region)
+ {
+ /* Make sure we have a buffer */
+ gimp_projection_allocate_buffer (proj);
+
+ if (now) /* Synchronous */
+ {
+ gint n_rects = cairo_region_num_rectangles (proj->priv->update_region);
+ gint i;
+
+ for (i = 0; i < n_rects; i++)
+ {
+ cairo_rectangle_int_t rect;
+
+ cairo_region_get_rectangle (proj->priv->update_region,
+ i, &rect);
+
+ gimp_projection_paint_area (proj,
+ direct,
+ rect.x,
+ rect.y,
+ rect.width,
+ rect.height);
+ }
+
+ /* Free the update region */
+ g_clear_pointer (&proj->priv->update_region, cairo_region_destroy);
+ }
+ else /* Asynchronous */
+ {
+ /* Consumes the update region */
+ gimp_projection_chunk_render_start (proj);
+ }
+ }
+ else if (! now && ! proj->priv->iter && proj->priv->invalidate_preview)
+ {
+ /* invalidate the preview here since it is constructed from
+ * the projection
+ */
+ proj->priv->invalidate_preview = FALSE;
+
+ gimp_projectable_invalidate_preview (proj->priv->projectable);
+ }
+}
+
+static void
+gimp_projection_update_priority_rect (GimpProjection *proj)
+{
+ if (proj->priv->iter)
+ {
+ GeglRectangle rect;
+ GeglRectangle bounding_box;
+ gint off_x, off_y;
+
+ rect = proj->priv->priority_rect;
+
+ gimp_projectable_get_offset (proj->priv->projectable, &off_x, &off_y);
+ bounding_box = gimp_projectable_get_bounding_box (proj->priv->projectable);
+
+ /* subtract the projectable's offsets because the list of update
+ * areas is in tile-pyramid coordinates, but our external API is
+ * always in terms of image coordinates.
+ */
+ rect.x -= off_x;
+ rect.y -= off_y;
+
+ gegl_rectangle_intersect (&rect, &rect, &bounding_box);
+
+ gimp_chunk_iterator_set_priority_rect (proj->priv->iter, &rect);
+ }
+}
+
+static void
+gimp_projection_chunk_render_start (GimpProjection *proj)
+{
+ cairo_region_t *region = proj->priv->update_region;
+ gboolean invalidate_preview = FALSE;
+
+ if (proj->priv->iter)
+ {
+ region = gimp_chunk_iterator_stop (proj->priv->iter, FALSE);
+
+ proj->priv->iter = NULL;
+
+ if (cairo_region_is_empty (region))
+ invalidate_preview = proj->priv->invalidate_preview;
+
+ if (proj->priv->update_region)
+ {
+ cairo_region_union (region, proj->priv->update_region);
+
+ cairo_region_destroy (proj->priv->update_region);
+ }
+ }
+
+ proj->priv->update_region = NULL;
+
+ if (region && ! cairo_region_is_empty (region))
+ {
+ proj->priv->iter = gimp_chunk_iterator_new (region);
+
+ gimp_projection_update_priority_rect (proj);
+
+ if (! proj->priv->idle_id)
+ {
+ proj->priv->idle_id = g_idle_add_full (
+ GIMP_PRIORITY_PROJECTION_IDLE + proj->priv->priority,
+ (GSourceFunc) gimp_projection_chunk_render_callback,
+ proj, NULL);
+ }
+ }
+ else
+ {
+ if (region)
+ cairo_region_destroy (region);
+
+ if (proj->priv->idle_id)
+ {
+ g_source_remove (proj->priv->idle_id);
+ proj->priv->idle_id = 0;
+ }
+
+ if (invalidate_preview)
+ {
+ /* invalidate the preview here since it is constructed from
+ * the projection
+ */
+ proj->priv->invalidate_preview = FALSE;
+
+ gimp_projectable_invalidate_preview (proj->priv->projectable);
+ }
+ }
+}
+
+static void
+gimp_projection_chunk_render_stop (GimpProjection *proj,
+ gboolean merge)
+{
+ if (proj->priv->idle_id)
+ {
+ g_source_remove (proj->priv->idle_id);
+ proj->priv->idle_id = 0;
+ }
+
+ if (proj->priv->iter)
+ {
+ if (merge)
+ {
+ cairo_region_t *region;
+
+ region = gimp_chunk_iterator_stop (proj->priv->iter, FALSE);
+
+ if (proj->priv->update_region)
+ {
+ cairo_region_union (proj->priv->update_region, region);
+
+ cairo_region_destroy (region);
+ }
+ else
+ {
+ proj->priv->update_region = region;
+ }
+ }
+ else
+ {
+ gimp_chunk_iterator_stop (proj->priv->iter, TRUE);
+ }
+
+ proj->priv->iter = NULL;
+ }
+}
+
+static gboolean
+gimp_projection_chunk_render_callback (GimpProjection *proj)
+{
+ if (gimp_projection_chunk_render_iteration (proj))
+ {
+ return G_SOURCE_CONTINUE;
+ }
+ else
+ {
+ proj->priv->idle_id = 0;
+
+ return G_SOURCE_REMOVE;
+ }
+}
+
+static gboolean
+gimp_projection_chunk_render_iteration (GimpProjection *proj)
+{
+ if (gimp_chunk_iterator_next (proj->priv->iter))
+ {
+ GeglRectangle rect;
+
+ gimp_tile_handler_validate_begin_validate (proj->priv->validate_handler);
+
+ while (gimp_chunk_iterator_get_rect (proj->priv->iter, &rect))
+ {
+ gimp_projection_paint_area (proj, TRUE,
+ rect.x, rect.y, rect.width, rect.height);
+ }
+
+ gimp_tile_handler_validate_end_validate (proj->priv->validate_handler);
+
+ /* Still work to do. */
+ return TRUE;
+ }
+ else
+ {
+ proj->priv->iter = NULL;
+
+ if (proj->priv->invalidate_preview)
+ {
+ /* invalidate the preview here since it is constructed from
+ * the projection
+ */
+ proj->priv->invalidate_preview = FALSE;
+
+ gimp_projectable_invalidate_preview (proj->priv->projectable);
+ }
+
+ /* FINISHED */
+ return FALSE;
+ }
+}
+
+static void
+gimp_projection_paint_area (GimpProjection *proj,
+ gboolean now,
+ gint x,
+ gint y,
+ gint w,
+ gint h)
+{
+ gint off_x, off_y;
+ GeglRectangle bounding_box;
+ GeglRectangle rect;
+
+ gimp_projectable_get_offset (proj->priv->projectable, &off_x, &off_y);
+ bounding_box = gimp_projectable_get_bounding_box (proj->priv->projectable);
+
+ if (gegl_rectangle_intersect (&rect,
+ GEGL_RECTANGLE (x, y, w, h), &bounding_box))
+ {
+ if (now)
+ {
+ gimp_tile_handler_validate_validate (
+ proj->priv->validate_handler,
+ proj->priv->buffer,
+ &rect,
+ FALSE, FALSE);
+ }
+ else
+ {
+ gimp_tile_handler_validate_invalidate (
+ proj->priv->validate_handler,
+ &rect);
+ }
+
+ /* add the projectable's offsets because the list of update areas
+ * is in tile-pyramid coordinates, but our external API is always
+ * in terms of image coordinates.
+ */
+ g_signal_emit (proj, projection_signals[UPDATE], 0,
+ now,
+ rect.x + off_x,
+ rect.y + off_y,
+ rect.width,
+ rect.height);
+ }
+}
+
+
+/* image callbacks */
+
+static void
+gimp_projection_projectable_invalidate (GimpProjectable *projectable,
+ gint x,
+ gint y,
+ gint w,
+ gint h,
+ GimpProjection *proj)
+{
+ gint off_x, off_y;
+
+ gimp_projectable_get_offset (proj->priv->projectable, &off_x, &off_y);
+
+ /* subtract the projectable's offsets because the list of update
+ * areas is in tile-pyramid coordinates, but our external API is
+ * always in terms of image coordinates.
+ */
+ x -= off_x;
+ y -= off_y;
+
+ gimp_projection_add_update_area (proj, x, y, w, h);
+}
+
+static void
+gimp_projection_projectable_flush (GimpProjectable *projectable,
+ gboolean invalidate_preview,
+ GimpProjection *proj)
+{
+ if (invalidate_preview)
+ proj->priv->invalidate_preview = TRUE;
+
+ gimp_projection_flush (proj);
+}
+
+static void
+gimp_projection_projectable_structure_changed (GimpProjectable *projectable,
+ GimpProjection *proj)
+{
+ GeglRectangle bounding_box;
+
+ gimp_projection_free_buffer (proj);
+
+ bounding_box = gimp_projectable_get_bounding_box (projectable);
+
+ gimp_projection_add_update_area (proj,
+ bounding_box.x, bounding_box.y,
+ bounding_box.width, bounding_box.height);
+}
+
+static void
+gimp_projection_projectable_bounds_changed (GimpProjectable *projectable,
+ gint old_x,
+ gint old_y,
+ GimpProjection *proj)
+{
+ GeglBuffer *old_buffer = proj->priv->buffer;
+ GimpTileHandlerValidate *old_validate_handler;
+ GeglRectangle old_bounding_box;
+ GeglRectangle bounding_box;
+ GeglRectangle old_bounds;
+ GeglRectangle bounds;
+ GeglRectangle int_bounds;
+ gint x, y;
+ gint dx, dy;
+
+ if (! old_buffer)
+ {
+ gimp_projection_projectable_structure_changed (projectable, proj);
+
+ return;
+ }
+
+ old_bounding_box = *gegl_buffer_get_extent (old_buffer);
+
+ gimp_projectable_get_offset (projectable, &x, &y);
+ bounding_box = gimp_projectable_get_bounding_box (projectable);
+
+ if (x == old_x && y == old_y &&
+ gegl_rectangle_equal (&bounding_box, &old_bounding_box))
+ {
+ return;
+ }
+
+ old_bounds = old_bounding_box;
+ old_bounds.x += old_x;
+ old_bounds.y += old_y;
+
+ bounds = bounding_box;
+ bounds.x += x;
+ bounds.y += y;
+
+ if (! gegl_rectangle_intersect (&int_bounds, &bounds, &old_bounds))
+ {
+ gimp_projection_projectable_structure_changed (projectable, proj);
+
+ return;
+ }
+
+ dx = x - old_x;
+ dy = y - old_y;
+
+#if 1
+ /* FIXME: when there's an offset between the new bounds and the old bounds,
+ * use gimp_projection_projectable_structure_changed(), instead of copying a
+ * shifted version of the old buffer, since the synchronous copy can take a
+ * notable amount of time for big buffers, when the offset is such that tiles
+ * are not COW-ed. while gimp_projection_projectable_structure_changed()
+ * causes the projection to be re-rendered, which is overall slower, it's
+ * done asynchronously.
+ *
+ * this needs to be improved.
+ */
+ if (dx || dy)
+ {
+ gimp_projection_projectable_structure_changed (projectable, proj);
+
+ return;
+ }
+#endif
+
+ /* reallocate the buffer, and copy the old buffer to the corresponding
+ * region of the new buffer.
+ */
+
+ gimp_projection_chunk_render_stop (proj, TRUE);
+
+ if (dx == 0 && dy == 0)
+ {
+ gimp_tile_handler_validate_buffer_set_extent (old_buffer, &bounding_box);
+ }
+ else
+ {
+ old_validate_handler = proj->priv->validate_handler;
+
+ proj->priv->buffer = NULL;
+ proj->priv->validate_handler = NULL;
+
+ gimp_projection_allocate_buffer (proj);
+
+ gimp_tile_handler_validate_buffer_copy (
+ old_buffer,
+ GEGL_RECTANGLE (int_bounds.x - old_x,
+ int_bounds.y - old_y,
+ int_bounds.width,
+ int_bounds.height),
+ proj->priv->buffer,
+ GEGL_RECTANGLE (int_bounds.x - x,
+ int_bounds.y - y,
+ int_bounds.width,
+ int_bounds.height));
+
+ gimp_tile_handler_validate_unassign (old_validate_handler,
+ old_buffer);
+
+ g_object_unref (old_validate_handler);
+ g_object_unref (old_buffer);
+ }
+
+ if (proj->priv->update_region)
+ {
+ cairo_region_translate (proj->priv->update_region, dx, dy);
+ cairo_region_intersect_rectangle (
+ proj->priv->update_region,
+ (const cairo_rectangle_int_t *) &bounding_box);
+ }
+
+ int_bounds.x -= x;
+ int_bounds.y -= y;
+
+ if (int_bounds.x > bounding_box.x)
+ {
+ gimp_projection_add_update_area (proj,
+ bounding_box.x,
+ bounding_box.y,
+ int_bounds.x - bounding_box.x,
+ bounding_box.height);
+ }
+ if (int_bounds.y > bounding_box.y)
+ {
+ gimp_projection_add_update_area (proj,
+ bounding_box.x,
+ bounding_box.y,
+ bounding_box.width,
+ int_bounds.y - bounding_box.y);
+ }
+ if (int_bounds.x + int_bounds.width < bounding_box.x + bounding_box.width)
+ {
+ gimp_projection_add_update_area (proj,
+ int_bounds.x + int_bounds.width,
+ bounding_box.y,
+ bounding_box.x + bounding_box.width -
+ (int_bounds.x + int_bounds.width),
+ bounding_box.height);
+ }
+ if (int_bounds.y + int_bounds.height < bounding_box.y + bounding_box.height)
+ {
+ gimp_projection_add_update_area (proj,
+ bounding_box.x,
+ int_bounds.y + int_bounds.height,
+ bounding_box.width,
+ bounding_box.y + bounding_box.height -
+ (int_bounds.y + int_bounds.height));
+ }
+
+ proj->priv->invalidate_preview = TRUE;
+}
diff --git a/app/core/gimpprojection.h b/app/core/gimpprojection.h
new file mode 100644
index 0000000..8c9baf6
--- /dev/null
+++ b/app/core/gimpprojection.h
@@ -0,0 +1,83 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_PROJECTION_H__
+#define __GIMP_PROJECTION_H__
+
+
+#include "gimpobject.h"
+
+
+#define GIMP_TYPE_PROJECTION (gimp_projection_get_type ())
+#define GIMP_PROJECTION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_PROJECTION, GimpProjection))
+#define GIMP_PROJECTION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_PROJECTION, GimpProjectionClass))
+#define GIMP_IS_PROJECTION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_PROJECTION))
+#define GIMP_IS_PROJECTION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_PROJECTION))
+#define GIMP_PROJECTION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_PROJECTION, GimpProjectionClass))
+
+
+typedef struct _GimpProjectionPrivate GimpProjectionPrivate;
+typedef struct _GimpProjectionClass GimpProjectionClass;
+
+struct _GimpProjection
+{
+ GimpObject parent_instance;
+
+ GimpProjectionPrivate *priv;
+};
+
+struct _GimpProjectionClass
+{
+ GimpObjectClass parent_class;
+
+ void (* update) (GimpProjection *proj,
+ gboolean now,
+ gint x,
+ gint y,
+ gint width,
+ gint height);
+};
+
+
+GType gimp_projection_get_type (void) G_GNUC_CONST;
+
+GimpProjection * gimp_projection_new (GimpProjectable *projectable);
+
+void gimp_projection_set_priority (GimpProjection *projection,
+ gint priority);
+gint gimp_projection_get_priority (GimpProjection *projection);
+
+void gimp_projection_set_priority_rect (GimpProjection *proj,
+ gint x,
+ gint y,
+ gint width,
+ gint height);
+
+void gimp_projection_stop_rendering (GimpProjection *proj);
+
+void gimp_projection_flush (GimpProjection *proj);
+void gimp_projection_flush_now (GimpProjection *proj,
+ gboolean direct);
+void gimp_projection_finish_draw (GimpProjection *proj);
+
+gint64 gimp_projection_estimate_memsize (GimpImageBaseType type,
+ GimpComponentType component_type,
+ gint width,
+ gint height);
+
+
+#endif /* __GIMP_PROJECTION_H__ */
diff --git a/app/core/gimpsamplepoint.c b/app/core/gimpsamplepoint.c
new file mode 100644
index 0000000..f0fe377
--- /dev/null
+++ b/app/core/gimpsamplepoint.c
@@ -0,0 +1,215 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gio/gio.h>
+
+#include "libgimpbase/gimpbase.h"
+#include "libgimpconfig/gimpconfig.h"
+
+#include "core-types.h"
+
+#include "gimpsamplepoint.h"
+
+
+enum
+{
+ PROP_0,
+ PROP_POSITION_X,
+ PROP_POSITION_Y,
+ PROP_PICK_MODE
+};
+
+
+struct _GimpSamplePointPrivate
+{
+ gint position_x;
+ gint position_y;
+ GimpColorPickMode pick_mode;
+};
+
+
+static void gimp_sample_point_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+static void gimp_sample_point_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+
+
+G_DEFINE_TYPE_WITH_PRIVATE (GimpSamplePoint, gimp_sample_point,
+ GIMP_TYPE_AUX_ITEM)
+
+
+static void
+gimp_sample_point_class_init (GimpSamplePointClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->get_property = gimp_sample_point_get_property;
+ object_class->set_property = gimp_sample_point_set_property;
+
+ GIMP_CONFIG_PROP_INT (object_class, PROP_POSITION_X,
+ "position-x",
+ NULL, NULL,
+ GIMP_SAMPLE_POINT_POSITION_UNDEFINED,
+ GIMP_MAX_IMAGE_SIZE,
+ GIMP_SAMPLE_POINT_POSITION_UNDEFINED,
+ 0);
+
+ GIMP_CONFIG_PROP_INT (object_class, PROP_POSITION_Y,
+ "position-y",
+ NULL, NULL,
+ GIMP_SAMPLE_POINT_POSITION_UNDEFINED,
+ GIMP_MAX_IMAGE_SIZE,
+ GIMP_SAMPLE_POINT_POSITION_UNDEFINED,
+ 0);
+
+ GIMP_CONFIG_PROP_ENUM (object_class, PROP_PICK_MODE,
+ "pick-mode",
+ NULL, NULL,
+ GIMP_TYPE_COLOR_PICK_MODE,
+ GIMP_COLOR_PICK_MODE_PIXEL,
+ 0);
+}
+
+static void
+gimp_sample_point_init (GimpSamplePoint *sample_point)
+{
+ sample_point->priv = gimp_sample_point_get_instance_private (sample_point);
+}
+
+static void
+gimp_sample_point_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpSamplePoint *sample_point = GIMP_SAMPLE_POINT (object);
+
+ switch (property_id)
+ {
+ case PROP_POSITION_X:
+ g_value_set_int (value, sample_point->priv->position_x);
+ break;
+ case PROP_POSITION_Y:
+ g_value_set_int (value, sample_point->priv->position_y);
+ break;
+ case PROP_PICK_MODE:
+ g_value_set_enum (value, sample_point->priv->pick_mode);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_sample_point_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpSamplePoint *sample_point = GIMP_SAMPLE_POINT (object);
+
+ switch (property_id)
+ {
+ case PROP_POSITION_X:
+ sample_point->priv->position_x = g_value_get_int (value);
+ break;
+ case PROP_POSITION_Y:
+ sample_point->priv->position_y = g_value_get_int (value);
+ break;
+ case PROP_PICK_MODE:
+ sample_point->priv->pick_mode = g_value_get_enum (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+GimpSamplePoint *
+gimp_sample_point_new (guint32 sample_point_ID)
+{
+ return g_object_new (GIMP_TYPE_SAMPLE_POINT,
+ "id", sample_point_ID,
+ NULL);
+}
+
+void
+gimp_sample_point_get_position (GimpSamplePoint *sample_point,
+ gint *position_x,
+ gint *position_y)
+{
+ g_return_if_fail (GIMP_IS_SAMPLE_POINT (sample_point));
+ g_return_if_fail (position_x != NULL);
+ g_return_if_fail (position_y != NULL);
+
+ *position_x = sample_point->priv->position_x;
+ *position_y = sample_point->priv->position_y;
+}
+
+void
+gimp_sample_point_set_position (GimpSamplePoint *sample_point,
+ gint position_x,
+ gint position_y)
+{
+ g_return_if_fail (GIMP_IS_SAMPLE_POINT (sample_point));
+
+ if (sample_point->priv->position_x != position_x ||
+ sample_point->priv->position_y != position_y)
+ {
+ sample_point->priv->position_x = position_x;
+ sample_point->priv->position_y = position_y;
+
+ g_object_freeze_notify (G_OBJECT (sample_point));
+
+ g_object_notify (G_OBJECT (sample_point), "position-x");
+ g_object_notify (G_OBJECT (sample_point), "position-y");
+
+ g_object_thaw_notify (G_OBJECT (sample_point));
+ }
+}
+
+GimpColorPickMode
+gimp_sample_point_get_pick_mode (GimpSamplePoint *sample_point)
+{
+ g_return_val_if_fail (GIMP_IS_SAMPLE_POINT (sample_point),
+ GIMP_COLOR_PICK_MODE_PIXEL);
+
+ return sample_point->priv->pick_mode;
+}
+
+void
+gimp_sample_point_set_pick_mode (GimpSamplePoint *sample_point,
+ GimpColorPickMode pick_mode)
+{
+ g_return_if_fail (GIMP_IS_SAMPLE_POINT (sample_point));
+
+ if (sample_point->priv->pick_mode != pick_mode)
+ {
+ sample_point->priv->pick_mode = pick_mode;
+
+ g_object_notify (G_OBJECT (sample_point), "pick-mode");
+ }
+}
diff --git a/app/core/gimpsamplepoint.h b/app/core/gimpsamplepoint.h
new file mode 100644
index 0000000..dddd328
--- /dev/null
+++ b/app/core/gimpsamplepoint.h
@@ -0,0 +1,68 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_SAMPLE_POINT_H__
+#define __GIMP_SAMPLE_POINT_H__
+
+
+#include "gimpauxitem.h"
+
+
+#define GIMP_SAMPLE_POINT_POSITION_UNDEFINED G_MININT
+
+
+#define GIMP_TYPE_SAMPLE_POINT (gimp_sample_point_get_type ())
+#define GIMP_SAMPLE_POINT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_SAMPLE_POINT, GimpSamplePoint))
+#define GIMP_SAMPLE_POINT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_SAMPLE_POINT, GimpSamplePointClass))
+#define GIMP_IS_SAMPLE_POINT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_SAMPLE_POINT))
+#define GIMP_IS_SAMPLE_POINT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_SAMPLE_POINT))
+#define GIMP_SAMPLE_POINT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_SAMPLE_POINT, GimpSamplePointClass))
+
+
+typedef struct _GimpSamplePointPrivate GimpSamplePointPrivate;
+typedef struct _GimpSamplePointClass GimpSamplePointClass;
+
+struct _GimpSamplePoint
+{
+ GimpAuxItem parent_instance;
+
+ GimpSamplePointPrivate *priv;
+};
+
+struct _GimpSamplePointClass
+{
+ GimpAuxItemClass parent_class;
+};
+
+
+GType gimp_sample_point_get_type (void) G_GNUC_CONST;
+
+GimpSamplePoint * gimp_sample_point_new (guint32 sample_point_ID);
+
+void gimp_sample_point_get_position (GimpSamplePoint *sample_point,
+ gint *position_x,
+ gint *position_y);
+void gimp_sample_point_set_position (GimpSamplePoint *sample_point,
+ gint position_x,
+ gint position_y);
+
+GimpColorPickMode gimp_sample_point_get_pick_mode (GimpSamplePoint *sample_point);
+void gimp_sample_point_set_pick_mode (GimpSamplePoint *sample_point,
+ GimpColorPickMode pick_mode);
+
+
+#endif /* __GIMP_SAMPLE_POINT_H__ */
diff --git a/app/core/gimpsamplepointundo.c b/app/core/gimpsamplepointundo.c
new file mode 100644
index 0000000..89cd8ae
--- /dev/null
+++ b/app/core/gimpsamplepointundo.c
@@ -0,0 +1,121 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "core-types.h"
+
+#include "gimpimage.h"
+#include "gimpimage-sample-points.h"
+#include "gimpsamplepoint.h"
+#include "gimpsamplepointundo.h"
+
+
+static void gimp_sample_point_undo_constructed (GObject *object);
+
+static void gimp_sample_point_undo_pop (GimpUndo *undo,
+ GimpUndoMode undo_mode,
+ GimpUndoAccumulator *accum);
+
+
+G_DEFINE_TYPE (GimpSamplePointUndo, gimp_sample_point_undo,
+ GIMP_TYPE_AUX_ITEM_UNDO)
+
+#define parent_class gimp_sample_point_undo_parent_class
+
+
+static void
+gimp_sample_point_undo_class_init (GimpSamplePointUndoClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpUndoClass *undo_class = GIMP_UNDO_CLASS (klass);
+
+ object_class->constructed = gimp_sample_point_undo_constructed;
+
+ undo_class->pop = gimp_sample_point_undo_pop;
+}
+
+static void
+gimp_sample_point_undo_init (GimpSamplePointUndo *undo)
+{
+}
+
+static void
+gimp_sample_point_undo_constructed (GObject *object)
+{
+ GimpSamplePointUndo *sample_point_undo = GIMP_SAMPLE_POINT_UNDO (object);
+ GimpSamplePoint *sample_point;
+
+ G_OBJECT_CLASS (parent_class)->constructed (object);
+
+ sample_point = GIMP_SAMPLE_POINT (GIMP_AUX_ITEM_UNDO (object)->aux_item);
+
+ gimp_assert (GIMP_IS_SAMPLE_POINT (sample_point));
+
+ gimp_sample_point_get_position (sample_point,
+ &sample_point_undo->x,
+ &sample_point_undo->y);
+ sample_point_undo->pick_mode = gimp_sample_point_get_pick_mode (sample_point);
+}
+
+static void
+gimp_sample_point_undo_pop (GimpUndo *undo,
+ GimpUndoMode undo_mode,
+ GimpUndoAccumulator *accum)
+{
+ GimpSamplePointUndo *sample_point_undo = GIMP_SAMPLE_POINT_UNDO (undo);
+ GimpSamplePoint *sample_point;
+ gint x;
+ gint y;
+ GimpColorPickMode pick_mode;
+
+ GIMP_UNDO_CLASS (parent_class)->pop (undo, undo_mode, accum);
+
+ sample_point = GIMP_SAMPLE_POINT (GIMP_AUX_ITEM_UNDO (undo)->aux_item);
+
+ gimp_sample_point_get_position (sample_point, &x, &y);
+ pick_mode = gimp_sample_point_get_pick_mode (sample_point);
+
+ if (x == GIMP_SAMPLE_POINT_POSITION_UNDEFINED)
+ {
+ gimp_image_add_sample_point (undo->image,
+ sample_point,
+ sample_point_undo->x,
+ sample_point_undo->y);
+ }
+ else if (sample_point_undo->x == GIMP_SAMPLE_POINT_POSITION_UNDEFINED)
+ {
+ gimp_image_remove_sample_point (undo->image, sample_point, FALSE);
+ }
+ else
+ {
+ gimp_sample_point_set_position (sample_point,
+ sample_point_undo->x,
+ sample_point_undo->y);
+ gimp_sample_point_set_pick_mode (sample_point,
+ sample_point_undo->pick_mode);
+
+ gimp_image_sample_point_moved (undo->image, sample_point);
+ }
+
+ sample_point_undo->x = x;
+ sample_point_undo->y = y;
+ sample_point_undo->pick_mode = pick_mode;
+}
diff --git a/app/core/gimpsamplepointundo.h b/app/core/gimpsamplepointundo.h
new file mode 100644
index 0000000..10a3e90
--- /dev/null
+++ b/app/core/gimpsamplepointundo.h
@@ -0,0 +1,54 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_SAMPLE_POINT_UNDO_H__
+#define __GIMP_SAMPLE_POINT_UNDO_H__
+
+
+#include "gimpauxitemundo.h"
+
+
+#define GIMP_TYPE_SAMPLE_POINT_UNDO (gimp_sample_point_undo_get_type ())
+#define GIMP_SAMPLE_POINT_UNDO(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_SAMPLE_POINT_UNDO, GimpSamplePointUndo))
+#define GIMP_SAMPLE_POINT_UNDO_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_SAMPLE_POINT_UNDO, GimpSamplePointUndoClass))
+#define GIMP_IS_SAMPLE_POINT_UNDO(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_SAMPLE_POINT_UNDO))
+#define GIMP_IS_SAMPLE_POINT_UNDO_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_SAMPLE_POINT_UNDO))
+#define GIMP_SAMPLE_POINT_UNDO_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_SAMPLE_POINT_UNDO, GimpSamplePointUndoClass))
+
+
+typedef struct _GimpSamplePointUndo GimpSamplePointUndo;
+typedef struct _GimpSamplePointUndoClass GimpSamplePointUndoClass;
+
+struct _GimpSamplePointUndo
+{
+ GimpAuxItemUndo parent_instance;
+
+ gint x;
+ gint y;
+ GimpColorPickMode pick_mode;
+};
+
+struct _GimpSamplePointUndoClass
+{
+ GimpAuxItemUndoClass parent_class;
+};
+
+
+GType gimp_sample_point_undo_get_type (void) G_GNUC_CONST;
+
+
+#endif /* __GIMP_SAMPLE_POINT_UNDO_H__ */
diff --git a/app/core/gimpscanconvert.c b/app/core/gimpscanconvert.c
new file mode 100644
index 0000000..ed5859c
--- /dev/null
+++ b/app/core/gimpscanconvert.c
@@ -0,0 +1,647 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-1999 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include <cairo.h>
+
+#include "libgimpbase/gimpbase.h"
+#include "libgimpmath/gimpmath.h"
+
+#include "core-types.h"
+
+#include "gimpboundary.h"
+#include "gimpbezierdesc.h"
+#include "gimpscanconvert.h"
+
+
+struct _GimpScanConvert
+{
+ gdouble ratio_xy;
+
+ gboolean clip;
+ gint clip_x;
+ gint clip_y;
+ gint clip_w;
+ gint clip_h;
+
+ /* stroking options */
+ gboolean do_stroke;
+ gdouble width;
+ GimpJoinStyle join;
+ GimpCapStyle cap;
+ gdouble miter;
+ gdouble dash_offset;
+ GArray *dash_info;
+
+ GArray *path_data;
+};
+
+
+/* public functions */
+
+/**
+ * gimp_scan_convert_new:
+ *
+ * Create a new scan conversion context.
+ *
+ * Return value: a newly allocated #GimpScanConvert context.
+ */
+GimpScanConvert *
+gimp_scan_convert_new (void)
+{
+ GimpScanConvert *sc = g_slice_new0 (GimpScanConvert);
+
+ sc->path_data = g_array_new (FALSE, FALSE, sizeof (cairo_path_data_t));
+ sc->ratio_xy = 1.0;
+
+ return sc;
+}
+
+GimpScanConvert *
+gimp_scan_convert_new_from_boundary (const GimpBoundSeg *bound_segs,
+ gint n_bound_segs,
+ gint offset_x,
+ gint offset_y)
+{
+ g_return_val_if_fail (bound_segs == NULL || n_bound_segs != 0, NULL);
+
+ if (bound_segs)
+ {
+ GimpBoundSeg *stroke_segs;
+ gint n_stroke_segs;
+
+ stroke_segs = gimp_boundary_sort (bound_segs, n_bound_segs,
+ &n_stroke_segs);
+
+ if (stroke_segs)
+ {
+ GimpBezierDesc *bezier;
+
+ bezier = gimp_bezier_desc_new_from_bound_segs (stroke_segs,
+ n_bound_segs,
+ n_stroke_segs);
+
+ g_free (stroke_segs);
+
+ if (bezier)
+ {
+ GimpScanConvert *scan_convert;
+
+ scan_convert = gimp_scan_convert_new ();
+
+ gimp_bezier_desc_translate (bezier, offset_x, offset_y);
+ gimp_scan_convert_add_bezier (scan_convert, bezier);
+
+ gimp_bezier_desc_free (bezier);
+
+ return scan_convert;
+ }
+ }
+ }
+
+ return NULL;
+}
+
+/**
+ * gimp_scan_convert_free:
+ * @sc: a #GimpScanConvert context
+ *
+ * Frees the resources allocated for @sc.
+ */
+void
+gimp_scan_convert_free (GimpScanConvert *sc)
+{
+ g_return_if_fail (sc != NULL);
+
+ if (sc->path_data)
+ g_array_free (sc->path_data, TRUE);
+
+ if (sc->dash_info)
+ g_array_free (sc->dash_info, TRUE);
+
+ g_slice_free (GimpScanConvert, sc);
+}
+
+/**
+ * gimp_scan_convert_set_pixel_ratio:
+ * @sc: a #GimpScanConvert context
+ * @ratio_xy: the aspect ratio of the major coordinate axes
+ *
+ * Sets the pixel aspect ratio.
+ */
+void
+gimp_scan_convert_set_pixel_ratio (GimpScanConvert *sc,
+ gdouble ratio_xy)
+{
+ g_return_if_fail (sc != NULL);
+
+ /* we only need the relative resolution */
+ sc->ratio_xy = ratio_xy;
+}
+
+/**
+ * gimp_scan_convert_set_clip_rectangle
+ * @sc: a #GimpScanConvert context
+ * @x: horizontal offset of clip rectangle
+ * @y: vertical offset of clip rectangle
+ * @width: width of clip rectangle
+ * @height: height of clip rectangle
+ *
+ * Sets a clip rectangle on @sc. Subsequent render operations will be
+ * restricted to this area.
+ */
+void
+gimp_scan_convert_set_clip_rectangle (GimpScanConvert *sc,
+ gint x,
+ gint y,
+ gint width,
+ gint height)
+{
+ g_return_if_fail (sc != NULL);
+
+ sc->clip = TRUE;
+ sc->clip_x = x;
+ sc->clip_y = y;
+ sc->clip_w = width;
+ sc->clip_h = height;
+}
+
+/**
+ * gimp_scan_convert_add_polyline:
+ * @sc: a #GimpScanConvert context
+ * @n_points: number of points to add
+ * @points: array of points to add
+ * @closed: whether to close the polyline and make it a polygon
+ *
+ * Add a polyline with @n_points @points that may be open or closed.
+ *
+ * Please note that you should use gimp_scan_convert_stroke() if you
+ * specify open polygons.
+ */
+void
+gimp_scan_convert_add_polyline (GimpScanConvert *sc,
+ guint n_points,
+ const GimpVector2 *points,
+ gboolean closed)
+{
+ GimpVector2 prev = { 0.0, 0.0, };
+ cairo_path_data_t pd;
+ gint i;
+
+ g_return_if_fail (sc != NULL);
+ g_return_if_fail (points != NULL);
+ g_return_if_fail (n_points > 0);
+
+ for (i = 0; i < n_points; i++)
+ {
+ /* compress multiple identical coordinates */
+ if (i == 0 ||
+ prev.x != points[i].x ||
+ prev.y != points[i].y)
+ {
+ pd.header.type = (i == 0) ? CAIRO_PATH_MOVE_TO : CAIRO_PATH_LINE_TO;
+ pd.header.length = 2;
+ sc->path_data = g_array_append_val (sc->path_data, pd);
+
+ pd.point.x = points[i].x;
+ pd.point.y = points[i].y;
+ sc->path_data = g_array_append_val (sc->path_data, pd);
+ prev = points[i];
+ }
+ }
+
+ /* close the polyline when needed */
+ if (closed)
+ {
+ pd.header.type = CAIRO_PATH_CLOSE_PATH;
+ pd.header.length = 1;
+ sc->path_data = g_array_append_val (sc->path_data, pd);
+ }
+}
+
+/**
+ * gimp_scan_convert_add_polyline:
+ * @sc: a #GimpScanConvert context
+ * @bezier: a #GimpBezierDesc
+ *
+ * Adds a @bezier path to @sc.
+ *
+ * Please note that you should use gimp_scan_convert_stroke() if you
+ * specify open paths.
+ **/
+void
+gimp_scan_convert_add_bezier (GimpScanConvert *sc,
+ const GimpBezierDesc *bezier)
+{
+ g_return_if_fail (sc != NULL);
+ g_return_if_fail (bezier != NULL);
+
+ sc->path_data = g_array_append_vals (sc->path_data,
+ bezier->data, bezier->num_data);
+}
+
+/**
+ * gimp_scan_convert_stroke:
+ * @sc: a #GimpScanConvert context
+ * @width: line width in pixels
+ * @join: how lines should be joined
+ * @cap: how to render the end of lines
+ * @miter: convert a mitered join to a bevelled join if the miter would
+ * extend to a distance of more than @miter times @width from
+ * the actual join point
+ * @dash_offset: offset to apply on the dash pattern
+ * @dash_info: dash pattern or %NULL for a solid line
+ *
+ * Stroke the content of a GimpScanConvert. The next
+ * gimp_scan_convert_render() will result in the outline of the
+ * polygon defined with the commands above.
+ *
+ * You cannot add additional polygons after this command.
+ *
+ * Note that if you have nonstandard resolution, "width" gives the
+ * width (in pixels) for a vertical stroke, i.e. use the X resolution
+ * to calculate the width of a stroke when operating with real world
+ * units.
+ */
+void
+gimp_scan_convert_stroke (GimpScanConvert *sc,
+ gdouble width,
+ GimpJoinStyle join,
+ GimpCapStyle cap,
+ gdouble miter,
+ gdouble dash_offset,
+ GArray *dash_info)
+{
+ sc->do_stroke = TRUE;
+ sc->width = width;
+ sc->join = join;
+ sc->cap = cap;
+ sc->miter = miter;
+
+ if (sc->dash_info)
+ {
+ g_array_free (sc->dash_info, TRUE);
+ sc->dash_info = NULL;
+ }
+
+ if (dash_info && dash_info->len >= 2)
+ {
+ gint n_dashes;
+ gdouble *dashes;
+ gint i;
+
+ dash_offset = dash_offset * MAX (width, 1.0);
+
+ n_dashes = dash_info->len;
+ dashes = g_new (gdouble, dash_info->len);
+
+ for (i = 0; i < dash_info->len ; i++)
+ dashes[i] = MAX (width, 1.0) * g_array_index (dash_info, gdouble, i);
+
+ /* correct 0.0 in the first element (starts with a gap) */
+
+ if (dashes[0] == 0.0)
+ {
+ gdouble first;
+
+ first = dashes[1];
+
+ /* shift the pattern to really starts with a dash and
+ * use the offset to skip into it.
+ */
+ for (i = 0; i < dash_info->len - 2; i++)
+ {
+ dashes[i] = dashes[i+2];
+ dash_offset += dashes[i];
+ }
+
+ if (dash_info->len % 2 == 1)
+ {
+ dashes[dash_info->len - 2] = first;
+ n_dashes --;
+ }
+ else if (dash_info->len > 2)
+ {
+ dashes [dash_info->len - 3] += first;
+ n_dashes -= 2;
+ }
+ }
+
+ /* correct odd number of dash specifiers */
+
+ if (n_dashes % 2 == 1)
+ {
+ gdouble last;
+
+ last = dashes[n_dashes - 1];
+ dashes[0] += last;
+ dash_offset += last;
+ n_dashes --;
+ }
+
+ if (n_dashes >= 2)
+ {
+ sc->dash_info = g_array_sized_new (FALSE, FALSE,
+ sizeof (gdouble), n_dashes);
+ sc->dash_info = g_array_append_vals (sc->dash_info, dashes, n_dashes);
+ sc->dash_offset = dash_offset;
+ }
+
+ g_free (dashes);
+ }
+}
+
+
+/**
+ * gimp_scan_convert_render:
+ * @sc: a #GimpScanConvert context
+ * @buffer: the #GeglBuffer to render to
+ * @off_x: horizontal offset into the @buffer
+ * @off_y: vertical offset into the @buffer
+ * @antialias: whether to apply antialiasiing
+ *
+ * This is a wrapper around gimp_scan_convert_render_full() that replaces the
+ * content of the @buffer with a rendered form of the path passed in.
+ *
+ * You cannot add additional polygons after this command.
+ */
+void
+gimp_scan_convert_render (GimpScanConvert *sc,
+ GeglBuffer *buffer,
+ gint off_x,
+ gint off_y,
+ gboolean antialias)
+{
+ gimp_scan_convert_render_full (sc, buffer, off_x, off_y,
+ TRUE, antialias, 1.0);
+}
+
+/**
+ * gimp_scan_convert_render_value:
+ * @sc: a #GimpScanConvert context
+ * @buffer: the #GeglBuffer to render to
+ * @off_x: horizontal offset into the @buffer
+ * @off_y: vertical offset into the @buffer
+ * @value: value to use for covered pixels
+ *
+ * This is a wrapper around gimp_scan_convert_render_full() that
+ * doesn't do antialiasing but gives control over the value that
+ * should be used for pixels covered by the scan conversion. Uncovered
+ * pixels are set to zero.
+ *
+ * You cannot add additional polygons after this command.
+ */
+void
+gimp_scan_convert_render_value (GimpScanConvert *sc,
+ GeglBuffer *buffer,
+ gint off_x,
+ gint off_y,
+ gdouble value)
+{
+ gimp_scan_convert_render_full (sc, buffer, off_x, off_y,
+ TRUE, FALSE, value);
+}
+
+/**
+ * gimp_scan_convert_compose:
+ * @sc: a #GimpScanConvert context
+ * @buffer: the #GeglBuffer to render to
+ * @off_x: horizontal offset into the @buffer
+ * @off_y: vertical offset into the @buffer
+ *
+ * This is a wrapper around of gimp_scan_convert_render_full() that composes
+ * the (aliased) scan conversion on top of the content of the @buffer.
+ *
+ * You cannot add additional polygons after this command.
+ */
+void
+gimp_scan_convert_compose (GimpScanConvert *sc,
+ GeglBuffer *buffer,
+ gint off_x,
+ gint off_y)
+{
+ gimp_scan_convert_render_full (sc, buffer, off_x, off_y,
+ FALSE, FALSE, 1.0);
+}
+
+/**
+ * gimp_scan_convert_compose_value:
+ * @sc: a #GimpScanConvert context
+ * @buffer: the #GeglBuffer to render to
+ * @off_x: horizontal offset into the @buffer
+ * @off_y: vertical offset into the @buffer
+ * @value: value to use for covered pixels
+ *
+ * This is a wrapper around gimp_scan_convert_render_full() that
+ * composes the (aliased) scan conversion with value @value on top of the
+ * content of the @buffer.
+ *
+ * You cannot add additional polygons after this command.
+ */
+void
+gimp_scan_convert_compose_value (GimpScanConvert *sc,
+ GeglBuffer *buffer,
+ gint off_x,
+ gint off_y,
+ gdouble value)
+{
+ gimp_scan_convert_render_full (sc, buffer, off_x, off_y,
+ FALSE, FALSE, value);
+}
+
+/**
+ * gimp_scan_convert_render_full:
+ * @sc: a #GimpScanConvert context
+ * @buffer: the #GeglBuffer to render to
+ * @off_x: horizontal offset into the @buffer
+ * @off_y: vertical offset into the @buffer
+ * @replace: if true the original content of the @buffer gets estroyed
+ * @antialias: if true the rendering happens antialiased
+ * @value: value to use for covered pixels
+ *
+ * This function renders the area described by the path to the
+ * @buffer, taking the offset @off_x and @off_y in the buffer into
+ * account. The rendering can happen antialiased and be rendered on
+ * top of existing content or replacing it completely. The @value
+ * specifies the opacity value to be used for the objects in the @sc.
+ *
+ * You cannot add additional polygons after this command.
+ */
+void
+gimp_scan_convert_render_full (GimpScanConvert *sc,
+ GeglBuffer *buffer,
+ gint off_x,
+ gint off_y,
+ gboolean replace,
+ gboolean antialias,
+ gdouble value)
+{
+ const Babl *format;
+ guchar *shared_buf = NULL;
+ gsize shared_buf_size = 0;
+ GeglBufferIterator *iter;
+ GeglRectangle *roi;
+ cairo_t *cr;
+ cairo_surface_t *surface;
+ cairo_path_t path;
+ gint bpp;
+ gint x, y;
+ gint width, height;
+
+ g_return_if_fail (sc != NULL);
+ g_return_if_fail (GEGL_IS_BUFFER (buffer));
+
+ x = gegl_buffer_get_x (buffer);
+ y = gegl_buffer_get_y (buffer);
+ width = gegl_buffer_get_width (buffer);
+ height = gegl_buffer_get_height (buffer);
+
+ if (sc->clip && ! gimp_rectangle_intersect (x, y, width, height,
+ sc->clip_x, sc->clip_y,
+ sc->clip_w, sc->clip_h,
+ &x, &y, &width, &height))
+ return;
+
+ path.status = CAIRO_STATUS_SUCCESS;
+ path.data = (cairo_path_data_t *) sc->path_data->data;
+ path.num_data = sc->path_data->len;
+
+ format = babl_format ("Y u8");
+ bpp = babl_format_get_bytes_per_pixel (format);
+
+ iter = gegl_buffer_iterator_new (buffer, NULL, 0, format,
+ GEGL_ACCESS_READWRITE, GEGL_ABYSS_NONE, 1);
+ roi = &iter->items[0].roi;
+
+ while (gegl_buffer_iterator_next (iter))
+ {
+ guchar *data = iter->items[0].data;
+ guchar *tmp_buf = NULL;
+ const gint stride = cairo_format_stride_for_width (CAIRO_FORMAT_A8,
+ roi->width);
+
+ /* cairo rowstrides are always multiples of 4, whereas
+ * maskPR.rowstride can be anything, so to be able to create an
+ * image surface, we maybe have to create our own temporary
+ * buffer
+ */
+ if (roi->width * bpp != stride)
+ {
+ if (shared_buf_size < stride * roi->height)
+ {
+ shared_buf_size = stride * roi->height;
+ g_free (shared_buf);
+ shared_buf = g_malloc (shared_buf_size);
+ }
+ tmp_buf = shared_buf;
+
+ if (! replace)
+ {
+ const guchar *src = data;
+ guchar *dest = tmp_buf;
+ gint i;
+
+ for (i = 0; i < roi->height; i++)
+ {
+ memcpy (dest, src, roi->width * bpp);
+
+ src += roi->width * bpp;
+ dest += stride;
+ }
+ }
+ }
+
+ surface = cairo_image_surface_create_for_data (tmp_buf ?
+ tmp_buf : data,
+ CAIRO_FORMAT_A8,
+ roi->width, roi->height,
+ stride);
+
+ cairo_surface_set_device_offset (surface,
+ -off_x - roi->x,
+ -off_y - roi->y);
+ cr = cairo_create (surface);
+ cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
+
+ if (replace)
+ {
+ cairo_set_source_rgba (cr, 0, 0, 0, 0);
+ cairo_paint (cr);
+ }
+
+ cairo_set_source_rgba (cr, 0, 0, 0, value);
+ cairo_append_path (cr, &path);
+
+ cairo_set_antialias (cr, antialias ?
+ CAIRO_ANTIALIAS_GRAY : CAIRO_ANTIALIAS_NONE);
+ cairo_set_miter_limit (cr, sc->miter);
+
+ if (sc->do_stroke)
+ {
+ cairo_set_line_cap (cr,
+ sc->cap == GIMP_CAP_BUTT ? CAIRO_LINE_CAP_BUTT :
+ sc->cap == GIMP_CAP_ROUND ? CAIRO_LINE_CAP_ROUND :
+ CAIRO_LINE_CAP_SQUARE);
+ cairo_set_line_join (cr,
+ sc->join == GIMP_JOIN_MITER ? CAIRO_LINE_JOIN_MITER :
+ sc->join == GIMP_JOIN_ROUND ? CAIRO_LINE_JOIN_ROUND :
+ CAIRO_LINE_JOIN_BEVEL);
+
+ cairo_set_line_width (cr, sc->width);
+
+ if (sc->dash_info)
+ cairo_set_dash (cr,
+ (double *) sc->dash_info->data,
+ sc->dash_info->len,
+ sc->dash_offset);
+
+ cairo_scale (cr, 1.0, sc->ratio_xy);
+ cairo_stroke (cr);
+ }
+ else
+ {
+ cairo_set_fill_rule (cr, CAIRO_FILL_RULE_EVEN_ODD);
+ cairo_fill (cr);
+ }
+
+ cairo_destroy (cr);
+ cairo_surface_destroy (surface);
+
+ if (tmp_buf)
+ {
+ const guchar *src = tmp_buf;
+ guchar *dest = data;
+ gint i;
+
+ for (i = 0; i < roi->height; i++)
+ {
+ memcpy (dest, src, roi->width * bpp);
+
+ src += stride;
+ dest += roi->width * bpp;
+ }
+ }
+ }
+
+ g_free (shared_buf);
+}
diff --git a/app/core/gimpscanconvert.h b/app/core/gimpscanconvert.h
new file mode 100644
index 0000000..9df4da1
--- /dev/null
+++ b/app/core/gimpscanconvert.h
@@ -0,0 +1,81 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-1999 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_SCAN_CONVERT_H__
+#define __GIMP_SCAN_CONVERT_H__
+
+
+GimpScanConvert *
+ gimp_scan_convert_new (void);
+
+GimpScanConvert *
+ gimp_scan_convert_new_from_boundary (const GimpBoundSeg *bound_segs,
+ gint n_bound_segs,
+ gint offset_x,
+ gint offset_y);
+
+void gimp_scan_convert_free (GimpScanConvert *sc);
+void gimp_scan_convert_set_pixel_ratio (GimpScanConvert *sc,
+ gdouble ratio_xy);
+void gimp_scan_convert_set_clip_rectangle (GimpScanConvert *sc,
+ gint x,
+ gint y,
+ gint width,
+ gint height);
+void gimp_scan_convert_add_polyline (GimpScanConvert *sc,
+ guint n_points,
+ const GimpVector2 *points,
+ gboolean closed);
+void gimp_scan_convert_add_bezier (GimpScanConvert *sc,
+ const GimpBezierDesc *bezier);
+void gimp_scan_convert_stroke (GimpScanConvert *sc,
+ gdouble width,
+ GimpJoinStyle join,
+ GimpCapStyle cap,
+ gdouble miter,
+ gdouble dash_offset,
+ GArray *dash_info);
+void gimp_scan_convert_render_full (GimpScanConvert *sc,
+ GeglBuffer *buffer,
+ gint off_x,
+ gint off_y,
+ gboolean replace,
+ gboolean antialias,
+ gdouble value);
+
+void gimp_scan_convert_render (GimpScanConvert *sc,
+ GeglBuffer *buffer,
+ gint off_x,
+ gint off_y,
+ gboolean antialias);
+void gimp_scan_convert_render_value (GimpScanConvert *sc,
+ GeglBuffer *buffer,
+ gint off_x,
+ gint off_y,
+ gdouble value);
+void gimp_scan_convert_compose (GimpScanConvert *sc,
+ GeglBuffer *buffer,
+ gint off_x,
+ gint off_y);
+void gimp_scan_convert_compose_value (GimpScanConvert *sc,
+ GeglBuffer *buffer,
+ gint off_x,
+ gint off_y,
+ gdouble value);
+
+
+#endif /* __GIMP_SCAN_CONVERT_H__ */
diff --git a/app/core/gimpselection.c b/app/core/gimpselection.c
new file mode 100644
index 0000000..5f66b98
--- /dev/null
+++ b/app/core/gimpselection.c
@@ -0,0 +1,863 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <cairo.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpcolor/gimpcolor.h"
+
+#include "core-types.h"
+
+#include "gegl/gimp-babl.h"
+#include "gegl/gimp-gegl-apply-operation.h"
+#include "gegl/gimp-gegl-loops.h"
+
+#include "gimp.h"
+#include "gimpcontext.h"
+#include "gimpdrawable-edit.h"
+#include "gimpdrawable-private.h"
+#include "gimperror.h"
+#include "gimpimage.h"
+#include "gimpimage-undo.h"
+#include "gimpimage-undo-push.h"
+#include "gimplayer.h"
+#include "gimplayer-new.h"
+#include "gimplayermask.h"
+#include "gimplayer-floating-selection.h"
+#include "gimppickable.h"
+#include "gimpselection.h"
+
+#include "gimp-intl.h"
+
+
+static gboolean gimp_selection_is_attached (GimpItem *item);
+static GimpItemTree * gimp_selection_get_tree (GimpItem *item);
+static void gimp_selection_translate (GimpItem *item,
+ gdouble offset_x,
+ gdouble offset_y,
+ gboolean push_undo);
+static void gimp_selection_scale (GimpItem *item,
+ gint new_width,
+ gint new_height,
+ gint new_offset_x,
+ gint new_offset_y,
+ GimpInterpolationType interp_type,
+ GimpProgress *progress);
+static void gimp_selection_resize (GimpItem *item,
+ GimpContext *context,
+ GimpFillType fill_type,
+ gint new_width,
+ gint new_height,
+ gint offset_x,
+ gint offset_y);
+static void gimp_selection_flip (GimpItem *item,
+ GimpContext *context,
+ GimpOrientationType flip_type,
+ gdouble axis,
+ gboolean clip_result);
+static void gimp_selection_rotate (GimpItem *item,
+ GimpContext *context,
+ GimpRotationType rotation_type,
+ gdouble center_x,
+ gdouble center_y,
+ gboolean clip_result);
+static gboolean gimp_selection_fill (GimpItem *item,
+ GimpDrawable *drawable,
+ GimpFillOptions *fill_options,
+ gboolean push_undo,
+ GimpProgress *progress,
+ GError **error);
+static gboolean gimp_selection_stroke (GimpItem *item,
+ GimpDrawable *drawable,
+ GimpStrokeOptions *stroke_options,
+ gboolean push_undo,
+ GimpProgress *progress,
+ GError **error);
+static void gimp_selection_convert_type (GimpDrawable *drawable,
+ GimpImage *dest_image,
+ const Babl *new_format,
+ GimpColorProfile *dest_profile,
+ GeglDitherMethod layer_dither_type,
+ GeglDitherMethod mask_dither_type,
+ gboolean push_undo,
+ GimpProgress *progress);
+static void gimp_selection_invalidate_boundary (GimpDrawable *drawable);
+
+static gboolean gimp_selection_boundary (GimpChannel *channel,
+ const GimpBoundSeg **segs_in,
+ const GimpBoundSeg **segs_out,
+ gint *num_segs_in,
+ gint *num_segs_out,
+ gint x1,
+ gint y1,
+ gint x2,
+ gint y2);
+static gboolean gimp_selection_is_empty (GimpChannel *channel);
+static void gimp_selection_feather (GimpChannel *channel,
+ gdouble radius_x,
+ gdouble radius_y,
+ gboolean edge_lock,
+ gboolean push_undo);
+static void gimp_selection_sharpen (GimpChannel *channel,
+ gboolean push_undo);
+static void gimp_selection_clear (GimpChannel *channel,
+ const gchar *undo_desc,
+ gboolean push_undo);
+static void gimp_selection_all (GimpChannel *channel,
+ gboolean push_undo);
+static void gimp_selection_invert (GimpChannel *channel,
+ gboolean push_undo);
+static void gimp_selection_border (GimpChannel *channel,
+ gint radius_x,
+ gint radius_y,
+ GimpChannelBorderStyle style,
+ gboolean edge_lock,
+ gboolean push_undo);
+static void gimp_selection_grow (GimpChannel *channel,
+ gint radius_x,
+ gint radius_y,
+ gboolean push_undo);
+static void gimp_selection_shrink (GimpChannel *channel,
+ gint radius_x,
+ gint radius_y,
+ gboolean edge_lock,
+ gboolean push_undo);
+static void gimp_selection_flood (GimpChannel *channel,
+ gboolean push_undo);
+
+
+G_DEFINE_TYPE (GimpSelection, gimp_selection, GIMP_TYPE_CHANNEL)
+
+#define parent_class gimp_selection_parent_class
+
+
+static void
+gimp_selection_class_init (GimpSelectionClass *klass)
+{
+ GimpViewableClass *viewable_class = GIMP_VIEWABLE_CLASS (klass);
+ GimpItemClass *item_class = GIMP_ITEM_CLASS (klass);
+ GimpDrawableClass *drawable_class = GIMP_DRAWABLE_CLASS (klass);
+ GimpChannelClass *channel_class = GIMP_CHANNEL_CLASS (klass);
+
+ viewable_class->default_icon_name = "gimp-selection";
+
+ item_class->is_attached = gimp_selection_is_attached;
+ item_class->get_tree = gimp_selection_get_tree;
+ item_class->translate = gimp_selection_translate;
+ item_class->scale = gimp_selection_scale;
+ item_class->resize = gimp_selection_resize;
+ item_class->flip = gimp_selection_flip;
+ item_class->rotate = gimp_selection_rotate;
+ item_class->fill = gimp_selection_fill;
+ item_class->stroke = gimp_selection_stroke;
+ item_class->default_name = _("Selection Mask");
+ item_class->translate_desc = C_("undo-type", "Move Selection");
+ item_class->fill_desc = C_("undo-type", "Fill Selection");
+ item_class->stroke_desc = C_("undo-type", "Stroke Selection");
+
+ drawable_class->convert_type = gimp_selection_convert_type;
+ drawable_class->invalidate_boundary = gimp_selection_invalidate_boundary;
+
+ channel_class->boundary = gimp_selection_boundary;
+ channel_class->is_empty = gimp_selection_is_empty;
+ channel_class->feather = gimp_selection_feather;
+ channel_class->sharpen = gimp_selection_sharpen;
+ channel_class->clear = gimp_selection_clear;
+ channel_class->all = gimp_selection_all;
+ channel_class->invert = gimp_selection_invert;
+ channel_class->border = gimp_selection_border;
+ channel_class->grow = gimp_selection_grow;
+ channel_class->shrink = gimp_selection_shrink;
+ channel_class->flood = gimp_selection_flood;
+
+ channel_class->feather_desc = C_("undo-type", "Feather Selection");
+ channel_class->sharpen_desc = C_("undo-type", "Sharpen Selection");
+ channel_class->clear_desc = C_("undo-type", "Select None");
+ channel_class->all_desc = C_("undo-type", "Select All");
+ channel_class->invert_desc = C_("undo-type", "Invert Selection");
+ channel_class->border_desc = C_("undo-type", "Border Selection");
+ channel_class->grow_desc = C_("undo-type", "Grow Selection");
+ channel_class->shrink_desc = C_("undo-type", "Shrink Selection");
+ channel_class->flood_desc = C_("undo-type", "Remove Holes");
+}
+
+static void
+gimp_selection_init (GimpSelection *selection)
+{
+}
+
+static gboolean
+gimp_selection_is_attached (GimpItem *item)
+{
+ return (GIMP_IS_IMAGE (gimp_item_get_image (item)) &&
+ gimp_image_get_mask (gimp_item_get_image (item)) ==
+ GIMP_CHANNEL (item));
+}
+
+static GimpItemTree *
+gimp_selection_get_tree (GimpItem *item)
+{
+ return NULL;
+}
+
+static void
+gimp_selection_translate (GimpItem *item,
+ gdouble offset_x,
+ gdouble offset_y,
+ gboolean push_undo)
+{
+ GIMP_ITEM_CLASS (parent_class)->translate (item, offset_x, offset_y,
+ push_undo);
+}
+
+static void
+gimp_selection_scale (GimpItem *item,
+ gint new_width,
+ gint new_height,
+ gint new_offset_x,
+ gint new_offset_y,
+ GimpInterpolationType interp_type,
+ GimpProgress *progress)
+{
+ GIMP_ITEM_CLASS (parent_class)->scale (item, new_width, new_height,
+ new_offset_x, new_offset_y,
+ interp_type, progress);
+
+ gimp_item_set_offset (item, 0, 0);
+}
+
+static void
+gimp_selection_resize (GimpItem *item,
+ GimpContext *context,
+ GimpFillType fill_type,
+ gint new_width,
+ gint new_height,
+ gint offset_x,
+ gint offset_y)
+{
+ GIMP_ITEM_CLASS (parent_class)->resize (item, context, GIMP_FILL_TRANSPARENT,
+ new_width, new_height,
+ offset_x, offset_y);
+
+ gimp_item_set_offset (item, 0, 0);
+}
+
+static void
+gimp_selection_flip (GimpItem *item,
+ GimpContext *context,
+ GimpOrientationType flip_type,
+ gdouble axis,
+ gboolean clip_result)
+{
+ GIMP_ITEM_CLASS (parent_class)->flip (item, context, flip_type, axis, TRUE);
+}
+
+static void
+gimp_selection_rotate (GimpItem *item,
+ GimpContext *context,
+ GimpRotationType rotation_type,
+ gdouble center_x,
+ gdouble center_y,
+ gboolean clip_result)
+{
+ GIMP_ITEM_CLASS (parent_class)->rotate (item, context, rotation_type,
+ center_x, center_y,
+ clip_result);
+}
+
+static gboolean
+gimp_selection_fill (GimpItem *item,
+ GimpDrawable *drawable,
+ GimpFillOptions *fill_options,
+ gboolean push_undo,
+ GimpProgress *progress,
+ GError **error)
+{
+ GimpSelection *selection = GIMP_SELECTION (item);
+ const GimpBoundSeg *dummy_in;
+ const GimpBoundSeg *dummy_out;
+ gint num_dummy_in;
+ gint num_dummy_out;
+ gboolean retval;
+
+ if (! gimp_channel_boundary (GIMP_CHANNEL (selection),
+ &dummy_in, &dummy_out,
+ &num_dummy_in, &num_dummy_out,
+ 0, 0, 0, 0))
+ {
+ g_set_error_literal (error, GIMP_ERROR, GIMP_FAILED,
+ _("There is no selection to fill."));
+ return FALSE;
+ }
+
+ gimp_selection_suspend (selection);
+
+ retval = GIMP_ITEM_CLASS (parent_class)->fill (item, drawable,
+ fill_options,
+ push_undo, progress, error);
+
+ gimp_selection_resume (selection);
+
+ return retval;
+}
+
+static gboolean
+gimp_selection_stroke (GimpItem *item,
+ GimpDrawable *drawable,
+ GimpStrokeOptions *stroke_options,
+ gboolean push_undo,
+ GimpProgress *progress,
+ GError **error)
+{
+ GimpSelection *selection = GIMP_SELECTION (item);
+ const GimpBoundSeg *dummy_in;
+ const GimpBoundSeg *dummy_out;
+ gint num_dummy_in;
+ gint num_dummy_out;
+ gboolean retval;
+
+ if (! gimp_channel_boundary (GIMP_CHANNEL (selection),
+ &dummy_in, &dummy_out,
+ &num_dummy_in, &num_dummy_out,
+ 0, 0, 0, 0))
+ {
+ g_set_error_literal (error, GIMP_ERROR, GIMP_FAILED,
+ _("There is no selection to stroke."));
+ return FALSE;
+ }
+
+ gimp_selection_suspend (selection);
+
+ retval = GIMP_ITEM_CLASS (parent_class)->stroke (item, drawable,
+ stroke_options,
+ push_undo, progress, error);
+
+ gimp_selection_resume (selection);
+
+ return retval;
+}
+
+static void
+gimp_selection_convert_type (GimpDrawable *drawable,
+ GimpImage *dest_image,
+ const Babl *new_format,
+ GimpColorProfile *dest_profile,
+ GeglDitherMethod layer_dither_type,
+ GeglDitherMethod mask_dither_type,
+ gboolean push_undo,
+ GimpProgress *progress)
+{
+ new_format =
+ gimp_babl_mask_format (gimp_babl_format_get_precision (new_format));
+
+ GIMP_DRAWABLE_CLASS (parent_class)->convert_type (drawable, dest_image,
+ new_format,
+ dest_profile,
+ layer_dither_type,
+ mask_dither_type,
+ push_undo,
+ progress);
+}
+
+static void
+gimp_selection_invalidate_boundary (GimpDrawable *drawable)
+{
+ GimpImage *image = gimp_item_get_image (GIMP_ITEM (drawable));
+ GimpLayer *layer;
+
+ /* Turn the current selection off */
+ gimp_image_selection_invalidate (image);
+
+ GIMP_DRAWABLE_CLASS (parent_class)->invalidate_boundary (drawable);
+
+ /* If there is a floating selection, update it's area...
+ * we need to do this since this selection mask can act as an additional
+ * mask in the composition of the floating selection
+ */
+ layer = gimp_image_get_active_layer (image);
+
+ if (layer && gimp_layer_is_floating_sel (layer))
+ {
+ gimp_drawable_update (GIMP_DRAWABLE (layer), 0, 0, -1, -1);
+ }
+
+#if 0
+ /* invalidate the preview */
+ drawable->private->preview_valid = FALSE;
+#endif
+}
+
+static gboolean
+gimp_selection_boundary (GimpChannel *channel,
+ const GimpBoundSeg **segs_in,
+ const GimpBoundSeg **segs_out,
+ gint *num_segs_in,
+ gint *num_segs_out,
+ gint unused1,
+ gint unused2,
+ gint unused3,
+ gint unused4)
+{
+ GimpImage *image = gimp_item_get_image (GIMP_ITEM (channel));
+ GimpDrawable *drawable;
+ GimpLayer *layer;
+
+ if ((layer = gimp_image_get_floating_selection (image)))
+ {
+ /* If there is a floating selection, then
+ * we need to do some slightly different boundaries.
+ * Instead of inside and outside boundaries being defined
+ * by the extents of the layer, the inside boundary (the one
+ * that actually marches and is black/white) is the boundary of
+ * the floating selection. The outside boundary (doesn't move,
+ * is black/gray) is defined as the normal selection mask
+ */
+
+ /* Find the selection mask boundary */
+ GIMP_CHANNEL_CLASS (parent_class)->boundary (channel,
+ segs_in, segs_out,
+ num_segs_in, num_segs_out,
+ 0, 0, 0, 0);
+
+ /* Find the floating selection boundary */
+ *segs_in = floating_sel_boundary (layer, num_segs_in);
+
+ return TRUE;
+ }
+ else if ((drawable = gimp_image_get_active_drawable (image)) &&
+ GIMP_IS_CHANNEL (drawable))
+ {
+ /* Otherwise, return the boundary...if a channel is active */
+
+ return GIMP_CHANNEL_CLASS (parent_class)->boundary (channel,
+ segs_in, segs_out,
+ num_segs_in,
+ num_segs_out,
+ 0, 0,
+ gimp_image_get_width (image),
+ gimp_image_get_height (image));
+ }
+ else if ((layer = gimp_image_get_active_layer (image)))
+ {
+ /* If a layer is active, we return multiple boundaries based
+ * on the extents
+ */
+
+ gint x1, y1;
+ gint x2, y2;
+ gint offset_x;
+ gint offset_y;
+
+ gimp_item_get_offset (GIMP_ITEM (layer), &offset_x, &offset_y);
+
+ x1 = CLAMP (offset_x, 0, gimp_image_get_width (image));
+ y1 = CLAMP (offset_y, 0, gimp_image_get_height (image));
+ x2 = CLAMP (offset_x + gimp_item_get_width (GIMP_ITEM (layer)),
+ 0, gimp_image_get_width (image));
+ y2 = CLAMP (offset_y + gimp_item_get_height (GIMP_ITEM (layer)),
+ 0, gimp_image_get_height (image));
+
+ return GIMP_CHANNEL_CLASS (parent_class)->boundary (channel,
+ segs_in, segs_out,
+ num_segs_in,
+ num_segs_out,
+ x1, y1, x2, y2);
+ }
+
+ *segs_in = NULL;
+ *segs_out = NULL;
+ *num_segs_in = 0;
+ *num_segs_out = 0;
+
+ return FALSE;
+}
+
+static gboolean
+gimp_selection_is_empty (GimpChannel *channel)
+{
+ GimpSelection *selection = GIMP_SELECTION (channel);
+
+ /* in order to allow stroking of selections, we need to pretend here
+ * that the selection mask is empty so that it doesn't mask the paint
+ * during the stroke operation.
+ */
+ if (selection->suspend_count > 0)
+ return TRUE;
+
+ return GIMP_CHANNEL_CLASS (parent_class)->is_empty (channel);
+}
+
+static void
+gimp_selection_feather (GimpChannel *channel,
+ gdouble radius_x,
+ gdouble radius_y,
+ gboolean edge_lock,
+ gboolean push_undo)
+{
+ GIMP_CHANNEL_CLASS (parent_class)->feather (channel, radius_x, radius_y,
+ edge_lock, push_undo);
+}
+
+static void
+gimp_selection_sharpen (GimpChannel *channel,
+ gboolean push_undo)
+{
+ GIMP_CHANNEL_CLASS (parent_class)->sharpen (channel, push_undo);
+}
+
+static void
+gimp_selection_clear (GimpChannel *channel,
+ const gchar *undo_desc,
+ gboolean push_undo)
+{
+ GIMP_CHANNEL_CLASS (parent_class)->clear (channel, undo_desc, push_undo);
+}
+
+static void
+gimp_selection_all (GimpChannel *channel,
+ gboolean push_undo)
+{
+ GIMP_CHANNEL_CLASS (parent_class)->all (channel, push_undo);
+}
+
+static void
+gimp_selection_invert (GimpChannel *channel,
+ gboolean push_undo)
+{
+ GIMP_CHANNEL_CLASS (parent_class)->invert (channel, push_undo);
+}
+
+static void
+gimp_selection_border (GimpChannel *channel,
+ gint radius_x,
+ gint radius_y,
+ GimpChannelBorderStyle style,
+ gboolean edge_lock,
+ gboolean push_undo)
+{
+ GIMP_CHANNEL_CLASS (parent_class)->border (channel,
+ radius_x, radius_y,
+ style, edge_lock,
+ push_undo);
+}
+
+static void
+gimp_selection_grow (GimpChannel *channel,
+ gint radius_x,
+ gint radius_y,
+ gboolean push_undo)
+{
+ GIMP_CHANNEL_CLASS (parent_class)->grow (channel,
+ radius_x, radius_y,
+ push_undo);
+}
+
+static void
+gimp_selection_shrink (GimpChannel *channel,
+ gint radius_x,
+ gint radius_y,
+ gboolean edge_lock,
+ gboolean push_undo)
+{
+ GIMP_CHANNEL_CLASS (parent_class)->shrink (channel,
+ radius_x, radius_y, edge_lock,
+ push_undo);
+}
+
+static void
+gimp_selection_flood (GimpChannel *channel,
+ gboolean push_undo)
+{
+ GIMP_CHANNEL_CLASS (parent_class)->flood (channel, push_undo);
+}
+
+
+/* public functions */
+
+GimpChannel *
+gimp_selection_new (GimpImage *image,
+ gint width,
+ gint height)
+{
+ GimpRGB black = { 0.0, 0.0, 0.0, 0.5 };
+ GimpChannel *channel;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+ g_return_val_if_fail (width > 0 && height > 0, NULL);
+
+ channel = GIMP_CHANNEL (gimp_drawable_new (GIMP_TYPE_SELECTION,
+ image, NULL,
+ 0, 0, width, height,
+ gimp_image_get_mask_format (image)));
+
+ gimp_channel_set_color (channel, &black, FALSE);
+ gimp_channel_set_show_masked (channel, TRUE);
+
+ channel->x2 = width;
+ channel->y2 = height;
+
+ return channel;
+}
+
+gint
+gimp_selection_suspend (GimpSelection *selection)
+{
+ g_return_val_if_fail (GIMP_IS_SELECTION (selection), 0);
+
+ selection->suspend_count++;
+
+ return selection->suspend_count;
+}
+
+gint
+gimp_selection_resume (GimpSelection *selection)
+{
+ g_return_val_if_fail (GIMP_IS_SELECTION (selection), 0);
+ g_return_val_if_fail (selection->suspend_count > 0, 0);
+
+ selection->suspend_count--;
+
+ return selection->suspend_count;
+}
+
+GeglBuffer *
+gimp_selection_extract (GimpSelection *selection,
+ GimpPickable *pickable,
+ GimpContext *context,
+ gboolean cut_image,
+ gboolean keep_indexed,
+ gboolean add_alpha,
+ gint *offset_x,
+ gint *offset_y,
+ GError **error)
+{
+ GimpImage *image;
+ GeglBuffer *src_buffer;
+ GeglBuffer *dest_buffer;
+ const Babl *src_format;
+ const Babl *dest_format;
+ gint x1, y1, x2, y2;
+ gboolean non_empty;
+ gint off_x, off_y;
+
+ g_return_val_if_fail (GIMP_IS_SELECTION (selection), NULL);
+ g_return_val_if_fail (GIMP_IS_PICKABLE (pickable), NULL);
+ if (GIMP_IS_ITEM (pickable))
+ g_return_val_if_fail (gimp_item_is_attached (GIMP_ITEM (pickable)), NULL);
+ g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ image = gimp_pickable_get_image (pickable);
+
+ /* If there are no bounds, then just extract the entire image
+ * This may not be the correct behavior, but after getting rid
+ * of floating selections, it's still tempting to "cut" or "copy"
+ * a small layer and expect it to work, even though there is no
+ * actual selection mask
+ */
+ if (GIMP_IS_DRAWABLE (pickable))
+ {
+ non_empty = gimp_item_mask_bounds (GIMP_ITEM (pickable),
+ &x1, &y1, &x2, &y2);
+
+ gimp_item_get_offset (GIMP_ITEM (pickable), &off_x, &off_y);
+ }
+ else
+ {
+ non_empty = gimp_item_bounds (GIMP_ITEM (selection),
+ &x1, &y1, &x2, &y2);
+ x2 += x1;
+ y2 += y1;
+
+ off_x = 0;
+ off_y = 0;
+
+ /* can't cut from non-drawables, fall back to copy */
+ cut_image = FALSE;
+ }
+
+ if (non_empty && ((x1 == x2) || (y1 == y2)))
+ {
+ g_set_error_literal (error, GIMP_ERROR, GIMP_FAILED,
+ _("Unable to cut or copy because the "
+ "selected region is empty."));
+ return NULL;
+ }
+
+ /* If there is a selection, we must add alpha because the selection
+ * could have any shape.
+ */
+ if (non_empty)
+ add_alpha = TRUE;
+
+ src_format = gimp_pickable_get_format (pickable);
+
+ /* How many bytes in the temp buffer? */
+ if (babl_format_is_palette (src_format) && ! keep_indexed)
+ {
+ dest_format = gimp_image_get_format (image, GIMP_RGB,
+ gimp_image_get_precision (image),
+ add_alpha ||
+ babl_format_has_alpha (src_format));
+ }
+ else
+ {
+ if (add_alpha)
+ dest_format = gimp_pickable_get_format_with_alpha (pickable);
+ else
+ dest_format = src_format;
+ }
+
+ gimp_pickable_flush (pickable);
+
+ src_buffer = gimp_pickable_get_buffer (pickable);
+
+ /* Allocate the temp buffer */
+ dest_buffer = gegl_buffer_new (GEGL_RECTANGLE (0, 0, x2 - x1, y2 - y1),
+ dest_format);
+
+ /* First, copy the pixels, possibly doing INDEXED->RGB and adding alpha */
+ gimp_gegl_buffer_copy (src_buffer, GEGL_RECTANGLE (x1, y1, x2 - x1, y2 - y1),
+ GEGL_ABYSS_NONE,
+ dest_buffer, GEGL_RECTANGLE (0, 0, 0, 0));
+
+ if (non_empty)
+ {
+ /* If there is a selection, mask the dest_buffer with it */
+
+ GeglBuffer *mask_buffer;
+
+ mask_buffer = gimp_drawable_get_buffer (GIMP_DRAWABLE (selection));
+
+ gimp_gegl_apply_opacity (dest_buffer, NULL, NULL, dest_buffer,
+ mask_buffer,
+ - (off_x + x1),
+ - (off_y + y1),
+ 1.0);
+
+ if (cut_image)
+ {
+ gimp_drawable_edit_clear (GIMP_DRAWABLE (pickable), context);
+ }
+ }
+ else if (cut_image)
+ {
+ /* If we're cutting without selection, remove either the layer
+ * (or floating selection), the layer mask, or the channel
+ */
+ if (GIMP_IS_LAYER (pickable))
+ {
+ gimp_image_remove_layer (image, GIMP_LAYER (pickable),
+ TRUE, NULL);
+ }
+ else if (GIMP_IS_LAYER_MASK (pickable))
+ {
+ gimp_layer_apply_mask (gimp_layer_mask_get_layer (GIMP_LAYER_MASK (pickable)),
+ GIMP_MASK_DISCARD, TRUE);
+ }
+ else if (GIMP_IS_CHANNEL (pickable))
+ {
+ gimp_image_remove_channel (image, GIMP_CHANNEL (pickable),
+ TRUE, NULL);
+ }
+ }
+
+ *offset_x = x1 + off_x;
+ *offset_y = y1 + off_y;
+
+ return dest_buffer;
+}
+
+GimpLayer *
+gimp_selection_float (GimpSelection *selection,
+ GimpDrawable *drawable,
+ GimpContext *context,
+ gboolean cut_image,
+ gint off_x,
+ gint off_y,
+ GError **error)
+{
+ GimpImage *image;
+ GimpLayer *layer;
+ GeglBuffer *buffer;
+ GimpColorProfile *profile;
+ gint x1, y1;
+ gint x2, y2;
+
+ g_return_val_if_fail (GIMP_IS_SELECTION (selection), NULL);
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), NULL);
+ g_return_val_if_fail (gimp_item_is_attached (GIMP_ITEM (drawable)), NULL);
+ g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ image = gimp_item_get_image (GIMP_ITEM (selection));
+
+ /* Make sure there is a region to float... */
+ if (! gimp_item_mask_bounds (GIMP_ITEM (drawable), &x1, &y1, &x2, &y2) ||
+ (x1 == x2 || y1 == y2))
+ {
+ g_set_error_literal (error, GIMP_ERROR, GIMP_FAILED,
+ _("Cannot float selection because the selected "
+ "region is empty."));
+ return NULL;
+ }
+
+ /* Start an undo group */
+ gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_FS_FLOAT,
+ C_("undo-type", "Float Selection"));
+
+ /* Cut or copy the selected region */
+ buffer = gimp_selection_extract (selection, GIMP_PICKABLE (drawable), context,
+ cut_image, FALSE, TRUE,
+ &x1, &y1, NULL);
+
+ profile = gimp_color_managed_get_color_profile (GIMP_COLOR_MANAGED (drawable));
+
+ /* Clear the selection */
+ gimp_channel_clear (GIMP_CHANNEL (selection), NULL, TRUE);
+
+ /* Create a new layer from the buffer, using the drawable's type
+ * because it may be different from the image's type if we cut from
+ * a channel or layer mask
+ */
+ layer = gimp_layer_new_from_gegl_buffer (buffer, image,
+ gimp_drawable_get_format_with_alpha (drawable),
+ _("Floated Layer"),
+ GIMP_OPACITY_OPAQUE,
+ gimp_image_get_default_new_layer_mode (image),
+ profile);
+
+ /* Set the offsets */
+ gimp_item_set_offset (GIMP_ITEM (layer), x1 + off_x, y1 + off_y);
+
+ /* Free the temp buffer */
+ g_object_unref (buffer);
+
+ /* Add the floating layer to the image */
+ floating_sel_attach (layer, drawable);
+
+ /* End an undo group */
+ gimp_image_undo_group_end (image);
+
+ /* invalidate the image's boundary variables */
+ GIMP_CHANNEL (selection)->boundary_known = FALSE;
+
+ return layer;
+}
diff --git a/app/core/gimpselection.h b/app/core/gimpselection.h
new file mode 100644
index 0000000..64a2962
--- /dev/null
+++ b/app/core/gimpselection.h
@@ -0,0 +1,76 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_SELECTION_H__
+#define __GIMP_SELECTION_H__
+
+
+#include "gimpchannel.h"
+
+
+#define GIMP_TYPE_SELECTION (gimp_selection_get_type ())
+#define GIMP_SELECTION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_SELECTION, GimpSelection))
+#define GIMP_SELECTION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_SELECTION, GimpSelectionClass))
+#define GIMP_IS_SELECTION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_SELECTION))
+#define GIMP_IS_SELECTION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_SELECTION))
+#define GIMP_SELECTION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_SELECTION, GimpSelectionClass))
+
+
+typedef struct _GimpSelectionClass GimpSelectionClass;
+
+struct _GimpSelection
+{
+ GimpChannel parent_instance;
+
+ gint suspend_count;
+};
+
+struct _GimpSelectionClass
+{
+ GimpChannelClass parent_class;
+};
+
+
+GType gimp_selection_get_type (void) G_GNUC_CONST;
+
+GimpChannel * gimp_selection_new (GimpImage *image,
+ gint width,
+ gint height);
+
+gint gimp_selection_suspend (GimpSelection *selection);
+gint gimp_selection_resume (GimpSelection *selection);
+
+GeglBuffer * gimp_selection_extract (GimpSelection *selection,
+ GimpPickable *pickable,
+ GimpContext *context,
+ gboolean cut_image,
+ gboolean keep_indexed,
+ gboolean add_alpha,
+ gint *offset_x,
+ gint *offset_y,
+ GError **error);
+
+GimpLayer * gimp_selection_float (GimpSelection *selection,
+ GimpDrawable *drawable,
+ GimpContext *context,
+ gboolean cut_image,
+ gint off_x,
+ gint off_y,
+ GError **error);
+
+
+#endif /* __GIMP_SELECTION_H__ */
diff --git a/app/core/gimpsettings.c b/app/core/gimpsettings.c
new file mode 100644
index 0000000..ee21a36
--- /dev/null
+++ b/app/core/gimpsettings.c
@@ -0,0 +1,195 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpsettings.c
+ * Copyright (C) 2008 Michael Natterer <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpconfig/gimpconfig.h"
+
+#include "core-types.h"
+
+#include "gimpsettings.h"
+
+#include "gimp-intl.h"
+
+
+enum
+{
+ PROP_0,
+ PROP_TIME
+};
+
+
+static void gimp_settings_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+static void gimp_settings_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+
+static gchar * gimp_settings_get_description (GimpViewable *viewable,
+ gchar **tooltip);
+
+
+G_DEFINE_TYPE (GimpSettings, gimp_settings, GIMP_TYPE_VIEWABLE)
+
+#define parent_class gimp_settings_parent_class
+
+
+static void
+gimp_settings_class_init (GimpSettingsClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpViewableClass *viewable_class = GIMP_VIEWABLE_CLASS (klass);
+
+ object_class->set_property = gimp_settings_set_property;
+ object_class->get_property = gimp_settings_get_property;
+
+ viewable_class->get_description = gimp_settings_get_description;
+ viewable_class->name_editable = TRUE;
+
+ GIMP_CONFIG_PROP_INT64 (object_class, PROP_TIME,
+ "time",
+ "Time",
+ "Time of settings creation",
+ 0, G_MAXINT64, 0, 0);
+}
+
+static void
+gimp_settings_init (GimpSettings *settings)
+{
+}
+
+static void
+gimp_settings_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpSettings *settings = GIMP_SETTINGS (object);
+
+ switch (property_id)
+ {
+ case PROP_TIME:
+ g_value_set_int64 (value, settings->time);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_settings_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpSettings *settings = GIMP_SETTINGS (object);
+
+ switch (property_id)
+ {
+ case PROP_TIME:
+ settings->time = g_value_get_int64 (value);
+
+ if (settings->time > 0)
+ {
+ GDateTime *utc = g_date_time_new_from_unix_utc (settings->time);
+ GDateTime *local = g_date_time_to_local (utc);
+ gchar *name;
+
+ name = g_date_time_format (local, "%Y-%m-%d %H:%M:%S");
+ gimp_object_take_name (GIMP_OBJECT (settings), name);
+
+ g_date_time_unref (local);
+ g_date_time_unref (utc);
+ }
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static gchar *
+gimp_settings_get_description (GimpViewable *viewable,
+ gchar **tooltip)
+{
+ GimpSettings *settings = GIMP_SETTINGS (viewable);
+
+ if (settings->time > 0)
+ {
+ if (tooltip)
+ *tooltip = g_strdup ("You can rename automatic presets "
+ "to make them permanently saved");
+
+ return g_strdup_printf (_("Last used: %s"),
+ gimp_object_get_name (settings));
+ }
+
+ return GIMP_VIEWABLE_CLASS (parent_class)->get_description (viewable,
+ tooltip);
+}
+
+
+/* public functions */
+
+gint
+gimp_settings_compare (GimpSettings *a,
+ GimpSettings *b)
+{
+ const gchar *name_a = gimp_object_get_name (a);
+ const gchar *name_b = gimp_object_get_name (b);
+
+ if (a->time > 0 && b->time > 0)
+ {
+ return - strcmp (name_a, name_b);
+ }
+ else if (a->time > 0)
+ {
+ return -1;
+ }
+ else if (b->time > 0)
+ {
+ return 1;
+ }
+ else if (name_a && name_b)
+ {
+ return strcmp (name_a, name_b);
+ }
+ else if (name_a)
+ {
+ return 1;
+ }
+ else if (name_b)
+ {
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/app/core/gimpsettings.h b/app/core/gimpsettings.h
new file mode 100644
index 0000000..73aa96f
--- /dev/null
+++ b/app/core/gimpsettings.h
@@ -0,0 +1,57 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpsettings.h
+ * Copyright (C) 2008 Michael Natterer <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_SETTINGS_H__
+#define __GIMP_SETTINGS_H__
+
+
+#include "gimpviewable.h"
+
+
+#define GIMP_TYPE_SETTINGS (gimp_settings_get_type ())
+#define GIMP_SETTINGS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_SETTINGS, GimpSettings))
+#define GIMP_SETTINGS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_SETTINGS, GimpSettingsClass))
+#define GIMP_IS_SETTINGS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_SETTINGS))
+#define GIMP_IS_SETTINGS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_SETTINGS))
+#define GIMP_SETTINGS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_SETTINGS, GimpSettingsClass))
+
+
+typedef struct _GimpSettingsClass GimpSettingsClass;
+
+struct _GimpSettings
+{
+ GimpViewable parent_instance;
+
+ gint64 time;
+};
+
+struct _GimpSettingsClass
+{
+ GimpViewableClass parent_class;
+};
+
+
+GType gimp_settings_get_type (void) G_GNUC_CONST;
+
+gint gimp_settings_compare (GimpSettings *a,
+ GimpSettings *b);
+
+
+#endif /* __GIMP_SETTINGS_H__ */
diff --git a/app/core/gimpstrokeoptions.c b/app/core/gimpstrokeoptions.c
new file mode 100644
index 0000000..3bb5c92
--- /dev/null
+++ b/app/core/gimpstrokeoptions.c
@@ -0,0 +1,638 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-1999 Spencer Kimball and Peter Mattis
+ *
+ * gimpstrokeoptions.c
+ * Copyright (C) 2003 Simon Budig
+ * Copyright (C) 2004 Michael Natterer <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpbase/gimpbase.h"
+#include "libgimpconfig/gimpconfig.h"
+
+#include "core-types.h"
+
+#include "config/gimpcoreconfig.h"
+
+#include "gimp.h"
+#include "gimpcontext.h"
+#include "gimpdashpattern.h"
+#include "gimpmarshal.h"
+#include "gimppaintinfo.h"
+#include "gimpparamspecs.h"
+#include "gimpstrokeoptions.h"
+
+#include "paint/gimppaintoptions.h"
+
+#include "gimp-intl.h"
+
+
+enum
+{
+ PROP_0,
+
+ PROP_METHOD,
+
+ PROP_STYLE,
+ PROP_WIDTH,
+ PROP_UNIT,
+ PROP_CAP_STYLE,
+ PROP_JOIN_STYLE,
+ PROP_MITER_LIMIT,
+ PROP_ANTIALIAS,
+ PROP_DASH_UNIT,
+ PROP_DASH_OFFSET,
+ PROP_DASH_INFO,
+
+ PROP_PAINT_OPTIONS,
+ PROP_EMULATE_DYNAMICS
+};
+
+enum
+{
+ DASH_INFO_CHANGED,
+ LAST_SIGNAL
+};
+
+
+typedef struct _GimpStrokeOptionsPrivate GimpStrokeOptionsPrivate;
+
+struct _GimpStrokeOptionsPrivate
+{
+ GimpStrokeMethod method;
+
+ /* options for method == LIBART */
+ gdouble width;
+ GimpUnit unit;
+
+ GimpCapStyle cap_style;
+ GimpJoinStyle join_style;
+
+ gdouble miter_limit;
+
+ gdouble dash_offset;
+ GArray *dash_info;
+
+ /* options for method == PAINT_TOOL */
+ GimpPaintOptions *paint_options;
+ gboolean emulate_dynamics;
+};
+
+#define GET_PRIVATE(options) \
+ ((GimpStrokeOptionsPrivate *) gimp_stroke_options_get_instance_private ((GimpStrokeOptions *) (options)))
+
+
+static void gimp_stroke_options_config_iface_init (gpointer iface,
+ gpointer iface_data);
+
+static void gimp_stroke_options_finalize (GObject *object);
+static void gimp_stroke_options_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_stroke_options_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static GimpConfig * gimp_stroke_options_duplicate (GimpConfig *config);
+
+
+G_DEFINE_TYPE_WITH_CODE (GimpStrokeOptions, gimp_stroke_options,
+ GIMP_TYPE_FILL_OPTIONS,
+ G_ADD_PRIVATE (GimpStrokeOptions)
+ G_IMPLEMENT_INTERFACE (GIMP_TYPE_CONFIG,
+ gimp_stroke_options_config_iface_init))
+
+#define parent_class gimp_stroke_options_parent_class
+
+static GimpConfigInterface *parent_config_iface = NULL;
+
+static guint stroke_options_signals[LAST_SIGNAL] = { 0 };
+
+
+static void
+gimp_stroke_options_class_init (GimpStrokeOptionsClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GParamSpec *array_spec;
+
+ object_class->finalize = gimp_stroke_options_finalize;
+ object_class->set_property = gimp_stroke_options_set_property;
+ object_class->get_property = gimp_stroke_options_get_property;
+
+ klass->dash_info_changed = NULL;
+
+ stroke_options_signals[DASH_INFO_CHANGED] =
+ g_signal_new ("dash-info-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpStrokeOptionsClass, dash_info_changed),
+ NULL, NULL,
+ gimp_marshal_VOID__ENUM,
+ G_TYPE_NONE, 1,
+ GIMP_TYPE_DASH_PRESET);
+
+ GIMP_CONFIG_PROP_ENUM (object_class, PROP_METHOD,
+ "method",
+ _("Method"),
+ NULL,
+ GIMP_TYPE_STROKE_METHOD,
+ GIMP_STROKE_LINE,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_DOUBLE (object_class, PROP_WIDTH,
+ "width",
+ _("Line width"),
+ NULL,
+ 0.0, 2000.0, 6.0,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_UNIT (object_class, PROP_UNIT,
+ "unit",
+ _("Unit"),
+ NULL,
+ TRUE, FALSE, GIMP_UNIT_PIXEL,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_ENUM (object_class, PROP_CAP_STYLE,
+ "cap-style",
+ _("Cap style"),
+ NULL,
+ GIMP_TYPE_CAP_STYLE, GIMP_CAP_BUTT,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_ENUM (object_class, PROP_JOIN_STYLE,
+ "join-style",
+ _("Join style"),
+ NULL,
+ GIMP_TYPE_JOIN_STYLE, GIMP_JOIN_MITER,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_DOUBLE (object_class, PROP_MITER_LIMIT,
+ "miter-limit",
+ _("Miter limit"),
+ _("Convert a mitered join to a bevelled "
+ "join if the miter would extend to a "
+ "distance of more than miter-limit * "
+ "line-width from the actual join point."),
+ 0.0, 100.0, 10.0,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_DOUBLE (object_class, PROP_DASH_OFFSET,
+ "dash-offset",
+ _("Dash offset"),
+ NULL,
+ 0.0, 2000.0, 0.0,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ array_spec = g_param_spec_double ("dash-length", NULL, NULL,
+ 0.0, 2000.0, 1.0, GIMP_PARAM_READWRITE);
+ g_object_class_install_property (object_class, PROP_DASH_INFO,
+ gimp_param_spec_value_array ("dash-info",
+ NULL, NULL,
+ array_spec,
+ GIMP_PARAM_STATIC_STRINGS |
+ GIMP_CONFIG_PARAM_FLAGS));
+
+ GIMP_CONFIG_PROP_OBJECT (object_class, PROP_PAINT_OPTIONS,
+ "paint-options",
+ NULL, NULL,
+ GIMP_TYPE_PAINT_OPTIONS,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_EMULATE_DYNAMICS,
+ "emulate-brush-dynamics",
+ _("Emulate brush dynamics"),
+ NULL,
+ FALSE,
+ GIMP_PARAM_STATIC_STRINGS);
+}
+
+static void
+gimp_stroke_options_config_iface_init (gpointer iface,
+ gpointer iface_data)
+{
+ GimpConfigInterface *config_iface = (GimpConfigInterface *) iface;
+
+ parent_config_iface = g_type_interface_peek_parent (config_iface);
+
+ if (! parent_config_iface)
+ parent_config_iface = g_type_default_interface_peek (GIMP_TYPE_CONFIG);
+
+ config_iface->duplicate = gimp_stroke_options_duplicate;
+}
+
+static void
+gimp_stroke_options_init (GimpStrokeOptions *options)
+{
+}
+
+static void
+gimp_stroke_options_finalize (GObject *object)
+{
+ GimpStrokeOptionsPrivate *private = GET_PRIVATE (object);
+
+ if (private->dash_info)
+ {
+ gimp_dash_pattern_free (private->dash_info);
+ private->dash_info = NULL;
+ }
+
+ g_clear_object (&private->paint_options);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gimp_stroke_options_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpStrokeOptions *options = GIMP_STROKE_OPTIONS (object);
+ GimpStrokeOptionsPrivate *private = GET_PRIVATE (object);
+
+ switch (property_id)
+ {
+ case PROP_METHOD:
+ private->method = g_value_get_enum (value);
+ break;
+
+ case PROP_WIDTH:
+ private->width = g_value_get_double (value);
+ break;
+ case PROP_UNIT:
+ private->unit = g_value_get_int (value);
+ break;
+ case PROP_CAP_STYLE:
+ private->cap_style = g_value_get_enum (value);
+ break;
+ case PROP_JOIN_STYLE:
+ private->join_style = g_value_get_enum (value);
+ break;
+ case PROP_MITER_LIMIT:
+ private->miter_limit = g_value_get_double (value);
+ break;
+ case PROP_DASH_OFFSET:
+ private->dash_offset = g_value_get_double (value);
+ break;
+ case PROP_DASH_INFO:
+ {
+ GimpValueArray *value_array = g_value_get_boxed (value);
+ GArray *pattern;
+
+ pattern = gimp_dash_pattern_from_value_array (value_array);
+ gimp_stroke_options_take_dash_pattern (options, GIMP_DASH_CUSTOM,
+ pattern);
+ }
+ break;
+
+ case PROP_PAINT_OPTIONS:
+ if (private->paint_options)
+ g_object_unref (private->paint_options);
+ private->paint_options = g_value_dup_object (value);
+ break;
+ case PROP_EMULATE_DYNAMICS:
+ private->emulate_dynamics = g_value_get_boolean (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_stroke_options_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpStrokeOptionsPrivate *private = GET_PRIVATE (object);
+
+ switch (property_id)
+ {
+ case PROP_METHOD:
+ g_value_set_enum (value, private->method);
+ break;
+
+ case PROP_WIDTH:
+ g_value_set_double (value, private->width);
+ break;
+ case PROP_UNIT:
+ g_value_set_int (value, private->unit);
+ break;
+ case PROP_CAP_STYLE:
+ g_value_set_enum (value, private->cap_style);
+ break;
+ case PROP_JOIN_STYLE:
+ g_value_set_enum (value, private->join_style);
+ break;
+ case PROP_MITER_LIMIT:
+ g_value_set_double (value, private->miter_limit);
+ break;
+ case PROP_DASH_OFFSET:
+ g_value_set_double (value, private->dash_offset);
+ break;
+ case PROP_DASH_INFO:
+ {
+ GimpValueArray *value_array;
+
+ value_array = gimp_dash_pattern_to_value_array (private->dash_info);
+ g_value_take_boxed (value, value_array);
+ }
+ break;
+
+ case PROP_PAINT_OPTIONS:
+ g_value_set_object (value, private->paint_options);
+ break;
+ case PROP_EMULATE_DYNAMICS:
+ g_value_set_boolean (value, private->emulate_dynamics);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static GimpConfig *
+gimp_stroke_options_duplicate (GimpConfig *config)
+{
+ GimpStrokeOptions *options = GIMP_STROKE_OPTIONS (config);
+ GimpStrokeOptionsPrivate *private = GET_PRIVATE (options);
+ GimpStrokeOptions *new_options;
+
+ new_options = GIMP_STROKE_OPTIONS (parent_config_iface->duplicate (config));
+
+ if (private->paint_options)
+ {
+ GObject *paint_options;
+
+ paint_options = gimp_config_duplicate (GIMP_CONFIG (private->paint_options));
+ g_object_set (new_options, "paint-options", paint_options, NULL);
+ g_object_unref (paint_options);
+ }
+
+ return GIMP_CONFIG (new_options);
+}
+
+
+/* public functions */
+
+GimpStrokeOptions *
+gimp_stroke_options_new (Gimp *gimp,
+ GimpContext *context,
+ gboolean use_context_color)
+{
+ GimpPaintInfo *paint_info = NULL;
+ GimpStrokeOptions *options;
+
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
+ g_return_val_if_fail (context == NULL || GIMP_IS_CONTEXT (context), NULL);
+ g_return_val_if_fail (use_context_color == FALSE || context != NULL, NULL);
+
+ if (context)
+ paint_info = gimp_context_get_paint_info (context);
+
+ if (! paint_info)
+ paint_info = gimp_paint_info_get_standard (gimp);
+
+ options = g_object_new (GIMP_TYPE_STROKE_OPTIONS,
+ "gimp", gimp,
+ "paint-info", paint_info,
+ NULL);
+
+ if (use_context_color)
+ {
+ gimp_context_define_properties (GIMP_CONTEXT (options),
+ GIMP_CONTEXT_PROP_MASK_FOREGROUND |
+ GIMP_CONTEXT_PROP_MASK_PATTERN,
+ FALSE);
+
+ gimp_context_set_parent (GIMP_CONTEXT (options), context);
+ }
+
+ return options;
+}
+
+GimpStrokeMethod
+gimp_stroke_options_get_method (GimpStrokeOptions *options)
+{
+ g_return_val_if_fail (GIMP_IS_STROKE_OPTIONS (options),
+ GIMP_STROKE_LINE);
+
+ return GET_PRIVATE (options)->method;
+}
+
+gdouble
+gimp_stroke_options_get_width (GimpStrokeOptions *options)
+{
+ g_return_val_if_fail (GIMP_IS_STROKE_OPTIONS (options), 1.0);
+
+ return GET_PRIVATE (options)->width;
+}
+
+GimpUnit
+gimp_stroke_options_get_unit (GimpStrokeOptions *options)
+{
+ g_return_val_if_fail (GIMP_IS_STROKE_OPTIONS (options), GIMP_UNIT_PIXEL);
+
+ return GET_PRIVATE (options)->unit;
+}
+
+GimpCapStyle
+gimp_stroke_options_get_cap_style (GimpStrokeOptions *options)
+{
+ g_return_val_if_fail (GIMP_IS_STROKE_OPTIONS (options), GIMP_CAP_BUTT);
+
+ return GET_PRIVATE (options)->cap_style;
+}
+
+GimpJoinStyle
+gimp_stroke_options_get_join_style (GimpStrokeOptions *options)
+{
+ g_return_val_if_fail (GIMP_IS_STROKE_OPTIONS (options), GIMP_JOIN_MITER);
+
+ return GET_PRIVATE (options)->join_style;
+}
+
+gdouble
+gimp_stroke_options_get_miter_limit (GimpStrokeOptions *options)
+{
+ g_return_val_if_fail (GIMP_IS_STROKE_OPTIONS (options), 1.0);
+
+ return GET_PRIVATE (options)->miter_limit;
+}
+
+gdouble
+gimp_stroke_options_get_dash_offset (GimpStrokeOptions *options)
+{
+ g_return_val_if_fail (GIMP_IS_STROKE_OPTIONS (options), 0.0);
+
+ return GET_PRIVATE (options)->dash_offset;
+}
+
+GArray *
+gimp_stroke_options_get_dash_info (GimpStrokeOptions *options)
+{
+ g_return_val_if_fail (GIMP_IS_STROKE_OPTIONS (options), NULL);
+
+ return GET_PRIVATE (options)->dash_info;
+}
+
+GimpPaintOptions *
+gimp_stroke_options_get_paint_options (GimpStrokeOptions *options)
+{
+ g_return_val_if_fail (GIMP_IS_STROKE_OPTIONS (options), NULL);
+
+ return GET_PRIVATE (options)->paint_options;
+}
+
+gboolean
+gimp_stroke_options_get_emulate_dynamics (GimpStrokeOptions *options)
+{
+ g_return_val_if_fail (GIMP_IS_STROKE_OPTIONS (options), FALSE);
+
+ return GET_PRIVATE (options)->emulate_dynamics;
+}
+
+/**
+ * gimp_stroke_options_take_dash_pattern:
+ * @options: a #GimpStrokeOptions object
+ * @preset: a value out of the #GimpDashPreset enum
+ * @pattern: a #GArray or %NULL if @preset is not %GIMP_DASH_CUSTOM
+ *
+ * Sets the dash pattern. Either a @preset is passed and @pattern is
+ * %NULL or @preset is %GIMP_DASH_CUSTOM and @pattern is the #GArray
+ * to use as the dash pattern. Note that this function takes ownership
+ * of the passed pattern.
+ */
+void
+gimp_stroke_options_take_dash_pattern (GimpStrokeOptions *options,
+ GimpDashPreset preset,
+ GArray *pattern)
+{
+ GimpStrokeOptionsPrivate *private;
+
+ g_return_if_fail (GIMP_IS_STROKE_OPTIONS (options));
+ g_return_if_fail (preset == GIMP_DASH_CUSTOM || pattern == NULL);
+
+ private = GET_PRIVATE (options);
+
+ if (preset != GIMP_DASH_CUSTOM)
+ pattern = gimp_dash_pattern_new_from_preset (preset);
+
+ if (private->dash_info)
+ gimp_dash_pattern_free (private->dash_info);
+
+ private->dash_info = pattern;
+
+ g_object_notify (G_OBJECT (options), "dash-info");
+
+ g_signal_emit (options, stroke_options_signals [DASH_INFO_CHANGED], 0,
+ preset);
+}
+
+void
+gimp_stroke_options_prepare (GimpStrokeOptions *options,
+ GimpContext *context,
+ GimpPaintOptions *paint_options)
+{
+ GimpStrokeOptionsPrivate *private;
+
+ g_return_if_fail (GIMP_IS_STROKE_OPTIONS (options));
+ g_return_if_fail (GIMP_IS_CONTEXT (context));
+ g_return_if_fail (paint_options == NULL ||
+ GIMP_IS_PAINT_OPTIONS (paint_options));
+
+ private = GET_PRIVATE (options);
+
+ switch (private->method)
+ {
+ case GIMP_STROKE_LINE:
+ break;
+
+ case GIMP_STROKE_PAINT_METHOD:
+ {
+ GimpPaintInfo *paint_info = GIMP_CONTEXT (options)->paint_info;
+
+ if (paint_options)
+ {
+ g_return_if_fail (paint_info == paint_options->paint_info);
+
+ /* undefine the paint-relevant context properties and get them
+ * from the passed context
+ */
+ gimp_context_define_properties (GIMP_CONTEXT (paint_options),
+ GIMP_CONTEXT_PROP_MASK_PAINT,
+ FALSE);
+ gimp_context_set_parent (GIMP_CONTEXT (paint_options), context);
+
+ g_object_ref (paint_options);
+ }
+ else
+ {
+ GimpCoreConfig *config = context->gimp->config;
+ GimpContextPropMask global_props = 0;
+
+ paint_options =
+ gimp_config_duplicate (GIMP_CONFIG (paint_info->paint_options));
+
+ /* FG and BG are always shared between all tools */
+ global_props |= GIMP_CONTEXT_PROP_MASK_FOREGROUND;
+ global_props |= GIMP_CONTEXT_PROP_MASK_BACKGROUND;
+
+ if (config->global_brush)
+ global_props |= GIMP_CONTEXT_PROP_MASK_BRUSH;
+ if (config->global_dynamics)
+ global_props |= GIMP_CONTEXT_PROP_MASK_DYNAMICS;
+ if (config->global_pattern)
+ global_props |= GIMP_CONTEXT_PROP_MASK_PATTERN;
+ if (config->global_palette)
+ global_props |= GIMP_CONTEXT_PROP_MASK_PALETTE;
+ if (config->global_gradient)
+ global_props |= GIMP_CONTEXT_PROP_MASK_GRADIENT;
+ if (config->global_font)
+ global_props |= GIMP_CONTEXT_PROP_MASK_FONT;
+
+ gimp_context_copy_properties (context,
+ GIMP_CONTEXT (paint_options),
+ global_props);
+ }
+
+ g_object_set (options, "paint-options", paint_options, NULL);
+ g_object_unref (paint_options);
+ }
+ break;
+
+ default:
+ g_return_if_reached ();
+ }
+}
+
+void
+gimp_stroke_options_finish (GimpStrokeOptions *options)
+{
+ g_return_if_fail (GIMP_IS_STROKE_OPTIONS (options));
+
+ g_object_set (options, "paint-options", NULL, NULL);
+}
diff --git a/app/core/gimpstrokeoptions.h b/app/core/gimpstrokeoptions.h
new file mode 100644
index 0000000..f5210e8
--- /dev/null
+++ b/app/core/gimpstrokeoptions.h
@@ -0,0 +1,81 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-1999 Spencer Kimball and Peter Mattis
+ *
+ * gimpstrokeoptions.h
+ * Copyright (C) 2003 Simon Budig
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_STROKE_OPTIONS_H__
+#define __GIMP_STROKE_OPTIONS_H__
+
+
+#include "gimpfilloptions.h"
+
+
+#define GIMP_TYPE_STROKE_OPTIONS (gimp_stroke_options_get_type ())
+#define GIMP_STROKE_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_STROKE_OPTIONS, GimpStrokeOptions))
+#define GIMP_STROKE_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_STROKE_OPTIONS, GimpStrokeOptionsClass))
+#define GIMP_IS_STROKE_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_STROKE_OPTIONS))
+#define GIMP_IS_STROKE_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_STROKE_OPTIONS))
+#define GIMP_STROKE_OPTIONS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_STROKE_OPTIONS, GimpStrokeOptionsClass))
+
+
+typedef struct _GimpStrokeOptionsClass GimpStrokeOptionsClass;
+
+struct _GimpStrokeOptions
+{
+ GimpFillOptions parent_instance;
+};
+
+struct _GimpStrokeOptionsClass
+{
+ GimpFillOptionsClass parent_class;
+
+ void (* dash_info_changed) (GimpStrokeOptions *stroke_options,
+ GimpDashPreset preset);
+};
+
+
+GType gimp_stroke_options_get_type (void) G_GNUC_CONST;
+
+GimpStrokeOptions * gimp_stroke_options_new (Gimp *gimp,
+ GimpContext *context,
+ gboolean use_context_color);
+
+GimpStrokeMethod gimp_stroke_options_get_method (GimpStrokeOptions *options);
+
+gdouble gimp_stroke_options_get_width (GimpStrokeOptions *options);
+GimpUnit gimp_stroke_options_get_unit (GimpStrokeOptions *options);
+GimpCapStyle gimp_stroke_options_get_cap_style (GimpStrokeOptions *options);
+GimpJoinStyle gimp_stroke_options_get_join_style (GimpStrokeOptions *options);
+gdouble gimp_stroke_options_get_miter_limit (GimpStrokeOptions *options);
+gdouble gimp_stroke_options_get_dash_offset (GimpStrokeOptions *options);
+GArray * gimp_stroke_options_get_dash_info (GimpStrokeOptions *options);
+
+GimpPaintOptions * gimp_stroke_options_get_paint_options (GimpStrokeOptions *options);
+gboolean gimp_stroke_options_get_emulate_dynamics (GimpStrokeOptions *options);
+
+void gimp_stroke_options_take_dash_pattern (GimpStrokeOptions *options,
+ GimpDashPreset preset,
+ GArray *pattern);
+
+void gimp_stroke_options_prepare (GimpStrokeOptions *options,
+ GimpContext *context,
+ GimpPaintOptions *paint_options);
+void gimp_stroke_options_finish (GimpStrokeOptions *options);
+
+
+#endif /* __GIMP_STROKE_OPTIONS_H__ */
diff --git a/app/core/gimpsubprogress.c b/app/core/gimpsubprogress.c
new file mode 100644
index 0000000..3183f61
--- /dev/null
+++ b/app/core/gimpsubprogress.c
@@ -0,0 +1,317 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <glib-object.h>
+
+#include "core-types.h"
+
+#include "core/gimpsubprogress.h"
+#include "core/gimpprogress.h"
+
+
+enum
+{
+ PROP_0,
+ PROP_PROGRESS
+};
+
+
+static void gimp_sub_progress_iface_init (GimpProgressInterface *iface);
+
+static void gimp_sub_progress_finalize (GObject *object);
+static void gimp_sub_progress_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_sub_progress_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static GimpProgress * gimp_sub_progress_start (GimpProgress *progress,
+ gboolean cancellable,
+ const gchar *message);
+static void gimp_sub_progress_end (GimpProgress *progress);
+static gboolean gimp_sub_progress_is_active (GimpProgress *progress);
+static void gimp_sub_progress_set_text (GimpProgress *progress,
+ const gchar *message);
+static void gimp_sub_progress_set_value (GimpProgress *progress,
+ gdouble percentage);
+static gdouble gimp_sub_progress_get_value (GimpProgress *progress);
+static void gimp_sub_progress_pulse (GimpProgress *progress);
+static guint32 gimp_sub_progress_get_window_id (GimpProgress *progress);
+static gboolean gimp_sub_progress_message (GimpProgress *progress,
+ Gimp *gimp,
+ GimpMessageSeverity severity,
+ const gchar *domain,
+ const gchar *message);
+
+
+G_DEFINE_TYPE_WITH_CODE (GimpSubProgress, gimp_sub_progress, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (GIMP_TYPE_PROGRESS,
+ gimp_sub_progress_iface_init))
+
+#define parent_class gimp_sub_progress_parent_class
+
+
+static void
+gimp_sub_progress_class_init (GimpSubProgressClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = gimp_sub_progress_finalize;
+ object_class->set_property = gimp_sub_progress_set_property;
+ object_class->get_property = gimp_sub_progress_get_property;
+
+ g_object_class_install_property (object_class, PROP_PROGRESS,
+ g_param_spec_object ("progress",
+ NULL, NULL,
+ GIMP_TYPE_PROGRESS,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+}
+
+static void
+gimp_sub_progress_init (GimpSubProgress *sub)
+{
+ sub->progress = NULL;
+ sub->start = 0.0;
+ sub->end = 1.0;
+}
+
+static void
+gimp_sub_progress_finalize (GObject *object)
+{
+ GimpSubProgress *sub = GIMP_SUB_PROGRESS (object);
+
+ g_clear_object (&sub->progress);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gimp_sub_progress_iface_init (GimpProgressInterface *iface)
+{
+ iface->start = gimp_sub_progress_start;
+ iface->end = gimp_sub_progress_end;
+ iface->is_active = gimp_sub_progress_is_active;
+ iface->set_text = gimp_sub_progress_set_text;
+ iface->set_value = gimp_sub_progress_set_value;
+ iface->get_value = gimp_sub_progress_get_value;
+ iface->pulse = gimp_sub_progress_pulse;
+ iface->get_window_id = gimp_sub_progress_get_window_id;
+ iface->message = gimp_sub_progress_message;
+}
+
+static void
+gimp_sub_progress_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpSubProgress *sub = GIMP_SUB_PROGRESS (object);
+
+ switch (property_id)
+ {
+ case PROP_PROGRESS:
+ g_return_if_fail (sub->progress == NULL);
+ sub->progress = g_value_dup_object (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_sub_progress_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpSubProgress *sub = GIMP_SUB_PROGRESS (object);
+
+ switch (property_id)
+ {
+ case PROP_PROGRESS:
+ g_value_set_object (value, sub->progress);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static GimpProgress *
+gimp_sub_progress_start (GimpProgress *progress,
+ gboolean cancellable,
+ const gchar *message)
+{
+ /* does nothing */
+ return NULL;
+}
+
+static void
+gimp_sub_progress_end (GimpProgress *progress)
+{
+ /* does nothing */
+}
+
+static gboolean
+gimp_sub_progress_is_active (GimpProgress *progress)
+{
+ GimpSubProgress *sub = GIMP_SUB_PROGRESS (progress);
+
+ if (sub->progress)
+ return gimp_progress_is_active (sub->progress);
+
+ return FALSE;
+}
+
+static void
+gimp_sub_progress_set_text (GimpProgress *progress,
+ const gchar *message)
+{
+ /* does nothing */
+}
+
+static void
+gimp_sub_progress_set_value (GimpProgress *progress,
+ gdouble percentage)
+{
+ GimpSubProgress *sub = GIMP_SUB_PROGRESS (progress);
+
+ if (sub->progress)
+ gimp_progress_set_value (sub->progress,
+ sub->start + percentage * (sub->end - sub->start));
+}
+
+static gdouble
+gimp_sub_progress_get_value (GimpProgress *progress)
+{
+ GimpSubProgress *sub = GIMP_SUB_PROGRESS (progress);
+
+ if (sub->progress)
+ return gimp_progress_get_value (sub->progress);
+
+ return 0.0;
+}
+
+static void
+gimp_sub_progress_pulse (GimpProgress *progress)
+{
+ GimpSubProgress *sub = GIMP_SUB_PROGRESS (progress);
+
+ if (sub->progress)
+ gimp_progress_pulse (sub->progress);
+}
+
+static guint32
+gimp_sub_progress_get_window_id (GimpProgress *progress)
+{
+ GimpSubProgress *sub = GIMP_SUB_PROGRESS (progress);
+
+ if (sub->progress)
+ return gimp_progress_get_window_id (sub->progress);
+
+ return 0;
+}
+
+static gboolean
+gimp_sub_progress_message (GimpProgress *progress,
+ Gimp *gimp,
+ GimpMessageSeverity severity,
+ const gchar *domain,
+ const gchar *message)
+{
+ GimpSubProgress *sub = GIMP_SUB_PROGRESS (progress);
+
+ if (sub->progress)
+ return gimp_progress_message (sub->progress,
+ gimp, severity, domain, message);
+
+ return FALSE;
+}
+
+/**
+ * gimp_sub_progress_new:
+ * @progress: parent progress or %NULL
+ *
+ * GimpSubProgress implements the GimpProgress interface and can be
+ * used wherever a GimpProgress is needed. It maps progress
+ * information to a sub-range of its parent @progress. This is useful
+ * when an action breaks down into multiple sub-actions that itself
+ * need a #GimpProgress pointer. See gimp_image_scale() for an example.
+ *
+ * Return value: a new #GimpProgress object
+ */
+GimpProgress *
+gimp_sub_progress_new (GimpProgress *progress)
+{
+ GimpSubProgress *sub;
+
+ g_return_val_if_fail (progress == NULL || GIMP_IS_PROGRESS (progress), NULL);
+
+ sub = g_object_new (GIMP_TYPE_SUB_PROGRESS,
+ "progress", progress,
+ NULL);
+
+ return GIMP_PROGRESS (sub);
+}
+
+/**
+ * gimp_sub_progress_set_range:
+ * @start: start value of range on the parent process
+ * @end: end value of range on the parent process
+ *
+ * Sets a range on the parent progress that this @progress should be
+ * mapped to.
+ */
+void
+gimp_sub_progress_set_range (GimpSubProgress *progress,
+ gdouble start,
+ gdouble end)
+{
+ g_return_if_fail (GIMP_IS_SUB_PROGRESS (progress));
+ g_return_if_fail (start < end);
+
+ progress->start = start;
+ progress->end = end;
+}
+
+/**
+ * gimp_sub_progress_set_step:
+ * @index: step index
+ * @num_steps: number of steps
+ *
+ * A more convenient form of gimp_sub_progress_set_range().
+ */
+void
+gimp_sub_progress_set_step (GimpSubProgress *progress,
+ gint index,
+ gint num_steps)
+{
+ g_return_if_fail (GIMP_IS_SUB_PROGRESS (progress));
+ g_return_if_fail (index < num_steps && num_steps > 0);
+
+ progress->start = (gdouble) index / num_steps;
+ progress->end = (gdouble) (index + 1) / num_steps;
+}
diff --git a/app/core/gimpsubprogress.h b/app/core/gimpsubprogress.h
new file mode 100644
index 0000000..c031182
--- /dev/null
+++ b/app/core/gimpsubprogress.h
@@ -0,0 +1,59 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_SUB_PROGRESS_H__
+#define __GIMP_SUB_PROGRESS_H__
+
+
+#define GIMP_TYPE_SUB_PROGRESS (gimp_sub_progress_get_type ())
+#define GIMP_SUB_PROGRESS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_SUB_PROGRESS, GimpSubProgress))
+#define GIMP_SUB_PROGRESS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_SUB_PROGRESS, GimpSubProgressClass))
+#define GIMP_IS_SUB_PROGRESS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_SUB_PROGRESS))
+#define GIMP_IS_SUB_PROGRESS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_SUB_PROGRESS))
+#define GIMP_SUB_PROGRESS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_SUB_PROGRESS, GimpSubProgressClass))
+
+
+typedef struct _GimpSubProgressClass GimpSubProgressClass;
+
+struct _GimpSubProgress
+{
+ GObject parent_instance;
+
+ GimpProgress *progress;
+ gdouble start;
+ gdouble end;
+};
+
+struct _GimpSubProgressClass
+{
+ GObjectClass parent_class;
+};
+
+
+GType gimp_sub_progress_get_type (void) G_GNUC_CONST;
+
+GimpProgress * gimp_sub_progress_new (GimpProgress *progress);
+void gimp_sub_progress_set_range (GimpSubProgress *progress,
+ gdouble start,
+ gdouble end);
+void gimp_sub_progress_set_step (GimpSubProgress *progress,
+ gint index,
+ gint num_steps);
+
+
+
+#endif /* __GIMP_SUB_PROGRESS_H__ */
diff --git a/app/core/gimpsymmetry-mandala.c b/app/core/gimpsymmetry-mandala.c
new file mode 100644
index 0000000..668819a
--- /dev/null
+++ b/app/core/gimpsymmetry-mandala.c
@@ -0,0 +1,574 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpsymmetry-mandala.c
+ * Copyright (C) 2015 Jehan <jehan@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <cairo.h>
+#include <gegl.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+#include "libgimpconfig/gimpconfig.h"
+#include "libgimpmath/gimpmath.h"
+
+#include "core-types.h"
+
+#include "gimp.h"
+#include "gimp-cairo.h"
+#include "gimpdrawable.h"
+#include "gimpguide.h"
+#include "gimpimage.h"
+#include "gimpimage-guides.h"
+#include "gimpimage-symmetry.h"
+#include "gimpitem.h"
+#include "gimpsymmetry-mandala.h"
+
+#include "gimp-intl.h"
+
+
+enum
+{
+ PROP_0,
+ PROP_CENTER_X,
+ PROP_CENTER_Y,
+ PROP_SIZE,
+ PROP_DISABLE_TRANSFORMATION,
+ PROP_ENABLE_REFLECTION,
+};
+
+
+/* Local function prototypes */
+
+static void gimp_mandala_constructed (GObject *object);
+static void gimp_mandala_finalize (GObject *object);
+static void gimp_mandala_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_mandala_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+static void gimp_mandala_active_changed (GimpSymmetry *sym);
+
+static void gimp_mandala_add_guide (GimpMandala *mandala,
+ GimpOrientationType orientation);
+static void gimp_mandala_remove_guide (GimpMandala *mandala,
+ GimpOrientationType orientation);
+static void gimp_mandala_guide_removed_cb (GObject *object,
+ GimpMandala *mandala);
+static void gimp_mandala_guide_position_cb (GObject *object,
+ GParamSpec *pspec,
+ GimpMandala *mandala);
+
+static void gimp_mandala_update_strokes (GimpSymmetry *mandala,
+ GimpDrawable *drawable,
+ GimpCoords *origin);
+static void gimp_mandala_get_transform (GimpSymmetry *mandala,
+ gint stroke,
+ gdouble *angle,
+ gboolean *reflect);
+static void gimp_mandala_image_size_changed_cb (GimpImage *image,
+ gint previous_origin_x,
+ gint previous_origin_y,
+ gint previous_width,
+ gint previous_height,
+ GimpSymmetry *sym);
+
+
+G_DEFINE_TYPE (GimpMandala, gimp_mandala, GIMP_TYPE_SYMMETRY)
+
+#define parent_class gimp_mandala_parent_class
+
+
+static void
+gimp_mandala_class_init (GimpMandalaClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpSymmetryClass *symmetry_class = GIMP_SYMMETRY_CLASS (klass);
+ GParamSpec *pspec;
+
+ object_class->constructed = gimp_mandala_constructed;
+ object_class->finalize = gimp_mandala_finalize;
+ object_class->set_property = gimp_mandala_set_property;
+ object_class->get_property = gimp_mandala_get_property;
+
+ symmetry_class->label = _("Mandala");
+ symmetry_class->update_strokes = gimp_mandala_update_strokes;
+ symmetry_class->get_transform = gimp_mandala_get_transform;
+ symmetry_class->active_changed = gimp_mandala_active_changed;
+
+ GIMP_CONFIG_PROP_DOUBLE (object_class, PROP_CENTER_X,
+ "center-x",
+ _("Center abscissa"),
+ NULL,
+ 0.0, G_MAXDOUBLE, 0.0,
+ GIMP_PARAM_STATIC_STRINGS |
+ GIMP_SYMMETRY_PARAM_GUI);
+
+ pspec = g_object_class_find_property (object_class, "center-x");
+ gegl_param_spec_set_property_key (pspec, "unit", "pixel-coordinate");
+ gegl_param_spec_set_property_key (pspec, "axis", "x");
+
+ GIMP_CONFIG_PROP_DOUBLE (object_class, PROP_CENTER_Y,
+ "center-y",
+ _("Center ordinate"),
+ NULL,
+ 0.0, G_MAXDOUBLE, 0.0,
+ GIMP_PARAM_STATIC_STRINGS |
+ GIMP_SYMMETRY_PARAM_GUI);
+
+ pspec = g_object_class_find_property (object_class, "center-y");
+ gegl_param_spec_set_property_key (pspec, "unit", "pixel-coordinate");
+ gegl_param_spec_set_property_key (pspec, "axis", "y");
+
+ GIMP_CONFIG_PROP_INT (object_class, PROP_SIZE,
+ "size",
+ _("Number of points"),
+ NULL,
+ 1, 100, 6.0,
+ GIMP_PARAM_STATIC_STRINGS |
+ GIMP_SYMMETRY_PARAM_GUI);
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_DISABLE_TRANSFORMATION,
+ "disable-transformation",
+ _("Disable brush transform"),
+ _("Disable brush rotation"),
+ FALSE,
+ GIMP_PARAM_STATIC_STRINGS |
+ GIMP_SYMMETRY_PARAM_GUI);
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_ENABLE_REFLECTION,
+ "enable-reflection",
+ _("Kaleidoscope"),
+ _("Reflect consecutive strokes"),
+ FALSE,
+ GIMP_PARAM_STATIC_STRINGS |
+ GIMP_SYMMETRY_PARAM_GUI);
+}
+
+static void
+gimp_mandala_init (GimpMandala *mandala)
+{
+}
+
+static void
+gimp_mandala_constructed (GObject *object)
+{
+ GimpSymmetry *sym = GIMP_SYMMETRY (object);
+
+ g_signal_connect_object (sym->image, "size-changed-detailed",
+ G_CALLBACK (gimp_mandala_image_size_changed_cb),
+ sym, 0);
+}
+
+static void
+gimp_mandala_finalize (GObject *object)
+{
+ GimpMandala *mandala = GIMP_MANDALA (object);
+
+ g_clear_object (&mandala->horizontal_guide);
+ g_clear_object (&mandala->vertical_guide);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gimp_mandala_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpMandala *mandala = GIMP_MANDALA (object);
+ GimpImage *image = GIMP_SYMMETRY (mandala)->image;
+
+ switch (property_id)
+ {
+ case PROP_CENTER_X:
+ if (g_value_get_double (value) > 0.0 &&
+ g_value_get_double (value) < (gdouble) gimp_image_get_width (image))
+ {
+ mandala->center_x = g_value_get_double (value);
+
+ if (mandala->vertical_guide)
+ {
+ g_signal_handlers_block_by_func (mandala->vertical_guide,
+ gimp_mandala_guide_position_cb,
+ mandala);
+ gimp_image_move_guide (image, mandala->vertical_guide,
+ mandala->center_x,
+ FALSE);
+ g_signal_handlers_unblock_by_func (mandala->vertical_guide,
+ gimp_mandala_guide_position_cb,
+ mandala);
+ }
+ }
+ break;
+
+ case PROP_CENTER_Y:
+ if (g_value_get_double (value) > 0.0 &&
+ g_value_get_double (value) < (gdouble) gimp_image_get_height (image))
+ {
+ mandala->center_y = g_value_get_double (value);
+
+ if (mandala->horizontal_guide)
+ {
+ g_signal_handlers_block_by_func (mandala->horizontal_guide,
+ gimp_mandala_guide_position_cb,
+ mandala);
+ gimp_image_move_guide (image, mandala->horizontal_guide,
+ mandala->center_y,
+ FALSE);
+ g_signal_handlers_unblock_by_func (mandala->horizontal_guide,
+ gimp_mandala_guide_position_cb,
+ mandala);
+ }
+ }
+ break;
+
+ case PROP_SIZE:
+ mandala->size = g_value_get_int (value);
+ break;
+
+ case PROP_DISABLE_TRANSFORMATION:
+ mandala->disable_transformation = g_value_get_boolean (value);
+ break;
+
+ case PROP_ENABLE_REFLECTION:
+ mandala->enable_reflection = g_value_get_boolean (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_mandala_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpMandala *mandala = GIMP_MANDALA (object);
+
+ switch (property_id)
+ {
+ case PROP_CENTER_X:
+ g_value_set_double (value, mandala->center_x);
+ break;
+ case PROP_CENTER_Y:
+ g_value_set_double (value, mandala->center_y);
+ break;
+ case PROP_SIZE:
+ g_value_set_int (value, mandala->size);
+ break;
+ case PROP_DISABLE_TRANSFORMATION:
+ g_value_set_boolean (value, mandala->disable_transformation);
+ break;
+ case PROP_ENABLE_REFLECTION:
+ g_value_set_boolean (value, mandala->enable_reflection);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_mandala_active_changed (GimpSymmetry *sym)
+{
+ GimpMandala *mandala = GIMP_MANDALA (sym);
+
+ if (sym->active)
+ {
+ if (! mandala->horizontal_guide)
+ gimp_mandala_add_guide (mandala, GIMP_ORIENTATION_HORIZONTAL);
+
+ if (! mandala->vertical_guide)
+ gimp_mandala_add_guide (mandala, GIMP_ORIENTATION_VERTICAL);
+ }
+ else
+ {
+ if (mandala->horizontal_guide)
+ gimp_mandala_remove_guide (mandala, GIMP_ORIENTATION_HORIZONTAL);
+
+ if (mandala->vertical_guide)
+ gimp_mandala_remove_guide (mandala, GIMP_ORIENTATION_VERTICAL);
+ }
+}
+
+static void
+gimp_mandala_add_guide (GimpMandala *mandala,
+ GimpOrientationType orientation)
+{
+ GimpSymmetry *sym = GIMP_SYMMETRY (mandala);
+ GimpImage *image;
+ Gimp *gimp;
+ GimpGuide *guide;
+ gint position;
+
+ image = sym->image;
+ gimp = image->gimp;
+
+ guide = gimp_guide_custom_new (orientation,
+ gimp->next_guide_ID++,
+ GIMP_GUIDE_STYLE_MANDALA);
+
+ if (orientation == GIMP_ORIENTATION_HORIZONTAL)
+ {
+ mandala->horizontal_guide = guide;
+
+ /* Mandala guide position at first activation is at canvas middle. */
+ if (mandala->center_y < 1.0)
+ mandala->center_y = (gdouble) gimp_image_get_height (image) / 2.0;
+
+ position = (gint) mandala->center_y;
+ }
+ else
+ {
+ mandala->vertical_guide = guide;
+
+ /* Mandala guide position at first activation is at canvas middle. */
+ if (mandala->center_x < 1.0)
+ mandala->center_x = (gdouble) gimp_image_get_width (image) / 2.0;
+
+ position = (gint) mandala->center_x;
+ }
+
+ g_signal_connect (guide, "removed",
+ G_CALLBACK (gimp_mandala_guide_removed_cb),
+ mandala);
+
+ gimp_image_add_guide (image, guide, position);
+
+ g_signal_connect (guide, "notify::position",
+ G_CALLBACK (gimp_mandala_guide_position_cb),
+ mandala);
+}
+
+static void
+gimp_mandala_remove_guide (GimpMandala *mandala,
+ GimpOrientationType orientation)
+{
+ GimpSymmetry *sym = GIMP_SYMMETRY (mandala);
+ GimpImage *image;
+ GimpGuide *guide;
+
+ image = sym->image;
+ guide = (orientation == GIMP_ORIENTATION_HORIZONTAL) ?
+ mandala->horizontal_guide : mandala->vertical_guide;
+
+ g_signal_handlers_disconnect_by_func (guide,
+ gimp_mandala_guide_removed_cb,
+ mandala);
+ g_signal_handlers_disconnect_by_func (guide,
+ gimp_mandala_guide_position_cb,
+ mandala);
+
+ gimp_image_remove_guide (image, guide, FALSE);
+ g_object_unref (guide);
+
+ if (orientation == GIMP_ORIENTATION_HORIZONTAL)
+ mandala->horizontal_guide = NULL;
+ else
+ mandala->vertical_guide = NULL;
+}
+
+static void
+gimp_mandala_guide_removed_cb (GObject *object,
+ GimpMandala *mandala)
+{
+ GimpSymmetry *sym = GIMP_SYMMETRY (mandala);
+
+ g_signal_handlers_disconnect_by_func (object,
+ gimp_mandala_guide_removed_cb,
+ mandala);
+ g_signal_handlers_disconnect_by_func (object,
+ gimp_mandala_guide_position_cb,
+ mandala);
+
+ if (GIMP_GUIDE (object) == mandala->horizontal_guide)
+ {
+ g_object_unref (mandala->horizontal_guide);
+
+ mandala->horizontal_guide = NULL;
+ mandala->center_y = 0.0;
+
+ gimp_mandala_remove_guide (mandala, GIMP_ORIENTATION_VERTICAL);
+ }
+ else if (GIMP_GUIDE (object) == mandala->vertical_guide)
+ {
+ g_object_unref (mandala->vertical_guide);
+ mandala->vertical_guide = NULL;
+ mandala->center_x = 0.0;
+
+ gimp_mandala_remove_guide (mandala, GIMP_ORIENTATION_HORIZONTAL);
+ }
+
+ gimp_image_symmetry_remove (sym->image, sym);
+}
+
+static void
+gimp_mandala_guide_position_cb (GObject *object,
+ GParamSpec *pspec,
+ GimpMandala *mandala)
+{
+ GimpGuide *guide = GIMP_GUIDE (object);
+
+ if (guide == mandala->horizontal_guide)
+ {
+ g_object_set (G_OBJECT (mandala),
+ "center-y", (gdouble) gimp_guide_get_position (guide),
+ NULL);
+ }
+ else if (guide == mandala->vertical_guide)
+ {
+ g_object_set (G_OBJECT (mandala),
+ "center-x", (gdouble) gimp_guide_get_position (guide),
+ NULL);
+ }
+}
+
+static void
+gimp_mandala_update_strokes (GimpSymmetry *sym,
+ GimpDrawable *drawable,
+ GimpCoords *origin)
+{
+ GimpMandala *mandala = GIMP_MANDALA (sym);
+ GimpCoords *coords;
+ GimpMatrix3 matrix;
+ gdouble slice_angle;
+ gdouble mid_slice_angle = 0.0;
+ gdouble center_x, center_y;
+ gint offset_x, offset_y;
+ gint i;
+
+ gimp_item_get_offset (GIMP_ITEM (drawable), &offset_x, &offset_y);
+
+ center_x = mandala->center_x - offset_x;
+ center_y = mandala->center_y - offset_y;
+
+ g_list_free_full (sym->strokes, g_free);
+ sym->strokes = NULL;
+
+ coords = g_memdup (sym->origin, sizeof (GimpCoords));
+ sym->strokes = g_list_prepend (sym->strokes, coords);
+
+ /* The angle of each slice, in radians. */
+ slice_angle = 2.0 * G_PI / mandala->size;
+
+ if (mandala->enable_reflection)
+ {
+ /* Find out in which slice the user is currently drawing. */
+ gdouble angle = atan2 (sym->origin->y - center_y,
+ sym->origin->x - center_x);
+ gint slice_no = (int) floor(angle/slice_angle);
+
+ /* Angle where the middle of that slice is. */
+ mid_slice_angle = slice_no * slice_angle + slice_angle / 2.0;
+ }
+
+ for (i = 1; i < mandala->size; i++)
+ {
+ gdouble new_x, new_y;
+
+ coords = g_memdup (sym->origin, sizeof (GimpCoords));
+ gimp_matrix3_identity (&matrix);
+ gimp_matrix3_translate (&matrix,
+ -center_x,
+ -center_y);
+ if (mandala->enable_reflection && i % 2 == 1)
+ {
+ /* Reflecting over the mid_slice_angle axis, reflects slice without changing position. */
+ gimp_matrix3_rotate(&matrix, -mid_slice_angle);
+ gimp_matrix3_scale (&matrix, 1, -1);
+ gimp_matrix3_rotate(&matrix, mid_slice_angle - i * slice_angle);
+ }
+ else
+ {
+ gimp_matrix3_rotate (&matrix, - i * slice_angle);
+ }
+ gimp_matrix3_translate (&matrix,
+ +center_x,
+ +center_y);
+ gimp_matrix3_transform_point (&matrix,
+ coords->x,
+ coords->y,
+ &new_x,
+ &new_y);
+ coords->x = new_x;
+ coords->y = new_y;
+ sym->strokes = g_list_prepend (sym->strokes, coords);
+ }
+
+ sym->strokes = g_list_reverse (sym->strokes);
+
+ g_signal_emit_by_name (sym, "strokes-updated", sym->image);
+}
+
+static void
+gimp_mandala_get_transform (GimpSymmetry *sym,
+ gint stroke,
+ gdouble *angle,
+ gboolean *reflect)
+{
+ GimpMandala *mandala = GIMP_MANDALA (sym);
+ gdouble slice_angle;
+
+ if (mandala->disable_transformation)
+ return;
+
+ slice_angle = 360.0 / mandala->size;
+
+ if (mandala->enable_reflection && stroke % 2 == 1)
+ {
+ /* Find out in which slice the user is currently drawing. */
+ gdouble origin_angle = gimp_rad_to_deg (atan2 (sym->origin->y - mandala->center_y,
+ sym->origin->x - mandala->center_x));
+ gint slice_no = (int) floor(origin_angle/slice_angle);
+
+ /* Angle where the middle of that slice is. */
+ gdouble mid_slice_angle = slice_no * slice_angle + slice_angle / 2.0;
+
+ *angle = 180.0 - (-2 * mid_slice_angle + stroke * slice_angle) ;
+ *reflect = TRUE;
+ }
+ else
+ {
+ *angle = stroke * slice_angle;
+ }
+}
+
+static void
+gimp_mandala_image_size_changed_cb (GimpImage *image,
+ gint previous_origin_x,
+ gint previous_origin_y,
+ gint previous_width,
+ gint previous_height,
+ GimpSymmetry *sym)
+{
+ if (previous_width != gimp_image_get_width (image) ||
+ previous_height != gimp_image_get_height (image))
+ {
+ g_signal_emit_by_name (sym, "gui-param-changed", sym->image);
+ }
+}
diff --git a/app/core/gimpsymmetry-mandala.h b/app/core/gimpsymmetry-mandala.h
new file mode 100644
index 0000000..0978a40
--- /dev/null
+++ b/app/core/gimpsymmetry-mandala.h
@@ -0,0 +1,61 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpsymmetry-mandala.h
+ * Copyright (C) 2015 Jehan <jehan@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_MANDALA_H__
+#define __GIMP_MANDALA_H__
+
+
+#include "gimpsymmetry.h"
+
+
+#define GIMP_TYPE_MANDALA (gimp_mandala_get_type ())
+#define GIMP_MANDALA(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_MANDALA, GimpMandala))
+#define GIMP_MANDALA_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_MANDALA, GimpMandalaClass))
+#define GIMP_IS_MANDALA(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_MANDALA))
+#define GIMP_IS_MANDALA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_MANDALA))
+#define GIMP_MANDALA_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_MANDALA, GimpMandalaClass))
+
+
+typedef struct _GimpMandalaClass GimpMandalaClass;
+
+struct _GimpMandala
+{
+ GimpSymmetry parent_instance;
+
+ gdouble center_x;
+ gdouble center_y;
+ gint size;
+ gboolean disable_transformation;
+ gboolean enable_reflection;
+
+ GimpGuide *horizontal_guide;
+ GimpGuide *vertical_guide;
+};
+
+struct _GimpMandalaClass
+{
+ GimpSymmetryClass parent_class;
+};
+
+
+GType gimp_mandala_get_type (void) G_GNUC_CONST;
+
+
+#endif /* __GIMP_MANDALA_H__ */
diff --git a/app/core/gimpsymmetry-mirror.c b/app/core/gimpsymmetry-mirror.c
new file mode 100644
index 0000000..66a0eaa
--- /dev/null
+++ b/app/core/gimpsymmetry-mirror.c
@@ -0,0 +1,738 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpsymmetry-mirror.c
+ * Copyright (C) 2015 Jehan <jehan@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <cairo.h>
+#include <gegl.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+#include "libgimpconfig/gimpconfig.h"
+
+#include "core-types.h"
+
+#include "gimp.h"
+#include "gimp-cairo.h"
+#include "gimpbrush.h"
+#include "gimpguide.h"
+#include "gimpimage.h"
+#include "gimpimage-guides.h"
+#include "gimpimage-symmetry.h"
+#include "gimpitem.h"
+#include "gimpsymmetry-mirror.h"
+
+#include "gimp-intl.h"
+
+
+enum
+{
+ PROP_0,
+ PROP_HORIZONTAL_SYMMETRY,
+ PROP_VERTICAL_SYMMETRY,
+ PROP_POINT_SYMMETRY,
+ PROP_DISABLE_TRANSFORMATION,
+ PROP_MIRROR_POSITION_X,
+ PROP_MIRROR_POSITION_Y
+};
+
+
+/* Local function prototypes */
+
+static void gimp_mirror_constructed (GObject *object);
+static void gimp_mirror_finalize (GObject *object);
+static void gimp_mirror_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_mirror_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static void gimp_mirror_update_strokes (GimpSymmetry *mirror,
+ GimpDrawable *drawable,
+ GimpCoords *origin);
+static void gimp_mirror_get_transform (GimpSymmetry *mirror,
+ gint stroke,
+ gdouble *angle,
+ gboolean *reflect);
+static void gimp_mirror_reset (GimpMirror *mirror);
+static void gimp_mirror_add_guide (GimpMirror *mirror,
+ GimpOrientationType orientation);
+static void gimp_mirror_remove_guide (GimpMirror *mirror,
+ GimpOrientationType orientation);
+static void gimp_mirror_guide_removed_cb (GObject *object,
+ GimpMirror *mirror);
+static void gimp_mirror_guide_position_cb (GObject *object,
+ GParamSpec *pspec,
+ GimpMirror *mirror);
+static void gimp_mirror_active_changed (GimpSymmetry *sym);
+static void gimp_mirror_set_horizontal_symmetry (GimpMirror *mirror,
+ gboolean active);
+static void gimp_mirror_set_vertical_symmetry (GimpMirror *mirror,
+ gboolean active);
+static void gimp_mirror_set_point_symmetry (GimpMirror *mirror,
+ gboolean active);
+
+static void gimp_mirror_image_size_changed_cb (GimpImage *image,
+ gint previous_origin_x,
+ gint previous_origin_y,
+ gint previous_width,
+ gint previous_height,
+ GimpSymmetry *sym);
+
+G_DEFINE_TYPE (GimpMirror, gimp_mirror, GIMP_TYPE_SYMMETRY)
+
+#define parent_class gimp_mirror_parent_class
+
+
+static void
+gimp_mirror_class_init (GimpMirrorClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpSymmetryClass *symmetry_class = GIMP_SYMMETRY_CLASS (klass);
+ GParamSpec *pspec;
+
+ object_class->constructed = gimp_mirror_constructed;
+ object_class->finalize = gimp_mirror_finalize;
+ object_class->set_property = gimp_mirror_set_property;
+ object_class->get_property = gimp_mirror_get_property;
+
+ symmetry_class->label = _("Mirror");
+ symmetry_class->update_strokes = gimp_mirror_update_strokes;
+ symmetry_class->get_transform = gimp_mirror_get_transform;
+ symmetry_class->active_changed = gimp_mirror_active_changed;
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_HORIZONTAL_SYMMETRY,
+ "horizontal-symmetry",
+ _("Horizontal Symmetry"),
+ _("Reflect the initial stroke across a horizontal axis"),
+ FALSE,
+ GIMP_PARAM_STATIC_STRINGS |
+ GIMP_SYMMETRY_PARAM_GUI);
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_VERTICAL_SYMMETRY,
+ "vertical-symmetry",
+ _("Vertical Symmetry"),
+ _("Reflect the initial stroke across a vertical axis"),
+ FALSE,
+ GIMP_PARAM_STATIC_STRINGS |
+ GIMP_SYMMETRY_PARAM_GUI);
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_POINT_SYMMETRY,
+ "point-symmetry",
+ _("Central Symmetry"),
+ _("Invert the initial stroke through a point"),
+ FALSE,
+ GIMP_PARAM_STATIC_STRINGS |
+ GIMP_SYMMETRY_PARAM_GUI);
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_DISABLE_TRANSFORMATION,
+ "disable-transformation",
+ _("Disable brush transform"),
+ _("Disable brush reflection"),
+ FALSE,
+ GIMP_PARAM_STATIC_STRINGS |
+ GIMP_SYMMETRY_PARAM_GUI);
+
+ GIMP_CONFIG_PROP_DOUBLE (object_class, PROP_MIRROR_POSITION_X,
+ "mirror-position-x",
+ _("Vertical axis position"),
+ NULL,
+ 0.0, G_MAXDOUBLE, 0.0,
+ GIMP_PARAM_STATIC_STRINGS |
+ GIMP_SYMMETRY_PARAM_GUI);
+
+ pspec = g_object_class_find_property (object_class, "mirror-position-x");
+ gegl_param_spec_set_property_key (pspec, "unit", "pixel-coordinate");
+ gegl_param_spec_set_property_key (pspec, "axis", "x");
+
+ GIMP_CONFIG_PROP_DOUBLE (object_class, PROP_MIRROR_POSITION_Y,
+ "mirror-position-y",
+ _("Horizontal axis position"),
+ NULL,
+ 0.0, G_MAXDOUBLE, 0.0,
+ GIMP_PARAM_STATIC_STRINGS |
+ GIMP_SYMMETRY_PARAM_GUI);
+
+ pspec = g_object_class_find_property (object_class, "mirror-position-y");
+ gegl_param_spec_set_property_key (pspec, "unit", "pixel-coordinate");
+ gegl_param_spec_set_property_key (pspec, "axis", "y");
+}
+
+static void
+gimp_mirror_init (GimpMirror *mirror)
+{
+}
+
+static void
+gimp_mirror_constructed (GObject *object)
+{
+ GimpSymmetry *sym = GIMP_SYMMETRY (object);
+
+ g_signal_connect_object (sym->image, "size-changed-detailed",
+ G_CALLBACK (gimp_mirror_image_size_changed_cb),
+ sym, 0);
+}
+
+static void
+gimp_mirror_finalize (GObject *object)
+{
+ GimpMirror *mirror = GIMP_MIRROR (object);
+
+ g_clear_object (&mirror->horizontal_guide);
+ g_clear_object (&mirror->vertical_guide);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gimp_mirror_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpMirror *mirror = GIMP_MIRROR (object);
+ GimpImage *image = GIMP_SYMMETRY (mirror)->image;
+
+ switch (property_id)
+ {
+ case PROP_HORIZONTAL_SYMMETRY:
+ gimp_mirror_set_horizontal_symmetry (mirror,
+ g_value_get_boolean (value));
+ break;
+
+ case PROP_VERTICAL_SYMMETRY:
+ gimp_mirror_set_vertical_symmetry (mirror,
+ g_value_get_boolean (value));
+ break;
+
+ case PROP_POINT_SYMMETRY:
+ gimp_mirror_set_point_symmetry (mirror,
+ g_value_get_boolean (value));
+ break;
+
+ case PROP_DISABLE_TRANSFORMATION:
+ mirror->disable_transformation = g_value_get_boolean (value);
+ break;
+
+ case PROP_MIRROR_POSITION_X:
+ if (g_value_get_double (value) >= 0.0 &&
+ g_value_get_double (value) < (gdouble) gimp_image_get_width (image))
+ {
+ mirror->mirror_position_x = g_value_get_double (value);
+
+ if (mirror->vertical_guide)
+ {
+ g_signal_handlers_block_by_func (mirror->vertical_guide,
+ gimp_mirror_guide_position_cb,
+ mirror);
+ gimp_image_move_guide (image, mirror->vertical_guide,
+ mirror->mirror_position_x,
+ FALSE);
+ g_signal_handlers_unblock_by_func (mirror->vertical_guide,
+ gimp_mirror_guide_position_cb,
+ mirror);
+ }
+ }
+ break;
+
+ case PROP_MIRROR_POSITION_Y:
+ if (g_value_get_double (value) >= 0.0 &&
+ g_value_get_double (value) < (gdouble) gimp_image_get_height (image))
+ {
+ mirror->mirror_position_y = g_value_get_double (value);
+
+ if (mirror->horizontal_guide)
+ {
+ g_signal_handlers_block_by_func (mirror->horizontal_guide,
+ gimp_mirror_guide_position_cb,
+ mirror);
+ gimp_image_move_guide (image, mirror->horizontal_guide,
+ mirror->mirror_position_y,
+ FALSE);
+ g_signal_handlers_unblock_by_func (mirror->horizontal_guide,
+ gimp_mirror_guide_position_cb,
+ mirror);
+ }
+ }
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_mirror_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpMirror *mirror = GIMP_MIRROR (object);
+
+ switch (property_id)
+ {
+ case PROP_HORIZONTAL_SYMMETRY:
+ g_value_set_boolean (value, mirror->horizontal_mirror);
+ break;
+ case PROP_VERTICAL_SYMMETRY:
+ g_value_set_boolean (value, mirror->vertical_mirror);
+ break;
+ case PROP_POINT_SYMMETRY:
+ g_value_set_boolean (value, mirror->point_symmetry);
+ break;
+ case PROP_DISABLE_TRANSFORMATION:
+ g_value_set_boolean (value, mirror->disable_transformation);
+ break;
+ case PROP_MIRROR_POSITION_X:
+ g_value_set_double (value, mirror->mirror_position_x);
+ break;
+ case PROP_MIRROR_POSITION_Y:
+ g_value_set_double (value, mirror->mirror_position_y);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_mirror_update_strokes (GimpSymmetry *sym,
+ GimpDrawable *drawable,
+ GimpCoords *origin)
+{
+ GimpMirror *mirror = GIMP_MIRROR (sym);
+ GList *strokes = NULL;
+ GimpCoords *coords;
+ gdouble mirror_position_x, mirror_position_y;
+ gint offset_x, offset_y;
+
+ gimp_item_get_offset (GIMP_ITEM (drawable), &offset_x, &offset_y);
+
+ mirror_position_x = mirror->mirror_position_x - offset_x;
+ mirror_position_y = mirror->mirror_position_y - offset_y;
+
+ g_list_free_full (sym->strokes, g_free);
+ strokes = g_list_prepend (strokes,
+ g_memdup (origin, sizeof (GimpCoords)));
+
+ if (mirror->horizontal_mirror)
+ {
+ coords = g_memdup (origin, sizeof (GimpCoords));
+ coords->y = 2.0 * mirror_position_y - origin->y;
+ strokes = g_list_prepend (strokes, coords);
+ }
+
+ if (mirror->vertical_mirror)
+ {
+ coords = g_memdup (origin, sizeof (GimpCoords));
+ coords->x = 2.0 * mirror_position_x - origin->x;
+ strokes = g_list_prepend (strokes, coords);
+ }
+
+ if (mirror->point_symmetry)
+ {
+ coords = g_memdup (origin, sizeof (GimpCoords));
+ coords->x = 2.0 * mirror_position_x - origin->x;
+ coords->y = 2.0 * mirror_position_y - origin->y;
+ strokes = g_list_prepend (strokes, coords);
+ }
+
+ sym->strokes = g_list_reverse (strokes);
+
+ g_signal_emit_by_name (sym, "strokes-updated", sym->image);
+}
+
+static void
+gimp_mirror_get_transform (GimpSymmetry *sym,
+ gint stroke,
+ gdouble *angle,
+ gboolean *reflect)
+{
+ GimpMirror *mirror = GIMP_MIRROR (sym);
+
+ if (mirror->disable_transformation)
+ return;
+
+ if (! mirror->horizontal_mirror && stroke >= 1)
+ stroke++;
+
+ if (! mirror->vertical_mirror && stroke >= 2)
+ stroke++;
+
+ switch (stroke)
+ {
+ /* original */
+ case 0:
+ break;
+
+ /* horizontal */
+ case 1:
+ *angle = 180.0;
+ *reflect = TRUE;
+ break;
+
+ /* vertical */
+ case 2:
+ *reflect = TRUE;
+ break;
+
+ /* central */
+ case 3:
+ *angle = 180.0;
+ break;
+
+ default:
+ g_return_if_reached ();
+ }
+}
+
+static void
+gimp_mirror_reset (GimpMirror *mirror)
+{
+ GimpSymmetry *sym = GIMP_SYMMETRY (mirror);
+
+ if (sym->origin)
+ {
+ gimp_symmetry_set_origin (sym, sym->drawable,
+ sym->origin);
+ }
+}
+
+static void
+gimp_mirror_add_guide (GimpMirror *mirror,
+ GimpOrientationType orientation)
+{
+ GimpSymmetry *sym = GIMP_SYMMETRY (mirror);
+ GimpImage *image;
+ Gimp *gimp;
+ GimpGuide *guide;
+ gdouble position;
+
+ image = sym->image;
+ gimp = image->gimp;
+
+ guide = gimp_guide_custom_new (orientation,
+ gimp->next_guide_ID++,
+ GIMP_GUIDE_STYLE_MIRROR);
+
+ if (orientation == GIMP_ORIENTATION_HORIZONTAL)
+ {
+ /* Mirror guide position at first activation is at canvas middle. */
+ if (mirror->mirror_position_y < 1.0)
+ position = gimp_image_get_height (image) / 2.0;
+ else
+ position = mirror->mirror_position_y;
+
+ g_object_set (mirror,
+ "mirror-position-y", position,
+ NULL);
+
+ mirror->horizontal_guide = guide;
+ }
+ else
+ {
+ /* Mirror guide position at first activation is at canvas middle. */
+ if (mirror->mirror_position_x < 1.0)
+ position = gimp_image_get_width (image) / 2.0;
+ else
+ position = mirror->mirror_position_x;
+
+ g_object_set (mirror,
+ "mirror-position-x", position,
+ NULL);
+
+ mirror->vertical_guide = guide;
+ }
+
+ g_signal_connect (guide, "removed",
+ G_CALLBACK (gimp_mirror_guide_removed_cb),
+ mirror);
+
+ gimp_image_add_guide (image, guide, (gint) position);
+
+ g_signal_connect (guide, "notify::position",
+ G_CALLBACK (gimp_mirror_guide_position_cb),
+ mirror);
+}
+
+static void
+gimp_mirror_remove_guide (GimpMirror *mirror,
+ GimpOrientationType orientation)
+{
+ GimpSymmetry *sym = GIMP_SYMMETRY (mirror);
+ GimpImage *image;
+ GimpGuide *guide;
+
+ image = sym->image;
+ guide = (orientation == GIMP_ORIENTATION_HORIZONTAL) ?
+ mirror->horizontal_guide : mirror->vertical_guide;
+
+ /* The guide may have already been removed, for instance from GUI. */
+ if (guide)
+ {
+ g_signal_handlers_disconnect_by_func (G_OBJECT (guide),
+ gimp_mirror_guide_removed_cb,
+ mirror);
+ g_signal_handlers_disconnect_by_func (G_OBJECT (guide),
+ gimp_mirror_guide_position_cb,
+ mirror);
+
+ gimp_image_remove_guide (image, guide, FALSE);
+ g_object_unref (guide);
+
+ if (orientation == GIMP_ORIENTATION_HORIZONTAL)
+ mirror->horizontal_guide = NULL;
+ else
+ mirror->vertical_guide = NULL;
+ }
+}
+
+static void
+gimp_mirror_guide_removed_cb (GObject *object,
+ GimpMirror *mirror)
+{
+ GimpSymmetry *symmetry = GIMP_SYMMETRY (mirror);
+
+ g_signal_handlers_disconnect_by_func (object,
+ gimp_mirror_guide_removed_cb,
+ mirror);
+ g_signal_handlers_disconnect_by_func (object,
+ gimp_mirror_guide_position_cb,
+ mirror);
+
+ if (GIMP_GUIDE (object) == mirror->horizontal_guide)
+ {
+ g_object_unref (mirror->horizontal_guide);
+ mirror->horizontal_guide = NULL;
+
+ g_object_set (mirror,
+ "horizontal-symmetry", FALSE,
+ NULL);
+ g_object_set (mirror,
+ "point-symmetry", FALSE,
+ NULL);
+ g_object_set (mirror,
+ "mirror-position-y", 0.0,
+ NULL);
+
+ if (mirror->vertical_guide &&
+ ! mirror->vertical_mirror)
+ {
+ g_signal_handlers_disconnect_by_func (G_OBJECT (mirror->vertical_guide),
+ gimp_mirror_guide_removed_cb,
+ mirror);
+ g_signal_handlers_disconnect_by_func (G_OBJECT (mirror->vertical_guide),
+ gimp_mirror_guide_position_cb,
+ mirror);
+
+ gimp_image_remove_guide (symmetry->image,
+ mirror->vertical_guide,
+ FALSE);
+ g_clear_object (&mirror->vertical_guide);
+ }
+ }
+ else if (GIMP_GUIDE (object) == mirror->vertical_guide)
+ {
+ g_object_unref (mirror->vertical_guide);
+ mirror->vertical_guide = NULL;
+
+ g_object_set (mirror,
+ "vertical-symmetry", FALSE,
+ NULL);
+ g_object_set (mirror,
+ "point-symmetry", FALSE,
+ NULL);
+ g_object_set (mirror,
+ "mirror-position-x", 0.0,
+ NULL);
+
+ if (mirror->horizontal_guide &&
+ ! mirror->horizontal_mirror)
+ {
+ g_signal_handlers_disconnect_by_func (G_OBJECT (mirror->horizontal_guide),
+ gimp_mirror_guide_removed_cb,
+ mirror);
+ g_signal_handlers_disconnect_by_func (G_OBJECT (mirror->horizontal_guide),
+ gimp_mirror_guide_position_cb,
+ mirror);
+
+ gimp_image_remove_guide (symmetry->image,
+ mirror->horizontal_guide,
+ FALSE);
+ g_clear_object (&mirror->horizontal_guide);
+ }
+ }
+
+ if (mirror->horizontal_guide == NULL &&
+ mirror->vertical_guide == NULL)
+ {
+ gimp_image_symmetry_remove (symmetry->image,
+ GIMP_SYMMETRY (mirror));
+ }
+ else
+ {
+ gimp_mirror_reset (mirror);
+ g_signal_emit_by_name (mirror, "gui-param-changed",
+ GIMP_SYMMETRY (mirror)->image);
+ }
+}
+
+static void
+gimp_mirror_guide_position_cb (GObject *object,
+ GParamSpec *pspec,
+ GimpMirror *mirror)
+{
+ GimpGuide *guide = GIMP_GUIDE (object);
+
+ if (guide == mirror->horizontal_guide)
+ {
+ g_object_set (mirror,
+ "mirror-position-y", (gdouble) gimp_guide_get_position (guide),
+ NULL);
+ }
+ else if (guide == mirror->vertical_guide)
+ {
+ g_object_set (mirror,
+ "mirror-position-x", (gdouble) gimp_guide_get_position (guide),
+ NULL);
+ }
+}
+
+static void
+gimp_mirror_active_changed (GimpSymmetry *sym)
+{
+ GimpMirror *mirror = GIMP_MIRROR (sym);
+
+ if (sym->active)
+ {
+ if ((mirror->horizontal_mirror || mirror->point_symmetry) &&
+ ! mirror->horizontal_guide)
+ gimp_mirror_add_guide (mirror, GIMP_ORIENTATION_HORIZONTAL);
+
+ if ((mirror->vertical_mirror || mirror->point_symmetry) &&
+ ! mirror->vertical_guide)
+ gimp_mirror_add_guide (mirror, GIMP_ORIENTATION_VERTICAL);
+ }
+ else
+ {
+ if (mirror->horizontal_guide)
+ gimp_mirror_remove_guide (mirror, GIMP_ORIENTATION_HORIZONTAL);
+
+ if (mirror->vertical_guide)
+ gimp_mirror_remove_guide (mirror, GIMP_ORIENTATION_VERTICAL);
+ }
+}
+
+static void
+gimp_mirror_set_horizontal_symmetry (GimpMirror *mirror,
+ gboolean active)
+{
+ if (active == mirror->horizontal_mirror)
+ return;
+
+ mirror->horizontal_mirror = active;
+
+ if (active)
+ {
+ if (! mirror->horizontal_guide)
+ gimp_mirror_add_guide (mirror, GIMP_ORIENTATION_HORIZONTAL);
+ }
+ else if (! mirror->point_symmetry)
+ {
+ gimp_mirror_remove_guide (mirror, GIMP_ORIENTATION_HORIZONTAL);
+ }
+
+ gimp_mirror_reset (mirror);
+}
+
+static void
+gimp_mirror_set_vertical_symmetry (GimpMirror *mirror,
+ gboolean active)
+{
+ if (active == mirror->vertical_mirror)
+ return;
+
+ mirror->vertical_mirror = active;
+
+ if (active)
+ {
+ if (! mirror->vertical_guide)
+ gimp_mirror_add_guide (mirror, GIMP_ORIENTATION_VERTICAL);
+ }
+ else if (! mirror->point_symmetry)
+ {
+ gimp_mirror_remove_guide (mirror, GIMP_ORIENTATION_VERTICAL);
+ }
+
+ gimp_mirror_reset (mirror);
+}
+
+static void
+gimp_mirror_set_point_symmetry (GimpMirror *mirror,
+ gboolean active)
+{
+ if (active == mirror->point_symmetry)
+ return;
+
+ mirror->point_symmetry = active;
+
+ if (active)
+ {
+ /* Show the horizontal guide unless already shown */
+ if (! mirror->horizontal_guide)
+ gimp_mirror_add_guide (mirror, GIMP_ORIENTATION_HORIZONTAL);
+
+ /* Show the vertical guide unless already shown */
+ if (! mirror->vertical_guide)
+ gimp_mirror_add_guide (mirror, GIMP_ORIENTATION_VERTICAL);
+ }
+ else
+ {
+ /* Remove the horizontal guide unless needed by horizontal mirror */
+ if (! mirror->horizontal_mirror)
+ gimp_mirror_remove_guide (mirror, GIMP_ORIENTATION_HORIZONTAL);
+
+ /* Remove the vertical guide unless needed by vertical mirror */
+ if (! mirror->vertical_mirror)
+ gimp_mirror_remove_guide (mirror, GIMP_ORIENTATION_VERTICAL);
+ }
+
+ gimp_mirror_reset (mirror);
+}
+
+static void
+gimp_mirror_image_size_changed_cb (GimpImage *image,
+ gint previous_origin_x,
+ gint previous_origin_y,
+ gint previous_width,
+ gint previous_height,
+ GimpSymmetry *sym)
+{
+ if (previous_width != gimp_image_get_width (image) ||
+ previous_height != gimp_image_get_height (image))
+ {
+ g_signal_emit_by_name (sym, "gui-param-changed", sym->image);
+ }
+}
diff --git a/app/core/gimpsymmetry-mirror.h b/app/core/gimpsymmetry-mirror.h
new file mode 100644
index 0000000..f9ac26a
--- /dev/null
+++ b/app/core/gimpsymmetry-mirror.h
@@ -0,0 +1,62 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpsymmetry-mirror.h
+ * Copyright (C) 2015 Jehan <jehan@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_MIRROR_H__
+#define __GIMP_MIRROR_H__
+
+
+#include "gimpsymmetry.h"
+
+
+#define GIMP_TYPE_MIRROR (gimp_mirror_get_type ())
+#define GIMP_MIRROR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_MIRROR, GimpMirror))
+#define GIMP_MIRROR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_MIRROR, GimpMirrorClass))
+#define GIMP_IS_MIRROR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_MIRROR))
+#define GIMP_IS_MIRROR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_MIRROR))
+#define GIMP_MIRROR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_MIRROR, GimpMirrorClass))
+
+
+typedef struct _GimpMirrorClass GimpMirrorClass;
+
+struct _GimpMirror
+{
+ GimpSymmetry parent_instance;
+
+ gboolean horizontal_mirror;
+ gboolean vertical_mirror;
+ gboolean point_symmetry;
+ gboolean disable_transformation;
+
+ gdouble mirror_position_y;
+ gdouble mirror_position_x;
+ GimpGuide *horizontal_guide;
+ GimpGuide *vertical_guide;
+};
+
+struct _GimpMirrorClass
+{
+ GimpSymmetryClass parent_class;
+};
+
+
+GType gimp_mirror_get_type (void) G_GNUC_CONST;
+
+
+#endif /* __GIMP_MIRROR_H__ */
diff --git a/app/core/gimpsymmetry-tiling.c b/app/core/gimpsymmetry-tiling.c
new file mode 100644
index 0000000..555f584
--- /dev/null
+++ b/app/core/gimpsymmetry-tiling.c
@@ -0,0 +1,442 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpsymmetry-tiling.c
+ * Copyright (C) 2015 Jehan <jehan@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <math.h>
+#include <string.h>
+
+#include <gegl.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+#include "libgimpconfig/gimpconfig.h"
+
+#include "core-types.h"
+
+#include "gimp.h"
+#include "gimpdrawable.h"
+#include "gimpimage.h"
+#include "gimpitem.h"
+#include "gimpsymmetry-tiling.h"
+
+#include "gimp-intl.h"
+
+
+/* Using same epsilon as in GLIB. */
+#define G_DOUBLE_EPSILON (1e-90)
+
+enum
+{
+ PROP_0,
+
+ PROP_INTERVAL_X,
+ PROP_INTERVAL_Y,
+ PROP_SHIFT,
+ PROP_MAX_X,
+ PROP_MAX_Y
+};
+
+
+/* Local function prototypes */
+
+static void gimp_tiling_constructed (GObject *object);
+static void gimp_tiling_finalize (GObject *object);
+static void gimp_tiling_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_tiling_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static void gimp_tiling_update_strokes (GimpSymmetry *tiling,
+ GimpDrawable *drawable,
+ GimpCoords *origin);
+static void gimp_tiling_image_size_changed_cb (GimpImage *image,
+ gint previous_origin_x,
+ gint previous_origin_y,
+ gint previous_width,
+ gint previous_height,
+ GimpSymmetry *sym);
+
+
+G_DEFINE_TYPE (GimpTiling, gimp_tiling, GIMP_TYPE_SYMMETRY)
+
+#define parent_class gimp_tiling_parent_class
+
+
+static void
+gimp_tiling_class_init (GimpTilingClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpSymmetryClass *symmetry_class = GIMP_SYMMETRY_CLASS (klass);
+ GParamSpec *pspec;
+
+ object_class->constructed = gimp_tiling_constructed;
+ object_class->finalize = gimp_tiling_finalize;
+ object_class->set_property = gimp_tiling_set_property;
+ object_class->get_property = gimp_tiling_get_property;
+
+ symmetry_class->label = _("Tiling");
+ symmetry_class->update_strokes = gimp_tiling_update_strokes;
+
+ GIMP_CONFIG_PROP_DOUBLE (object_class, PROP_INTERVAL_X,
+ "interval-x",
+ _("Interval X"),
+ _("Interval on the X axis (pixels)"),
+ 0.0, G_MAXDOUBLE, 0.0,
+ GIMP_PARAM_STATIC_STRINGS |
+ GIMP_SYMMETRY_PARAM_GUI);
+
+ pspec = g_object_class_find_property (object_class, "interval-x");
+ gegl_param_spec_set_property_key (pspec, "unit", "pixel-distance");
+ gegl_param_spec_set_property_key (pspec, "axis", "x");
+
+ GIMP_CONFIG_PROP_DOUBLE (object_class, PROP_INTERVAL_Y,
+ "interval-y",
+ _("Interval Y"),
+ _("Interval on the Y axis (pixels)"),
+ 0.0, G_MAXDOUBLE, 0.0,
+ GIMP_PARAM_STATIC_STRINGS |
+ GIMP_SYMMETRY_PARAM_GUI);
+
+ pspec = g_object_class_find_property (object_class, "interval-y");
+ gegl_param_spec_set_property_key (pspec, "unit", "pixel-distance");
+ gegl_param_spec_set_property_key (pspec, "axis", "y");
+
+ GIMP_CONFIG_PROP_DOUBLE (object_class, PROP_SHIFT,
+ "shift",
+ _("Shift"),
+ _("X-shift between lines (pixels)"),
+ 0.0, G_MAXDOUBLE, 0.0,
+ GIMP_PARAM_STATIC_STRINGS |
+ GIMP_SYMMETRY_PARAM_GUI);
+
+ pspec = g_object_class_find_property (object_class, "shift");
+ gegl_param_spec_set_property_key (pspec, "unit", "pixel-distance");
+ gegl_param_spec_set_property_key (pspec, "axis", "x");
+
+ GIMP_CONFIG_PROP_INT (object_class, PROP_MAX_X,
+ "max-x",
+ _("Max strokes X"),
+ _("Maximum number of strokes on the X axis"),
+ 0, 100, 0,
+ GIMP_PARAM_STATIC_STRINGS |
+ GIMP_SYMMETRY_PARAM_GUI);
+
+ GIMP_CONFIG_PROP_INT (object_class, PROP_MAX_Y,
+ "max-y",
+ _("Max strokes Y"),
+ _("Maximum number of strokes on the Y axis"),
+ 0, 100, 0,
+ GIMP_PARAM_STATIC_STRINGS |
+ GIMP_SYMMETRY_PARAM_GUI);
+}
+
+static void
+gimp_tiling_init (GimpTiling *tiling)
+{
+}
+
+static void
+gimp_tiling_constructed (GObject *object)
+{
+ GimpSymmetry *sym = GIMP_SYMMETRY (object);
+ GimpTiling *tiling = GIMP_TILING (object);
+
+ g_signal_connect_object (sym->image, "size-changed-detailed",
+ G_CALLBACK (gimp_tiling_image_size_changed_cb),
+ sym, 0);
+
+ /* Set reasonable defaults. */
+ tiling->interval_x = gimp_image_get_width (sym->image) / 2;
+ tiling->interval_y = gimp_image_get_height (sym->image) / 2;
+}
+
+static void
+gimp_tiling_finalize (GObject *object)
+{
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gimp_tiling_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpTiling *tiling = GIMP_TILING (object);
+ GimpSymmetry *sym = GIMP_SYMMETRY (tiling);
+
+ switch (property_id)
+ {
+ case PROP_INTERVAL_X:
+ if (sym->image)
+ {
+ gdouble new_x = g_value_get_double (value);
+
+ if (new_x < gimp_image_get_width (sym->image))
+ {
+ tiling->interval_x = new_x;
+
+ if (tiling->interval_x <= tiling->shift + G_DOUBLE_EPSILON)
+ {
+ GValue val = G_VALUE_INIT;
+
+ g_value_init (&val, G_TYPE_DOUBLE);
+ g_value_set_double (&val, 0.0);
+ g_object_set_property (G_OBJECT (object), "shift", &val);
+ }
+ if (sym->drawable)
+ gimp_tiling_update_strokes (sym, sym->drawable, sym->origin);
+ }
+ }
+ break;
+
+ case PROP_INTERVAL_Y:
+ {
+ gdouble new_y = g_value_get_double (value);
+
+ if (new_y < gimp_image_get_height (sym->image))
+ {
+ tiling->interval_y = new_y;
+
+ if (tiling->interval_y <= G_DOUBLE_EPSILON)
+ {
+ GValue val = G_VALUE_INIT;
+
+ g_value_init (&val, G_TYPE_DOUBLE);
+ g_value_set_double (&val, 0.0);
+ g_object_set_property (G_OBJECT (object), "shift", &val);
+ }
+ if (sym->drawable)
+ gimp_tiling_update_strokes (sym, sym->drawable, sym->origin);
+ }
+ }
+ break;
+
+ case PROP_SHIFT:
+ {
+ gdouble new_shift = g_value_get_double (value);
+
+ if (new_shift == 0.0 ||
+ (tiling->interval_y != 0.0 && new_shift < tiling->interval_x))
+ {
+ tiling->shift = new_shift;
+ if (sym->drawable)
+ gimp_tiling_update_strokes (sym, sym->drawable, sym->origin);
+ }
+ }
+ break;
+
+ case PROP_MAX_X:
+ tiling->max_x = g_value_get_int (value);
+ if (sym->drawable)
+ gimp_tiling_update_strokes (sym, sym->drawable, sym->origin);
+ break;
+
+ case PROP_MAX_Y:
+ tiling->max_y = g_value_get_int (value);
+ if (sym->drawable)
+ gimp_tiling_update_strokes (sym, sym->drawable, sym->origin);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_tiling_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpTiling *tiling = GIMP_TILING (object);
+
+ switch (property_id)
+ {
+ case PROP_INTERVAL_X:
+ g_value_set_double (value, tiling->interval_x);
+ break;
+ case PROP_INTERVAL_Y:
+ g_value_set_double (value, tiling->interval_y);
+ break;
+ case PROP_SHIFT:
+ g_value_set_double (value, tiling->shift);
+ break;
+ case PROP_MAX_X:
+ g_value_set_int (value, tiling->max_x);
+ break;
+ case PROP_MAX_Y:
+ g_value_set_int (value, tiling->max_y);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_tiling_update_strokes (GimpSymmetry *sym,
+ GimpDrawable *drawable,
+ GimpCoords *origin)
+{
+ GimpTiling *tiling = GIMP_TILING (sym);
+ GList *strokes = NULL;
+ GimpCoords *coords;
+ gdouble width;
+ gdouble height;
+ gdouble startx = origin->x;
+ gdouble starty = origin->y;
+ gdouble x;
+ gdouble y;
+ gint x_count;
+ gint y_count;
+
+ g_list_free_full (sym->strokes, g_free);
+ sym->strokes = NULL;
+
+ width = gimp_item_get_width (GIMP_ITEM (drawable));
+ height = gimp_item_get_height (GIMP_ITEM (drawable));
+
+ if (sym->stateful)
+ {
+ /* While I can compute exactly the right number of strokes to
+ * paint on-canvas for stateless tools, stateful tools need to
+ * always have the same number and order of strokes. For this
+ * reason, I compute strokes to fill 2 times the width and height.
+ * This makes the symmetry less efficient with stateful tools, but
+ * also weird behavior may happen if you decide to paint out of
+ * canvas and expect tiling to work in-canvas since it won't
+ * actually be infinite (as no new strokes can be added while
+ * painting since we are stateful).
+ */
+ gint i, j;
+
+ if (tiling->interval_x < 1.0)
+ {
+ x_count = 1;
+ }
+ else if (tiling->max_x == 0)
+ {
+ x_count = (gint) ceil (width / tiling->interval_x);
+ startx -= tiling->interval_x * (gdouble) x_count;
+ x_count = 2 * x_count + 1;
+ }
+ else
+ {
+ x_count = tiling->max_x;
+ }
+
+ if (tiling->interval_y < 1.0)
+ {
+ y_count = 1;
+ }
+ else if (tiling->max_y == 0)
+ {
+ y_count = (gint) ceil (height / tiling->interval_y);
+ starty -= tiling->interval_y * (gdouble) y_count;
+ y_count = 2 * y_count + 1;
+ }
+ else
+ {
+ y_count = tiling->max_y;
+ }
+
+ for (i = 0, x = startx; i < x_count; i++)
+ {
+ for (j = 0, y = starty; j < y_count; j++)
+ {
+ coords = g_memdup (origin, sizeof (GimpCoords));
+ coords->x = x;
+ coords->y = y;
+ strokes = g_list_prepend (strokes, coords);
+
+ y += tiling->interval_y;
+ }
+ x += tiling->interval_x;
+ }
+ }
+ else
+ {
+ if (origin->x > 0 && tiling->max_x == 0 && tiling->interval_x >= 1.0)
+ startx = fmod (origin->x, tiling->interval_x) - tiling->interval_x;
+
+ if (origin->y > 0 && tiling->max_y == 0 && tiling->interval_y >= 1.0)
+ {
+ starty = fmod (origin->y, tiling->interval_y) - tiling->interval_y;
+
+ if (tiling->shift > 0.0)
+ startx -= tiling->shift * floor (origin->y / tiling->interval_y + 1);
+ }
+
+ for (y_count = 0, y = starty; y < height + tiling->interval_y;
+ y_count++, y += tiling->interval_y)
+ {
+ if (tiling->max_y && y_count >= tiling->max_y)
+ break;
+
+ for (x_count = 0, x = startx; x < width + tiling->interval_x;
+ x_count++, x += tiling->interval_x)
+ {
+ if (tiling->max_x && x_count >= tiling->max_x)
+ break;
+
+ coords = g_memdup (origin, sizeof (GimpCoords));
+ coords->x = x;
+ coords->y = y;
+ strokes = g_list_prepend (strokes, coords);
+
+ if (tiling->interval_x < 1.0)
+ break;
+ }
+
+ if (tiling->max_x || startx + tiling->shift <= 0.0)
+ startx = startx + tiling->shift;
+ else
+ startx = startx - tiling->interval_x + tiling->shift;
+
+ if (tiling->interval_y < 1.0)
+ break;
+ }
+ }
+
+ sym->strokes = strokes;
+
+ g_signal_emit_by_name (sym, "strokes-updated", sym->image);
+}
+
+static void
+gimp_tiling_image_size_changed_cb (GimpImage *image,
+ gint previous_origin_x,
+ gint previous_origin_y,
+ gint previous_width,
+ gint previous_height,
+ GimpSymmetry *sym)
+{
+ if (previous_width != gimp_image_get_width (image) ||
+ previous_height != gimp_image_get_height (image))
+ {
+ g_signal_emit_by_name (sym, "gui-param-changed", sym->image);
+ }
+}
diff --git a/app/core/gimpsymmetry-tiling.h b/app/core/gimpsymmetry-tiling.h
new file mode 100644
index 0000000..7fbff71
--- /dev/null
+++ b/app/core/gimpsymmetry-tiling.h
@@ -0,0 +1,58 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpsymmetry-tiling.h
+ * Copyright (C) 2015 Jehan <jehan@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_TILING_H__
+#define __GIMP_TILING_H__
+
+
+#include "gimpsymmetry.h"
+
+
+#define GIMP_TYPE_TILING (gimp_tiling_get_type ())
+#define GIMP_TILING(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_TILING, GimpTiling))
+#define GIMP_TILING_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_TILING, GimpTilingClass))
+#define GIMP_IS_TILING(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_TILING))
+#define GIMP_IS_TILING_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_TILING))
+#define GIMP_TILING_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_TILING, GimpTilingClass))
+
+
+typedef struct _GimpTilingClass GimpTilingClass;
+
+struct _GimpTiling
+{
+ GimpSymmetry parent_instance;
+
+ gdouble interval_x;
+ gdouble interval_y;
+ gdouble shift;
+ gint max_x;
+ gint max_y;
+};
+
+struct _GimpTilingClass
+{
+ GimpSymmetryClass parent_class;
+};
+
+
+GType gimp_tiling_get_type (void) G_GNUC_CONST;
+
+
+#endif /* __GIMP_TILING_H__ */
diff --git a/app/core/gimpsymmetry.c b/app/core/gimpsymmetry.c
new file mode 100644
index 0000000..e0bc23b
--- /dev/null
+++ b/app/core/gimpsymmetry.c
@@ -0,0 +1,591 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpsymmetry.c
+ * Copyright (C) 2015 Jehan <jehan@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gegl.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+#include "libgimpbase/gimpbase.h"
+#include "libgimpconfig/gimpconfig.h"
+#include "libgimpmath/gimpmath.h"
+
+#include "core-types.h"
+
+#include "gegl/gimp-gegl-nodes.h"
+
+#include "gimpdrawable.h"
+#include "gimpimage.h"
+#include "gimpimage-symmetry.h"
+#include "gimpitem.h"
+#include "gimpsymmetry.h"
+
+#include "gimp-intl.h"
+
+
+enum
+{
+ STROKES_UPDATED,
+ GUI_PARAM_CHANGED,
+ ACTIVE_CHANGED,
+ LAST_SIGNAL
+};
+
+enum
+{
+ PROP_0,
+ PROP_IMAGE,
+ PROP_ACTIVE,
+ PROP_VERSION,
+};
+
+
+/* Local function prototypes */
+
+static void gimp_symmetry_finalize (GObject *object);
+static void gimp_symmetry_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_symmetry_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static void gimp_symmetry_real_update_strokes (GimpSymmetry *sym,
+ GimpDrawable *drawable,
+ GimpCoords *origin);
+static void gimp_symmetry_real_get_transform (GimpSymmetry *sym,
+ gint stroke,
+ gdouble *angle,
+ gboolean *reflect);
+static gboolean gimp_symmetry_real_update_version (GimpSymmetry *sym);
+
+
+G_DEFINE_TYPE_WITH_CODE (GimpSymmetry, gimp_symmetry, GIMP_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (GIMP_TYPE_CONFIG, NULL))
+
+#define parent_class gimp_symmetry_parent_class
+
+static guint gimp_symmetry_signals[LAST_SIGNAL] = { 0 };
+
+
+static void
+gimp_symmetry_class_init (GimpSymmetryClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ /* This signal should likely be emitted at the end of
+ * update_strokes() if stroke coordinates were changed.
+ */
+ gimp_symmetry_signals[STROKES_UPDATED] =
+ g_signal_new ("strokes-updated",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ 0,
+ NULL, NULL,
+ NULL,
+ G_TYPE_NONE,
+ 1, GIMP_TYPE_IMAGE);
+
+ /* This signal should be emitted when you request a change in the
+ * settings UI. For instance adding some settings (therefore having
+ * a dynamic UI), or changing scale min/max extremes, etc.
+ */
+ gimp_symmetry_signals[GUI_PARAM_CHANGED] =
+ g_signal_new ("gui-param-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ 0,
+ NULL, NULL,
+ NULL,
+ G_TYPE_NONE,
+ 1, GIMP_TYPE_IMAGE);
+
+ gimp_symmetry_signals[ACTIVE_CHANGED] =
+ g_signal_new ("active-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpSymmetryClass, active_changed),
+ NULL, NULL,
+ NULL,
+ G_TYPE_NONE, 0);
+
+ object_class->finalize = gimp_symmetry_finalize;
+ object_class->set_property = gimp_symmetry_set_property;
+ object_class->get_property = gimp_symmetry_get_property;
+
+ klass->label = _("None");
+ klass->update_strokes = gimp_symmetry_real_update_strokes;
+ klass->get_transform = gimp_symmetry_real_get_transform;
+ klass->active_changed = NULL;
+ klass->update_version = gimp_symmetry_real_update_version;
+
+ g_object_class_install_property (object_class, PROP_IMAGE,
+ g_param_spec_object ("image",
+ NULL, NULL,
+ GIMP_TYPE_IMAGE,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_ACTIVE,
+ "active",
+ _("Active"),
+ _("Activate symmetry painting"),
+ FALSE,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_INT (object_class, PROP_VERSION,
+ "version",
+ "Symmetry version",
+ "Version of the symmetry object",
+ -1, G_MAXINT, 0,
+ GIMP_PARAM_STATIC_STRINGS);
+}
+
+static void
+gimp_symmetry_init (GimpSymmetry *sym)
+{
+}
+
+static void
+gimp_symmetry_finalize (GObject *object)
+{
+ GimpSymmetry *sym = GIMP_SYMMETRY (object);
+
+ gimp_symmetry_clear_origin (sym);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gimp_symmetry_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpSymmetry *sym = GIMP_SYMMETRY (object);
+
+ switch (property_id)
+ {
+ case PROP_IMAGE:
+ sym->image = g_value_get_object (value);
+ break;
+ case PROP_ACTIVE:
+ sym->active = g_value_get_boolean (value);
+ g_signal_emit (sym, gimp_symmetry_signals[ACTIVE_CHANGED], 0,
+ sym->active);
+ break;
+ case PROP_VERSION:
+ sym->version = g_value_get_int (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_symmetry_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpSymmetry *sym = GIMP_SYMMETRY (object);
+
+ switch (property_id)
+ {
+ case PROP_IMAGE:
+ g_value_set_object (value, sym->image);
+ break;
+ case PROP_ACTIVE:
+ g_value_set_boolean (value, sym->active);
+ break;
+ case PROP_VERSION:
+ g_value_set_int (value, sym->version);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_symmetry_real_update_strokes (GimpSymmetry *sym,
+ GimpDrawable *drawable,
+ GimpCoords *origin)
+{
+ /* The basic symmetry just uses the origin as is. */
+ sym->strokes = g_list_prepend (sym->strokes,
+ g_memdup (origin, sizeof (GimpCoords)));
+}
+
+static void
+gimp_symmetry_real_get_transform (GimpSymmetry *sym,
+ gint stroke,
+ gdouble *angle,
+ gboolean *reflect)
+{
+ /* The basic symmetry does nothing, since no transformation of the
+ * brush painting happen. */
+}
+
+static gboolean
+gimp_symmetry_real_update_version (GimpSymmetry *symmetry)
+{
+ /* Currently all symmetries are at version 0. So all this check has to
+ * do is verify that we are at version 0.
+ * If one of the child symmetry bumps its version, it will have to
+ * override the update_version() virtual function and do any necessary
+ * update there (for instance new properties, modified properties, or
+ * whatnot).
+ */
+ gint version;
+
+ g_object_get (symmetry,
+ "version", &version,
+ NULL);
+
+ return (version == 0);
+}
+
+/***** Public Functions *****/
+
+/**
+ * gimp_symmetry_set_stateful:
+ * @sym: the #GimpSymmetry
+ * @stateful: whether the symmetry should be stateful or stateless.
+ *
+ * By default, symmetry is made stateless, which means in particular
+ * that the size of points can change from one stroke to the next, and
+ * in particular you cannot map the coordinates from a stroke to the
+ * next. I.e. stroke N at time T+1 is not necessarily the continuation
+ * of stroke N at time T.
+ * To obtain corresponding strokes, stateful tools, such as MyPaint
+ * brushes or the ink tool, need to run this function. They should reset
+ * to stateless behavior once finished painting.
+ *
+ * One of the first consequence of being stateful is that the number of
+ * strokes cannot be changed, so more strokes than possible on canvas
+ * may be computed, and oppositely it will be possible to end up in
+ * cases with missing strokes (e.g. a tiling, theoretically infinite,
+ * won't be for the ink tool if one draws too far out of canvas).
+ **/
+void
+gimp_symmetry_set_stateful (GimpSymmetry *symmetry,
+ gboolean stateful)
+{
+ symmetry->stateful = stateful;
+}
+
+/**
+ * gimp_symmetry_set_origin:
+ * @sym: the #GimpSymmetry
+ * @drawable: the #GimpDrawable where painting will happen
+ * @origin: new base coordinates.
+ *
+ * Set the symmetry to new origin coordinates and drawable.
+ **/
+void
+gimp_symmetry_set_origin (GimpSymmetry *sym,
+ GimpDrawable *drawable,
+ GimpCoords *origin)
+{
+ g_return_if_fail (GIMP_IS_SYMMETRY (sym));
+ g_return_if_fail (GIMP_IS_DRAWABLE (drawable));
+ g_return_if_fail (gimp_item_get_image (GIMP_ITEM (drawable)) == sym->image);
+
+ if (drawable != sym->drawable)
+ {
+ if (sym->drawable)
+ g_object_unref (sym->drawable);
+ sym->drawable = g_object_ref (drawable);
+ }
+
+ if (origin != sym->origin)
+ {
+ g_free (sym->origin);
+ sym->origin = g_memdup (origin, sizeof (GimpCoords));
+ }
+
+ g_list_free_full (sym->strokes, g_free);
+ sym->strokes = NULL;
+
+ GIMP_SYMMETRY_GET_CLASS (sym)->update_strokes (sym, drawable, origin);
+}
+
+/**
+ * gimp_symmetry_clear_origin:
+ * @sym: the #GimpSymmetry
+ *
+ * Clear the symmetry's origin coordinates and drawable.
+ **/
+void
+gimp_symmetry_clear_origin (GimpSymmetry *sym)
+{
+ g_return_if_fail (GIMP_IS_SYMMETRY (sym));
+
+ g_clear_object (&sym->drawable);
+
+ g_clear_pointer (&sym->origin, g_free);
+
+ g_list_free_full (sym->strokes, g_free);
+ sym->strokes = NULL;
+}
+
+/**
+ * gimp_symmetry_get_origin:
+ * @sym: the #GimpSymmetry
+ *
+ * Returns: the origin stroke coordinates.
+ * The returned value is owned by the #GimpSymmetry and must not be freed.
+ **/
+GimpCoords *
+gimp_symmetry_get_origin (GimpSymmetry *sym)
+{
+ g_return_val_if_fail (GIMP_IS_SYMMETRY (sym), NULL);
+
+ return sym->origin;
+}
+
+/**
+ * gimp_symmetry_get_size:
+ * @sym: the #GimpSymmetry
+ *
+ * Returns: the total number of strokes.
+ **/
+gint
+gimp_symmetry_get_size (GimpSymmetry *sym)
+{
+ g_return_val_if_fail (GIMP_IS_SYMMETRY (sym), 0);
+
+ return g_list_length (sym->strokes);
+}
+
+/**
+ * gimp_symmetry_get_coords:
+ * @sym: the #GimpSymmetry
+ * @stroke: the stroke number
+ *
+ * Returns: the coordinates of the stroke number @stroke.
+ * The returned value is owned by the #GimpSymmetry and must not be freed.
+ **/
+GimpCoords *
+gimp_symmetry_get_coords (GimpSymmetry *sym,
+ gint stroke)
+{
+ g_return_val_if_fail (GIMP_IS_SYMMETRY (sym), NULL);
+
+ return g_list_nth_data (sym->strokes, stroke);
+}
+
+/**
+ * gimp_symmetry_get_transform:
+ * @sym: the #GimpSymmetry
+ * @stroke: the stroke number
+ * @angle: output pointer to the transformation rotation angle,
+ * in degrees (ccw)
+ * @reflect: output pointer to the transformation reflection flag
+ *
+ * Returns: the transformation to apply to the paint content for stroke
+ * number @stroke. The transformation is comprised of rotation, possibly
+ * followed by horizontal reflection, around the stroke coordinates.
+ **/
+void
+gimp_symmetry_get_transform (GimpSymmetry *sym,
+ gint stroke,
+ gdouble *angle,
+ gboolean *reflect)
+{
+ g_return_if_fail (GIMP_IS_SYMMETRY (sym));
+ g_return_if_fail (angle != NULL);
+ g_return_if_fail (reflect != NULL);
+
+ *angle = 0.0;
+ *reflect = FALSE;
+
+ GIMP_SYMMETRY_GET_CLASS (sym)->get_transform (sym,
+ stroke,
+ angle,
+ reflect);
+}
+
+/**
+ * gimp_symmetry_get_matrix:
+ * @sym: the #GimpSymmetry
+ * @stroke: the stroke number
+ * @matrix: output pointer to the transformation matrix
+ *
+ * Returns: the transformation matrix to apply to the paint content for stroke
+ * number @stroke.
+ **/
+void
+gimp_symmetry_get_matrix (GimpSymmetry *sym,
+ gint stroke,
+ GimpMatrix3 *matrix)
+{
+ gdouble angle;
+ gboolean reflect;
+
+ g_return_if_fail (GIMP_IS_SYMMETRY (sym));
+ g_return_if_fail (matrix != NULL);
+
+ gimp_symmetry_get_transform (sym, stroke, &angle, &reflect);
+
+ gimp_matrix3_identity (matrix);
+ gimp_matrix3_rotate (matrix, -gimp_deg_to_rad (angle));
+ if (reflect)
+ gimp_matrix3_scale (matrix, -1.0, 1.0);
+}
+
+/**
+ * gimp_symmetry_get_operation:
+ * @sym: the #GimpSymmetry
+ * @stroke: the stroke number
+ *
+ * Returns: the transformation operation to apply to the paint content for
+ * stroke number @stroke, or NULL for the identity transformation.
+ *
+ * The returned #GeglNode should be freed by the caller.
+ **/
+GeglNode *
+gimp_symmetry_get_operation (GimpSymmetry *sym,
+ gint stroke)
+{
+ GimpMatrix3 matrix;
+
+ g_return_val_if_fail (GIMP_IS_SYMMETRY (sym), NULL);
+
+ gimp_symmetry_get_matrix (sym, stroke, &matrix);
+
+ if (gimp_matrix3_is_identity (&matrix))
+ return NULL;
+
+ return gimp_gegl_create_transform_node (&matrix);
+}
+
+/*
+ * gimp_symmetry_parasite_name:
+ * @type: the #GimpSymmetry's #GType
+ *
+ * Returns: a newly allocated string.
+ */
+gchar *
+gimp_symmetry_parasite_name (GType type)
+{
+ return g_strconcat ("gimp-image-symmetry:", g_type_name (type), NULL);
+}
+
+GimpParasite *
+gimp_symmetry_to_parasite (const GimpSymmetry *sym)
+{
+ GimpParasite *parasite;
+ gchar *parasite_name;
+ gchar *str;
+
+ g_return_val_if_fail (GIMP_IS_SYMMETRY (sym), NULL);
+
+ str = gimp_config_serialize_to_string (GIMP_CONFIG (sym), NULL);
+ g_return_val_if_fail (str != NULL, NULL);
+
+ parasite_name = gimp_symmetry_parasite_name (G_TYPE_FROM_INSTANCE (sym));
+ parasite = gimp_parasite_new (parasite_name,
+ GIMP_PARASITE_PERSISTENT,
+ strlen (str) + 1, str);
+ g_free (parasite_name);
+ g_free (str);
+
+ return parasite;
+}
+
+GimpSymmetry *
+gimp_symmetry_from_parasite (const GimpParasite *parasite,
+ GimpImage *image,
+ GType type)
+{
+ GimpSymmetry *symmetry;
+ gchar *parasite_name;
+ const gchar *str;
+ GError *error = NULL;
+
+ parasite_name = gimp_symmetry_parasite_name (type);
+
+ g_return_val_if_fail (parasite != NULL, NULL);
+ g_return_val_if_fail (strcmp (gimp_parasite_name (parasite),
+ parasite_name) == 0,
+ NULL);
+
+ str = gimp_parasite_data (parasite);
+
+ if (! str)
+ {
+ g_warning ("Empty symmetry parasite \"%s\"", parasite_name);
+
+ return NULL;
+ }
+
+ symmetry = gimp_image_symmetry_new (image, type);
+
+ g_object_set (symmetry,
+ "version", -1,
+ NULL);
+
+ if (! gimp_config_deserialize_string (GIMP_CONFIG (symmetry),
+ str,
+ gimp_parasite_data_size (parasite),
+ NULL,
+ &error))
+ {
+ g_printerr ("Failed to deserialize symmetry parasite: %s\n"
+ "\t- parasite name: %s\n\t- parasite data: %s\n",
+ error->message, parasite_name, str);
+ g_error_free (error);
+
+ g_object_unref (symmetry);
+ symmetry = NULL;
+ }
+ g_free (parasite_name);
+
+ if (symmetry)
+ {
+ gint version;
+
+ g_object_get (symmetry,
+ "version", &version,
+ NULL);
+ if (version == -1)
+ {
+ /* If version has not been updated, let's assume this parasite was
+ * not representing symmetry settings.
+ */
+ g_object_unref (symmetry);
+ symmetry = NULL;
+ }
+ else if (GIMP_SYMMETRY_GET_CLASS (symmetry)->update_version (symmetry) &&
+ ! GIMP_SYMMETRY_GET_CLASS (symmetry)->update_version (symmetry))
+ {
+ g_object_unref (symmetry);
+ symmetry = NULL;
+ }
+ }
+
+ return symmetry;
+}
diff --git a/app/core/gimpsymmetry.h b/app/core/gimpsymmetry.h
new file mode 100644
index 0000000..9474662
--- /dev/null
+++ b/app/core/gimpsymmetry.h
@@ -0,0 +1,106 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpsymmetry.h
+ * Copyright (C) 2015 Jehan <jehan@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_SYMMETRY_H__
+#define __GIMP_SYMMETRY_H__
+
+
+#include "gimpobject.h"
+
+
+/* shift one more than GIMP_CONFIG_PARAM_IGNORE */
+#define GIMP_SYMMETRY_PARAM_GUI (1 << (6 + G_PARAM_USER_SHIFT))
+
+
+#define GIMP_TYPE_SYMMETRY (gimp_symmetry_get_type ())
+#define GIMP_SYMMETRY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_SYMMETRY, GimpSymmetry))
+#define GIMP_SYMMETRY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_SYMMETRY, GimpSymmetryClass))
+#define GIMP_IS_SYMMETRY(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_SYMMETRY))
+#define GIMP_IS_SYMMETRY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_SYMMETRY))
+#define GIMP_SYMMETRY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_SYMMETRY, GimpSymmetryClass))
+
+
+typedef struct _GimpSymmetryClass GimpSymmetryClass;
+
+struct _GimpSymmetry
+{
+ GimpObject parent_instance;
+
+ GimpImage *image;
+ GimpDrawable *drawable;
+ GimpCoords *origin;
+ gboolean active;
+ gint version;
+
+ GList *strokes;
+ gboolean stateful;
+};
+
+struct _GimpSymmetryClass
+{
+ GimpObjectClass parent_class;
+
+ const gchar * label;
+
+ /* Virtual functions */
+ void (* update_strokes) (GimpSymmetry *symmetry,
+ GimpDrawable *drawable,
+ GimpCoords *origin);
+ void (* get_transform) (GimpSymmetry *symmetry,
+ gint stroke,
+ gdouble *angle,
+ gboolean *reflect);
+ void (* active_changed) (GimpSymmetry *symmetry);
+
+ gboolean (* update_version) (GimpSymmetry *symmetry);
+};
+
+
+GType gimp_symmetry_get_type (void) G_GNUC_CONST;
+
+void gimp_symmetry_set_stateful (GimpSymmetry *symmetry,
+ gboolean stateful);
+void gimp_symmetry_set_origin (GimpSymmetry *symmetry,
+ GimpDrawable *drawable,
+ GimpCoords *origin);
+void gimp_symmetry_clear_origin (GimpSymmetry *symmetry);
+
+GimpCoords * gimp_symmetry_get_origin (GimpSymmetry *symmetry);
+gint gimp_symmetry_get_size (GimpSymmetry *symmetry);
+GimpCoords * gimp_symmetry_get_coords (GimpSymmetry *symmetry,
+ gint stroke);
+void gimp_symmetry_get_transform (GimpSymmetry *symmetry,
+ gint stroke,
+ gdouble *angle,
+ gboolean *reflect);
+void gimp_symmetry_get_matrix (GimpSymmetry *symmetry,
+ gint stroke,
+ GimpMatrix3 *matrix);
+GeglNode * gimp_symmetry_get_operation (GimpSymmetry *symmetry,
+ gint stroke);
+
+gchar * gimp_symmetry_parasite_name (GType type);
+GimpParasite * gimp_symmetry_to_parasite (const GimpSymmetry *symmetry);
+GimpSymmetry * gimp_symmetry_from_parasite (const GimpParasite *parasite,
+ GimpImage *image,
+ GType type);
+
+
+#endif /* __GIMP_SYMMETRY_H__ */
diff --git a/app/core/gimptag.c b/app/core/gimptag.c
new file mode 100644
index 0000000..95c4954
--- /dev/null
+++ b/app/core/gimptag.c
@@ -0,0 +1,442 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimptag.c
+ * Copyright (C) 2008 Aurimas Juška <aurisj@svn.gnome.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <glib-object.h>
+#include <string.h>
+
+#include "core-types.h"
+
+#include "gimptag.h"
+
+
+#define GIMP_TAG_INTERNAL_PREFIX "gimp:"
+
+
+G_DEFINE_TYPE (GimpTag, gimp_tag, G_TYPE_OBJECT)
+
+#define parent_class gimp_tag_parent_class
+
+
+static void
+gimp_tag_class_init (GimpTagClass *klass)
+{
+}
+
+static void
+gimp_tag_init (GimpTag *tag)
+{
+ tag->tag = 0;
+ tag->collate_key = 0;
+ tag->internal = FALSE;
+}
+
+/**
+ * gimp_tag_new:
+ * @tag_string: a tag name.
+ *
+ * If given tag name is not valid, an attempt will be made to fix it.
+ *
+ * Return value: a new #GimpTag object, or NULL if tag string is invalid and
+ * cannot be fixed.
+ **/
+GimpTag *
+gimp_tag_new (const char *tag_string)
+{
+ GimpTag *tag;
+ gchar *tag_name;
+ gchar *case_folded;
+ gchar *collate_key;
+
+ g_return_val_if_fail (tag_string != NULL, NULL);
+
+ tag_name = gimp_tag_string_make_valid (tag_string);
+ if (! tag_name)
+ return NULL;
+
+ tag = g_object_new (GIMP_TYPE_TAG, NULL);
+
+ tag->tag = g_quark_from_string (tag_name);
+
+ case_folded = g_utf8_casefold (tag_name, -1);
+ collate_key = g_utf8_collate_key (case_folded, -1);
+ tag->collate_key = g_quark_from_string (collate_key);
+ g_free (collate_key);
+ g_free (case_folded);
+ g_free (tag_name);
+
+ return tag;
+}
+
+/**
+ * gimp_tag_try_new:
+ * @tag_string: a tag name.
+ *
+ * Similar to gimp_tag_new(), but returns NULL if tag is surely not equal
+ * to any of currently created tags. It is useful for tag querying to avoid
+ * unneeded comparisons. If tag is created, however, it does not mean that
+ * it would necessarily match with some other tag.
+ *
+ * Return value: new #GimpTag object, or NULL if tag will not match with any
+ * other #GimpTag.
+ **/
+GimpTag *
+gimp_tag_try_new (const char *tag_string)
+{
+ GimpTag *tag;
+ gchar *tag_name;
+ gchar *case_folded;
+ gchar *collate_key;
+ GQuark tag_quark;
+ GQuark collate_key_quark;
+
+ tag_name = gimp_tag_string_make_valid (tag_string);
+ if (! tag_name)
+ return NULL;
+
+ case_folded = g_utf8_casefold (tag_name, -1);
+ collate_key = g_utf8_collate_key (case_folded, -1);
+ collate_key_quark = g_quark_try_string (collate_key);
+ g_free (collate_key);
+ g_free (case_folded);
+
+ if (! collate_key_quark)
+ {
+ g_free (tag_name);
+ return NULL;
+ }
+
+ tag_quark = g_quark_from_string (tag_name);
+ g_free (tag_name);
+ if (! tag_quark)
+ return NULL;
+
+ tag = g_object_new (GIMP_TYPE_TAG, NULL);
+ tag->tag = tag_quark;
+ tag->collate_key = collate_key_quark;
+
+ return tag;
+}
+
+/**
+ * gimp_tag_get_internal:
+ * @tag: a gimp tag.
+ *
+ * Retrieve internal status of the tag.
+ *
+ * Return value: internal status of tag. Internal tags are not saved.
+ **/
+gboolean
+gimp_tag_get_internal (GimpTag *tag)
+{
+ g_return_val_if_fail (GIMP_IS_TAG (tag), FALSE);
+
+ return tag->internal;
+}
+
+/**
+ * gimp_tag_set_internal:
+ * @tag: a gimp tag.
+ * @internal: desired tag internal status
+ *
+ * Set internal status of the tag. Internal tags are usually automatically
+ * generated and will not be saved into users tag cache.
+ *
+ **/
+void
+gimp_tag_set_internal (GimpTag *tag, gboolean internal)
+{
+ g_return_if_fail (GIMP_IS_TAG (tag));
+
+ tag->internal = internal;
+}
+
+
+/**
+ * gimp_tag_get_name:
+ * @tag: a gimp tag.
+ *
+ * Retrieve name of the tag.
+ *
+ * Return value: name of tag.
+ **/
+const gchar *
+gimp_tag_get_name (GimpTag *tag)
+{
+ g_return_val_if_fail (GIMP_IS_TAG (tag), NULL);
+
+ return g_quark_to_string (tag->tag);
+}
+
+/**
+ * gimp_tag_get_hash:
+ * @tag: a gimp tag.
+ *
+ * Hashing function which is useful, for example, to store #GimpTag in
+ * a #GHashTable.
+ *
+ * Return value: hash value for tag.
+ **/
+guint
+gimp_tag_get_hash (GimpTag *tag)
+{
+ g_return_val_if_fail (GIMP_IS_TAG (tag), -1);
+
+ return tag->collate_key;
+}
+
+/**
+ * gimp_tag_equals:
+ * @tag: a gimp tag.
+ * @other: another gimp tag to compare with.
+ *
+ * Compares tags for equality according to tag comparison rules.
+ *
+ * Return value: TRUE if tags are equal, FALSE otherwise.
+ **/
+gboolean
+gimp_tag_equals (GimpTag *tag,
+ GimpTag *other)
+{
+ g_return_val_if_fail (GIMP_IS_TAG (tag), FALSE);
+ g_return_val_if_fail (GIMP_IS_TAG (other), FALSE);
+
+ return tag->collate_key == other->collate_key;
+}
+
+/**
+ * gimp_tag_compare_func:
+ * @p1: pointer to left-hand #GimpTag object.
+ * @p2: pointer to right-hand #GimpTag object.
+ *
+ * Compares tags according to tag comparison rules. Useful for sorting
+ * functions.
+ *
+ * Return value: meaning of return value is the same as in strcmp().
+ **/
+int
+gimp_tag_compare_func (const void *p1,
+ const void *p2)
+{
+ GimpTag *t1 = GIMP_TAG (p1);
+ GimpTag *t2 = GIMP_TAG (p2);
+
+ return g_strcmp0 (g_quark_to_string (t1->collate_key),
+ g_quark_to_string (t2->collate_key));
+}
+
+/**
+ * gimp_tag_compare_with_string:
+ * @tag: a #GimpTag object.
+ * @tag_string: the string to compare to.
+ *
+ * Compares tag and a string according to tag comparison rules. Similar to
+ * gimp_tag_compare_func(), but can be used without creating temporary tag
+ * object.
+ *
+ * Return value: meaning of return value is the same as in strcmp().
+ **/
+gint
+gimp_tag_compare_with_string (GimpTag *tag,
+ const gchar *tag_string)
+{
+ gchar *case_folded;
+ const gchar *collate_key;
+ gchar *collate_key2;
+ gint result;
+
+ g_return_val_if_fail (GIMP_IS_TAG (tag), 0);
+ g_return_val_if_fail (tag_string != NULL, 0);
+
+ collate_key = g_quark_to_string (tag->collate_key);
+ case_folded = g_utf8_casefold (tag_string, -1);
+ collate_key2 = g_utf8_collate_key (case_folded, -1);
+ result = g_strcmp0 (collate_key, collate_key2);
+ g_free (collate_key2);
+ g_free (case_folded);
+
+ return result;
+}
+
+/**
+ * gimp_tag_has_prefix:
+ * @tag: a #GimpTag object.
+ * @prefix_string: the prefix to compare to.
+ *
+ * Compares tag and a prefix according to tag comparison rules. Similar to
+ * gimp_tag_compare_with_string(), but does not work on the collate key
+ * because that can't be matched partially.
+ *
+ * Return value: wheher #tag starts with @prefix_string.
+ **/
+gboolean
+gimp_tag_has_prefix (GimpTag *tag,
+ const gchar *prefix_string)
+{
+ gchar *case_folded1;
+ gchar *case_folded2;
+ gboolean has_prefix;
+
+ g_return_val_if_fail (GIMP_IS_TAG (tag), FALSE);
+ g_return_val_if_fail (prefix_string != NULL, FALSE);
+
+ case_folded1 = g_utf8_casefold (g_quark_to_string (tag->tag), -1);
+ case_folded2 = g_utf8_casefold (prefix_string, -1);
+
+ has_prefix = g_str_has_prefix (case_folded1, case_folded2);
+
+ g_free (case_folded1);
+ g_free (case_folded2);
+
+ g_printerr ("'%s' has prefix '%s': %d\n",
+ g_quark_to_string (tag->tag), prefix_string, has_prefix);
+
+ return has_prefix;
+}
+
+/**
+ * gimp_tag_string_make_valid:
+ * @tag_string: a text string.
+ *
+ * Tries to create a valid tag string from given @tag_string.
+ *
+ * Return value: a newly allocated tag string in case given @tag_string was
+ * valid or could be fixed, otherwise NULL. Allocated value should be freed
+ * using g_free().
+ **/
+gchar *
+gimp_tag_string_make_valid (const gchar *tag_string)
+{
+ gchar *tag;
+ GString *buffer;
+ gchar *tag_cursor;
+ gunichar c;
+
+ g_return_val_if_fail (tag_string, NULL);
+
+ tag = g_utf8_normalize (tag_string, -1, G_NORMALIZE_ALL);
+ if (! tag)
+ return NULL;
+
+ tag = g_strstrip (tag);
+ if (! *tag)
+ {
+ g_free (tag);
+ return NULL;
+ }
+
+ buffer = g_string_new ("");
+ tag_cursor = tag;
+ if (g_str_has_prefix (tag_cursor, GIMP_TAG_INTERNAL_PREFIX))
+ {
+ tag_cursor += strlen (GIMP_TAG_INTERNAL_PREFIX);
+ }
+ do
+ {
+ c = g_utf8_get_char (tag_cursor);
+ tag_cursor = g_utf8_next_char (tag_cursor);
+ if (g_unichar_isprint (c)
+ && ! gimp_tag_is_tag_separator (c))
+ {
+ g_string_append_unichar (buffer, c);
+ }
+ } while (c);
+
+ g_free (tag);
+ tag = g_string_free (buffer, FALSE);
+ tag = g_strstrip (tag);
+
+ if (! *tag)
+ {
+ g_free (tag);
+ return NULL;
+ }
+
+ return tag;
+}
+
+/**
+ * gimp_tag_is_tag_separator:
+ * @c: Unicode character.
+ *
+ * Defines a set of characters that are considered tag separators. The
+ * tag separators are hand-picked from the set of characters with the
+ * Terminal_Punctuation property as specified in the version 5.1.0 of
+ * the Unicode Standard.
+ *
+ * Return value: %TRUE if the character is a tag separator.
+ */
+gboolean
+gimp_tag_is_tag_separator (gunichar c)
+{
+ switch (c)
+ {
+ case 0x002C: /* COMMA */
+ case 0x060C: /* ARABIC COMMA */
+ case 0x07F8: /* NKO COMMA */
+ case 0x1363: /* ETHIOPIC COMMA */
+ case 0x1802: /* MONGOLIAN COMMA */
+ case 0x1808: /* MONGOLIAN MANCHU COMMA */
+ case 0x3001: /* IDEOGRAPHIC COMMA */
+ case 0xA60D: /* VAI COMMA */
+ case 0xFE50: /* SMALL COMMA */
+ case 0xFF0C: /* FULLWIDTH COMMA */
+ case 0xFF64: /* HALFWIDTH IDEOGRAPHIC COMMA */
+ return TRUE;
+
+ default:
+ return FALSE;
+ }
+}
+
+/**
+ * gimp_tag_or_null_ref:
+ * @tag: a #GimpTag
+ *
+ * A simple wrapper around g_object_ref() that silently accepts #NULL.
+ **/
+void
+gimp_tag_or_null_ref (GimpTag *tag_or_null)
+{
+ if (tag_or_null)
+ {
+ g_return_if_fail (GIMP_IS_TAG (tag_or_null));
+
+ g_object_ref (tag_or_null);
+ }
+}
+
+/**
+ * gimp_tag_or_null_unref:
+ * @tag: a #GimpTag
+ *
+ * A simple wrapper around g_object_unref() that silently accepts #NULL.
+ **/
+void
+gimp_tag_or_null_unref (GimpTag *tag_or_null)
+{
+ if (tag_or_null)
+ {
+ g_return_if_fail (GIMP_IS_TAG (tag_or_null));
+
+ g_object_unref (tag_or_null);
+ }
+}
diff --git a/app/core/gimptag.h b/app/core/gimptag.h
new file mode 100644
index 0000000..0cd6c32
--- /dev/null
+++ b/app/core/gimptag.h
@@ -0,0 +1,80 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimptag.h
+ * Copyright (C) 2008 Aurimas Juška <aurisj@svn.gnome.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_TAG_H__
+#define __GIMP_TAG_H__
+
+
+#include <glib-object.h>
+
+
+#define GIMP_TYPE_TAG (gimp_tag_get_type ())
+#define GIMP_TAG(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_TAG, GimpTag))
+#define GIMP_TAG_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_TAG, GimpTagClass))
+#define GIMP_IS_TAG(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_TAG))
+#define GIMP_IS_TAG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_TAG))
+#define GIMP_TAG_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_TAG, GimpTagClass))
+
+
+typedef struct _GimpTagClass GimpTagClass;
+
+struct _GimpTag
+{
+ GObject parent_instance;
+
+ GQuark tag;
+ GQuark collate_key;
+
+ gboolean internal; /* Tags that are not serialized to disk */
+};
+
+struct _GimpTagClass
+{
+ GObjectClass parent_class;
+};
+
+GType gimp_tag_get_type (void) G_GNUC_CONST;
+
+GimpTag * gimp_tag_new (const gchar *tag_string);
+GimpTag * gimp_tag_try_new (const gchar *tag_string);
+
+const gchar * gimp_tag_get_name (GimpTag *tag);
+guint gimp_tag_get_hash (GimpTag *tag);
+
+gboolean gimp_tag_get_internal (GimpTag *tag);
+void gimp_tag_set_internal (GimpTag *tag,
+ gboolean internal);
+
+gboolean gimp_tag_equals (GimpTag *tag,
+ GimpTag *other);
+gint gimp_tag_compare_func (const void *p1,
+ const void *p2);
+gint gimp_tag_compare_with_string (GimpTag *tag,
+ const gchar *tag_string);
+gboolean gimp_tag_has_prefix (GimpTag *tag,
+ const gchar *prefix_string);
+gchar * gimp_tag_string_make_valid (const gchar *tag_string);
+gboolean gimp_tag_is_tag_separator (gunichar c);
+
+void gimp_tag_or_null_ref (GimpTag *tag_or_null);
+void gimp_tag_or_null_unref (GimpTag *tag_or_null);
+
+
+#endif /* __GIMP_TAG_H__ */
diff --git a/app/core/gimptagcache.c b/app/core/gimptagcache.c
new file mode 100644
index 0000000..a0e4534
--- /dev/null
+++ b/app/core/gimptagcache.c
@@ -0,0 +1,646 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimptagcache.c
+ * Copyright (C) 2008 Aurimas Juška <aurisj@svn.gnome.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpbase/gimpbase.h"
+#include "libgimpmath/gimpmath.h"
+#include "libgimpconfig/gimpconfig.h"
+
+#include "core-types.h"
+
+#include "config/gimpxmlparser.h"
+
+#include "gimp-memsize.h"
+#include "gimpcontext.h"
+#include "gimpdata.h"
+#include "gimplist.h"
+#include "gimptag.h"
+#include "gimptagcache.h"
+#include "gimptagged.h"
+
+#include "gimp-intl.h"
+
+
+#define GIMP_TAG_CACHE_FILE "tags.xml"
+
+/* #define DEBUG_GIMP_TAG_CACHE 1 */
+
+
+enum
+{
+ PROP_0,
+ PROP_GIMP
+};
+
+
+typedef struct
+{
+ GQuark identifier;
+ GQuark checksum;
+ GList *tags;
+ guint referenced : 1;
+} GimpTagCacheRecord;
+
+typedef struct
+{
+ GArray *records;
+ GimpTagCacheRecord current_record;
+} GimpTagCacheParseData;
+
+struct _GimpTagCachePrivate
+{
+ GArray *records;
+ GList *containers;
+};
+
+
+static void gimp_tag_cache_finalize (GObject *object);
+
+static gint64 gimp_tag_cache_get_memsize (GimpObject *object,
+ gint64 *gui_size);
+static void gimp_tag_cache_object_initialize (GimpTagged *tagged,
+ GimpTagCache *cache);
+static void gimp_tag_cache_add_object (GimpTagCache *cache,
+ GimpTagged *tagged);
+
+static void gimp_tag_cache_load_start_element (GMarkupParseContext *context,
+ const gchar *element_name,
+ const gchar **attribute_names,
+ const gchar **attribute_values,
+ gpointer user_data,
+ GError **error);
+static void gimp_tag_cache_load_end_element (GMarkupParseContext *context,
+ const gchar *element_name,
+ gpointer user_data,
+ GError **error);
+static void gimp_tag_cache_load_text (GMarkupParseContext *context,
+ const gchar *text,
+ gsize text_len,
+ gpointer user_data,
+ GError **error);
+static void gimp_tag_cache_load_error (GMarkupParseContext *context,
+ GError *error,
+ gpointer user_data);
+static const gchar * gimp_tag_cache_attribute_name_to_value
+ (const gchar **attribute_names,
+ const gchar **attribute_values,
+ const gchar *name);
+
+static GQuark gimp_tag_cache_get_error_domain (void);
+
+
+G_DEFINE_TYPE_WITH_PRIVATE (GimpTagCache, gimp_tag_cache, GIMP_TYPE_OBJECT)
+
+#define parent_class gimp_tag_cache_parent_class
+
+
+static void
+gimp_tag_cache_class_init (GimpTagCacheClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpObjectClass *gimp_object_class = GIMP_OBJECT_CLASS (klass);
+
+ object_class->finalize = gimp_tag_cache_finalize;
+
+ gimp_object_class->get_memsize = gimp_tag_cache_get_memsize;
+}
+
+static void
+gimp_tag_cache_init (GimpTagCache *cache)
+{
+ cache->priv = gimp_tag_cache_get_instance_private (cache);
+
+ cache->priv->records = g_array_new (FALSE, FALSE,
+ sizeof (GimpTagCacheRecord));
+ cache->priv->containers = NULL;
+}
+
+static void
+gimp_tag_cache_finalize (GObject *object)
+{
+ GimpTagCache *cache = GIMP_TAG_CACHE (object);
+
+ if (cache->priv->records)
+ {
+ gint i;
+
+ for (i = 0; i < cache->priv->records->len; i++)
+ {
+ GimpTagCacheRecord *rec = &g_array_index (cache->priv->records,
+ GimpTagCacheRecord, i);
+
+ g_list_free_full (rec->tags, (GDestroyNotify) g_object_unref);
+ }
+
+ g_array_free (cache->priv->records, TRUE);
+ cache->priv->records = NULL;
+ }
+
+ if (cache->priv->containers)
+ {
+ g_list_free (cache->priv->containers);
+ cache->priv->containers = NULL;
+ }
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static gint64
+gimp_tag_cache_get_memsize (GimpObject *object,
+ gint64 *gui_size)
+{
+ GimpTagCache *cache = GIMP_TAG_CACHE (object);
+ gint64 memsize = 0;
+
+ memsize += gimp_g_list_get_memsize (cache->priv->containers, 0);
+ memsize += cache->priv->records->len * sizeof (GimpTagCacheRecord);
+
+ return memsize + GIMP_OBJECT_CLASS (parent_class)->get_memsize (object,
+ gui_size);
+}
+
+/**
+ * gimp_tag_cache_new:
+ *
+ * Return value: creates new GimpTagCache object.
+ **/
+GimpTagCache *
+gimp_tag_cache_new (void)
+{
+ return g_object_new (GIMP_TYPE_TAG_CACHE, NULL);
+}
+
+static void
+gimp_tag_cache_container_add_callback (GimpTagCache *cache,
+ GimpTagged *tagged,
+ GimpContainer *not_used)
+{
+ gimp_tag_cache_add_object (cache, tagged);
+}
+
+/**
+ * gimp_tag_cache_add_container:
+ * @cache: a GimpTagCache object.
+ * @container: container containing GimpTagged objects.
+ *
+ * Adds container of GimpTagged objects to tag cache. Before calling this
+ * function tag cache must be loaded using gimp_tag_cache_load(). When tag
+ * cache is saved to file, tags are collected from objects in priv->containers.
+ **/
+void
+gimp_tag_cache_add_container (GimpTagCache *cache,
+ GimpContainer *container)
+{
+ g_return_if_fail (GIMP_IS_TAG_CACHE (cache));
+ g_return_if_fail (GIMP_IS_CONTAINER (container));
+
+ cache->priv->containers = g_list_append (cache->priv->containers, container);
+ gimp_container_foreach (container, (GFunc) gimp_tag_cache_object_initialize,
+ cache);
+
+ g_signal_connect_swapped (container, "add",
+ G_CALLBACK (gimp_tag_cache_container_add_callback),
+ cache);
+}
+
+static void
+gimp_tag_cache_add_object (GimpTagCache *cache,
+ GimpTagged *tagged)
+{
+ gchar *identifier;
+ GQuark identifier_quark = 0;
+ gchar *checksum;
+ GQuark checksum_quark = 0;
+ GList *list;
+ gint i;
+
+ identifier = gimp_tagged_get_identifier (tagged);
+
+ if (identifier)
+ {
+ identifier_quark = g_quark_try_string (identifier);
+ g_free (identifier);
+ }
+
+ if (identifier_quark)
+ {
+ for (i = 0; i < cache->priv->records->len; i++)
+ {
+ GimpTagCacheRecord *rec = &g_array_index (cache->priv->records,
+ GimpTagCacheRecord, i);
+
+ if (rec->identifier == identifier_quark)
+ {
+ for (list = rec->tags; list; list = g_list_next (list))
+ {
+ gimp_tagged_add_tag (tagged, GIMP_TAG (list->data));
+ }
+
+ rec->referenced = TRUE;
+ return;
+ }
+ }
+ }
+
+ checksum = gimp_tagged_get_checksum (tagged);
+
+ if (checksum)
+ {
+ checksum_quark = g_quark_try_string (checksum);
+ g_free (checksum);
+ }
+
+ if (checksum_quark)
+ {
+ for (i = 0; i < cache->priv->records->len; i++)
+ {
+ GimpTagCacheRecord *rec = &g_array_index (cache->priv->records,
+ GimpTagCacheRecord, i);
+
+ if (rec->checksum == checksum_quark)
+ {
+#if DEBUG_GIMP_TAG_CACHE
+ g_printerr ("remapping identifier: %s ==> %s\n",
+ rec->identifier ? g_quark_to_string (rec->identifier) : "(NULL)",
+ identifier_quark ? g_quark_to_string (identifier_quark) : "(NULL)");
+#endif
+
+ rec->identifier = identifier_quark;
+
+ for (list = rec->tags; list; list = g_list_next (list))
+ {
+ gimp_tagged_add_tag (tagged, GIMP_TAG (list->data));
+ }
+
+ rec->referenced = TRUE;
+ return;
+ }
+ }
+ }
+
+}
+
+static void
+gimp_tag_cache_object_initialize (GimpTagged *tagged,
+ GimpTagCache *cache)
+{
+ gimp_tag_cache_add_object (cache, tagged);
+}
+
+static void
+gimp_tag_cache_tagged_to_cache_record_foreach (GimpTagged *tagged,
+ GList **cache_records)
+{
+ gchar *identifier = gimp_tagged_get_identifier (tagged);
+
+ if (identifier)
+ {
+ GimpTagCacheRecord *cache_rec = g_new (GimpTagCacheRecord, 1);
+ gchar *checksum;
+
+ checksum = gimp_tagged_get_checksum (tagged);
+
+ cache_rec->identifier = g_quark_from_string (identifier);
+ cache_rec->checksum = g_quark_from_string (checksum);
+ cache_rec->tags = g_list_copy (gimp_tagged_get_tags (tagged));
+
+ g_free (checksum);
+
+ *cache_records = g_list_prepend (*cache_records, cache_rec);
+ }
+
+ g_free (identifier);
+}
+
+/**
+ * gimp_tag_cache_save:
+ * @cache: a GimpTagCache object.
+ *
+ * Saves tag cache to cache file.
+ **/
+void
+gimp_tag_cache_save (GimpTagCache *cache)
+{
+ GString *buf;
+ GList *saved_records;
+ GList *iterator;
+ GFile *file;
+ GOutputStream *output;
+ GError *error = NULL;
+ gint i;
+
+ g_return_if_fail (GIMP_IS_TAG_CACHE (cache));
+
+ saved_records = NULL;
+ for (i = 0; i < cache->priv->records->len; i++)
+ {
+ GimpTagCacheRecord *current_record = &g_array_index (cache->priv->records,
+ GimpTagCacheRecord, i);
+
+ if (! current_record->referenced && current_record->tags)
+ {
+ /* keep tagged objects which have tags assigned
+ * but were not loaded.
+ */
+ GimpTagCacheRecord *record_copy = g_new (GimpTagCacheRecord, 1);
+
+ record_copy->identifier = current_record->identifier;
+ record_copy->checksum = current_record->checksum;
+ record_copy->tags = g_list_copy (current_record->tags);
+
+ saved_records = g_list_prepend (saved_records, record_copy);
+ }
+ }
+
+ for (iterator = cache->priv->containers;
+ iterator;
+ iterator = g_list_next (iterator))
+ {
+ gimp_container_foreach (GIMP_CONTAINER (iterator->data),
+ (GFunc) gimp_tag_cache_tagged_to_cache_record_foreach,
+ &saved_records);
+ }
+
+ saved_records = g_list_reverse (saved_records);
+
+ buf = g_string_new ("");
+ g_string_append (buf, "<?xml version='1.0' encoding='UTF-8'?>\n");
+ g_string_append (buf, "<tags>\n");
+
+ for (iterator = saved_records; iterator; iterator = g_list_next (iterator))
+ {
+ GimpTagCacheRecord *cache_rec = iterator->data;
+ GList *tag_iterator;
+ gchar *identifier_string;
+ gchar *tag_string;
+
+ identifier_string = g_markup_escape_text (g_quark_to_string (cache_rec->identifier), -1);
+ g_string_append_printf (buf, "\n <resource identifier=\"%s\" checksum=\"%s\">\n",
+ identifier_string,
+ g_quark_to_string (cache_rec->checksum));
+ g_free (identifier_string);
+
+ for (tag_iterator = cache_rec->tags;
+ tag_iterator;
+ tag_iterator = g_list_next (tag_iterator))
+ {
+ GimpTag *tag = GIMP_TAG (tag_iterator->data);
+
+ if (! gimp_tag_get_internal (tag))
+ {
+ tag_string = g_markup_escape_text (gimp_tag_get_name (tag), -1);
+ g_string_append_printf (buf, " <tag>%s</tag>\n", tag_string);
+ g_free (tag_string);
+ }
+ }
+
+ g_string_append (buf, " </resource>\n");
+ }
+
+ g_string_append (buf, "</tags>\n");
+
+ file = gimp_directory_file (GIMP_TAG_CACHE_FILE, NULL);
+
+ output = G_OUTPUT_STREAM (g_file_replace (file,
+ NULL, FALSE, G_FILE_CREATE_NONE,
+ NULL, &error));
+ if (! output)
+ {
+ g_printerr ("%s\n", error->message);
+ }
+ else if (! g_output_stream_write_all (output, buf->str, buf->len,
+ NULL, NULL, &error))
+ {
+ GCancellable *cancellable = g_cancellable_new ();
+
+ g_printerr (_("Error writing '%s': %s\n"),
+ gimp_file_get_utf8_name (file), error->message);
+
+ /* Cancel the overwrite initiated by g_file_replace(). */
+ g_cancellable_cancel (cancellable);
+ g_output_stream_close (output, cancellable, NULL);
+ g_object_unref (cancellable);
+ }
+ else if (! g_output_stream_close (output, NULL, &error))
+ {
+ g_printerr (_("Error closing '%s': %s\n"),
+ gimp_file_get_utf8_name (file), error->message);
+ }
+
+ if (output)
+ g_object_unref (output);
+
+ g_clear_error (&error);
+ g_object_unref (file);
+ g_string_free (buf, TRUE);
+
+ for (iterator = saved_records;
+ iterator;
+ iterator = g_list_next (iterator))
+ {
+ GimpTagCacheRecord *cache_rec = iterator->data;
+
+ g_list_free (cache_rec->tags);
+ g_free (cache_rec);
+ }
+
+ g_list_free (saved_records);
+}
+
+/**
+ * gimp_tag_cache_load:
+ * @cache: a GimpTagCache object.
+ *
+ * Loads tag cache from file.
+ **/
+void
+gimp_tag_cache_load (GimpTagCache *cache)
+{
+ GFile *file;
+ GMarkupParser markup_parser;
+ GimpXmlParser *xml_parser;
+ GimpTagCacheParseData parse_data;
+ GError *error = NULL;
+
+ g_return_if_fail (GIMP_IS_TAG_CACHE (cache));
+
+ /* clear any previous priv->records */
+ cache->priv->records = g_array_set_size (cache->priv->records, 0);
+
+ parse_data.records = g_array_new (FALSE, FALSE, sizeof (GimpTagCacheRecord));
+ memset (&parse_data.current_record, 0, sizeof (GimpTagCacheRecord));
+
+ markup_parser.start_element = gimp_tag_cache_load_start_element;
+ markup_parser.end_element = gimp_tag_cache_load_end_element;
+ markup_parser.text = gimp_tag_cache_load_text;
+ markup_parser.passthrough = NULL;
+ markup_parser.error = gimp_tag_cache_load_error;
+
+ xml_parser = gimp_xml_parser_new (&markup_parser, &parse_data);
+
+ file = gimp_directory_file (GIMP_TAG_CACHE_FILE, NULL);
+
+ if (gimp_xml_parser_parse_gfile (xml_parser, file, &error))
+ {
+ cache->priv->records = g_array_append_vals (cache->priv->records,
+ parse_data.records->data,
+ parse_data.records->len);
+ }
+ else
+ {
+ g_printerr ("Failed to parse tag cache: %s\n",
+ error ? error->message : "WTF unknown error");
+ }
+
+ g_object_unref (file);
+ gimp_xml_parser_free (xml_parser);
+ g_array_free (parse_data.records, TRUE);
+}
+
+static void
+gimp_tag_cache_load_start_element (GMarkupParseContext *context,
+ const gchar *element_name,
+ const gchar **attribute_names,
+ const gchar **attribute_values,
+ gpointer user_data,
+ GError **error)
+{
+ GimpTagCacheParseData *parse_data = user_data;
+
+ if (! strcmp (element_name, "resource"))
+ {
+ const gchar *identifier;
+ const gchar *checksum;
+
+ identifier = gimp_tag_cache_attribute_name_to_value (attribute_names,
+ attribute_values,
+ "identifier");
+ checksum = gimp_tag_cache_attribute_name_to_value (attribute_names,
+ attribute_values,
+ "checksum");
+
+ if (! identifier)
+ {
+ g_set_error (error,
+ gimp_tag_cache_get_error_domain (),
+ 1001,
+ "Resource tag does not contain required attribute identifier.");
+ return;
+ }
+
+ memset (&parse_data->current_record, 0, sizeof (GimpTagCacheRecord));
+
+ parse_data->current_record.identifier = g_quark_from_string (identifier);
+ parse_data->current_record.checksum = g_quark_from_string (checksum);
+ }
+}
+
+static void
+gimp_tag_cache_load_end_element (GMarkupParseContext *context,
+ const gchar *element_name,
+ gpointer user_data,
+ GError **error)
+{
+ GimpTagCacheParseData *parse_data = user_data;
+
+ if (strcmp (element_name, "resource") == 0)
+ {
+ parse_data->records = g_array_append_val (parse_data->records,
+ parse_data->current_record);
+ memset (&parse_data->current_record, 0, sizeof (GimpTagCacheRecord));
+ }
+}
+
+static void
+gimp_tag_cache_load_text (GMarkupParseContext *context,
+ const gchar *text,
+ gsize text_len,
+ gpointer user_data,
+ GError **error)
+{
+ GimpTagCacheParseData *parse_data = user_data;
+ const gchar *current_element;
+ gchar buffer[2048];
+ GimpTag *tag;
+
+ current_element = g_markup_parse_context_get_element (context);
+
+ if (g_strcmp0 (current_element, "tag") == 0)
+ {
+ if (text_len >= sizeof (buffer))
+ {
+ g_set_error (error, gimp_tag_cache_get_error_domain (), 1002,
+ "Tag value is too long.");
+ return;
+ }
+
+ memcpy (buffer, text, text_len);
+ buffer[text_len] = '\0';
+
+ tag = gimp_tag_new (buffer);
+ if (tag)
+ {
+ parse_data->current_record.tags = g_list_append (parse_data->current_record.tags,
+ tag);
+ }
+ else
+ {
+ g_warning ("dropping invalid tag '%s' from '%s'\n", buffer,
+ g_quark_to_string (parse_data->current_record.identifier));
+ }
+ }
+}
+
+static void
+gimp_tag_cache_load_error (GMarkupParseContext *context,
+ GError *error,
+ gpointer user_data)
+{
+ g_printerr ("Tag cache parse error: %s\n", error->message);
+}
+
+static const gchar*
+gimp_tag_cache_attribute_name_to_value (const gchar **attribute_names,
+ const gchar **attribute_values,
+ const gchar *name)
+{
+ while (*attribute_names)
+ {
+ if (! strcmp (*attribute_names, name))
+ {
+ return *attribute_values;
+ }
+
+ attribute_names++;
+ attribute_values++;
+ }
+
+ return NULL;
+}
+
+static GQuark
+gimp_tag_cache_get_error_domain (void)
+{
+ return g_quark_from_static_string ("gimp-tag-cache-error-quark");
+}
diff --git a/app/core/gimptagcache.h b/app/core/gimptagcache.h
new file mode 100644
index 0000000..4c3a465
--- /dev/null
+++ b/app/core/gimptagcache.h
@@ -0,0 +1,63 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimptagcache.h
+ * Copyright (C) 2008 Aurimas Juška <aurisj@svn.gnome.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_TAG_CACHE_H__
+#define __GIMP_TAG_CACHE_H__
+
+
+#include "gimpobject.h"
+
+
+#define GIMP_TYPE_TAG_CACHE (gimp_tag_cache_get_type ())
+#define GIMP_TAG_CACHE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_TAG_CACHE, GimpTagCache))
+#define GIMP_TAG_CACHE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_TAG_CACHE, GimpTagCacheClass))
+#define GIMP_IS_TAG_CACHE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_TAG_CACHE))
+#define GIMP_IS_TAG_CACHE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_TAG_CACHE))
+#define GIMP_TAG_CACHE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_TAG_CACHE, GimpTagCacheClass))
+
+
+typedef struct _GimpTagCacheClass GimpTagCacheClass;
+typedef struct _GimpTagCachePrivate GimpTagCachePrivate;
+
+struct _GimpTagCache
+{
+ GimpObject parent_instance;
+
+ GimpTagCachePrivate *priv;
+};
+
+struct _GimpTagCacheClass
+{
+ GimpObjectClass parent_class;
+};
+
+
+GType gimp_tag_cache_get_type (void) G_GNUC_CONST;
+
+GimpTagCache * gimp_tag_cache_new (void);
+
+void gimp_tag_cache_save (GimpTagCache *cache);
+void gimp_tag_cache_load (GimpTagCache *cache);
+
+void gimp_tag_cache_add_container (GimpTagCache *cache,
+ GimpContainer *container);
+
+
+#endif /* __GIMP_TAG_CACHE_H__ */
diff --git a/app/core/gimptagged.c b/app/core/gimptagged.c
new file mode 100644
index 0000000..7875f9a
--- /dev/null
+++ b/app/core/gimptagged.c
@@ -0,0 +1,260 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimptagged.c
+ * Copyright (C) 2008 Sven Neumann <sven@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <glib-object.h>
+
+#include "core-types.h"
+
+#include "gimpmarshal.h"
+#include "gimptag.h"
+#include "gimptagged.h"
+
+
+enum
+{
+ TAG_ADDED,
+ TAG_REMOVED,
+ LAST_SIGNAL
+};
+
+
+G_DEFINE_INTERFACE (GimpTagged, gimp_tagged, G_TYPE_OBJECT)
+
+
+static guint gimp_tagged_signals[LAST_SIGNAL] = { 0, };
+
+
+/* private functions */
+
+
+static void
+gimp_tagged_default_init (GimpTaggedInterface *iface)
+{
+ gimp_tagged_signals[TAG_ADDED] =
+ g_signal_new ("tag-added",
+ GIMP_TYPE_TAGGED,
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GimpTaggedInterface, tag_added),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1,
+ GIMP_TYPE_TAG);
+
+ gimp_tagged_signals[TAG_REMOVED] =
+ g_signal_new ("tag-removed",
+ GIMP_TYPE_TAGGED,
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GimpTaggedInterface, tag_removed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1,
+ GIMP_TYPE_TAG);
+}
+
+
+/* public functions */
+
+
+/**
+ * gimp_tagged_add_tag:
+ * @tagged: an object that implements the %GimpTagged interface
+ * @tag: a %GimpTag
+ *
+ * Adds @tag to the @tagged object. The GimpTagged::tag-added signal
+ * is emitted if and only if the @tag was not already assigned to this
+ * object.
+ **/
+void
+gimp_tagged_add_tag (GimpTagged *tagged,
+ GimpTag *tag)
+{
+ g_return_if_fail (GIMP_IS_TAGGED (tagged));
+ g_return_if_fail (GIMP_IS_TAG (tag));
+
+ if (GIMP_TAGGED_GET_INTERFACE (tagged)->add_tag (tagged, tag))
+ {
+ g_signal_emit (tagged, gimp_tagged_signals[TAG_ADDED], 0, tag);
+ }
+}
+
+/**
+ * gimp_tagged_remove_tag:
+ * @tagged: an object that implements the %GimpTagged interface
+ * @tag: a %GimpTag
+ *
+ * Removes @tag from the @tagged object. The GimpTagged::tag-removed
+ * signal is emitted if and only if the @tag was actually assigned to
+ * this object.
+ **/
+void
+gimp_tagged_remove_tag (GimpTagged *tagged,
+ GimpTag *tag)
+{
+ GList *tag_iter;
+
+ g_return_if_fail (GIMP_IS_TAGGED (tagged));
+ g_return_if_fail (GIMP_IS_TAG (tag));
+
+ for (tag_iter = gimp_tagged_get_tags (tagged);
+ tag_iter;
+ tag_iter = g_list_next (tag_iter))
+ {
+ GimpTag *tag_ref = tag_iter->data;
+
+ if (gimp_tag_equals (tag_ref, tag))
+ {
+ g_object_ref (tag_ref);
+
+ if (GIMP_TAGGED_GET_INTERFACE (tagged)->remove_tag (tagged, tag_ref))
+ {
+ g_signal_emit (tagged, gimp_tagged_signals[TAG_REMOVED], 0,
+ tag_ref);
+ }
+
+ g_object_unref (tag_ref);
+
+ return;
+ }
+ }
+}
+
+/**
+ * gimp_tagged_set_tags:
+ * @tagged: an object that implements the %GimpTagged interface
+ * @tags: a list of tags
+ *
+ * Sets the list of tags assigned to this object. The passed list of
+ * tags is copied and should be freed by the caller.
+ **/
+void
+gimp_tagged_set_tags (GimpTagged *tagged,
+ GList *tags)
+{
+ GList *old_tags;
+ GList *list;
+
+ g_return_if_fail (GIMP_IS_TAGGED (tagged));
+
+ old_tags = g_list_copy (gimp_tagged_get_tags (tagged));
+
+ for (list = old_tags; list; list = g_list_next (list))
+ {
+ gimp_tagged_remove_tag (tagged, list->data);
+ }
+
+ g_list_free (old_tags);
+
+ for (list = tags; list; list = g_list_next (list))
+ {
+ g_return_if_fail (GIMP_IS_TAG (list->data));
+
+ gimp_tagged_add_tag (tagged, list->data);
+ }
+}
+
+/**
+ * gimp_tagged_get_tags:
+ * @tagged: an object that implements the %GimpTagged interface
+ *
+ * Returns the list of tags assigned to this object. The returned %GList
+ * is owned by the @tagged object and must not be modified or destroyed.
+ *
+ * Return value: a list of tags
+ **/
+GList *
+gimp_tagged_get_tags (GimpTagged *tagged)
+{
+ g_return_val_if_fail (GIMP_IS_TAGGED (tagged), NULL);
+
+ return GIMP_TAGGED_GET_INTERFACE (tagged)->get_tags (tagged);
+}
+
+/**
+ * gimp_tagged_get_identifier:
+ * @tagged: an object that implements the %GimpTagged interface
+ *
+ * Returns an identifier string which uniquely identifies the tagged
+ * object. Two different objects must have unique identifiers but may
+ * have the same checksum (which will be the case if one object is a
+ * copy of the other). The identifier must be the same across
+ * sessions, so for example an instance pointer cannot be used as an
+ * identifier.
+ *
+ * Return value: a newly allocated string containing unique identifier
+ * of the object. It must be freed using #g_free.
+ **/
+gchar *
+gimp_tagged_get_identifier (GimpTagged *tagged)
+{
+ g_return_val_if_fail (GIMP_IS_TAGGED (tagged), NULL);
+
+ return GIMP_TAGGED_GET_INTERFACE (tagged)->get_identifier (tagged);
+}
+
+/**
+ * gimp_tagged_get_checksum:
+ * @tagged: an object that implements the %GimpTagged interface
+ *
+ * Returns the checksum of the @tagged object. It is used to remap the
+ * tags for an object for which the identifier has changed, for
+ * example if the user has renamed a data file since the last session.
+ *
+ * If the object does not want to support such remapping (objects not
+ * stored in file for example) it can return #NULL.
+ *
+ * Return value: checksum string if object needs identifier remapping,
+ * #NULL otherwise. Returned string must be freed with #g_free().
+ **/
+gchar *
+gimp_tagged_get_checksum (GimpTagged *tagged)
+{
+ g_return_val_if_fail (GIMP_IS_TAGGED (tagged), FALSE);
+
+ return GIMP_TAGGED_GET_INTERFACE (tagged)->get_checksum (tagged);
+}
+
+/**
+ * gimp_tagged_has_tag:
+ * @tagged: an object that implements the %GimpTagged interface
+ * @tag: a %GimpTag
+ *
+ * Return value: #TRUE if the object has @tag, #FALSE otherwise.
+ **/
+gboolean
+gimp_tagged_has_tag (GimpTagged *tagged,
+ GimpTag *tag)
+{
+ GList *tag_iter;
+
+ g_return_val_if_fail (GIMP_IS_TAGGED (tagged), FALSE);
+ g_return_val_if_fail (GIMP_IS_TAG (tag), FALSE);
+
+ for (tag_iter = gimp_tagged_get_tags (tagged);
+ tag_iter;
+ tag_iter = g_list_next (tag_iter))
+ {
+ if (gimp_tag_equals (tag_iter->data, tag))
+ return TRUE;
+ }
+
+ return FALSE;
+}
diff --git a/app/core/gimptagged.h b/app/core/gimptagged.h
new file mode 100644
index 0000000..32251e9
--- /dev/null
+++ b/app/core/gimptagged.h
@@ -0,0 +1,72 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-1997 Spencer Kimball and Peter Mattis
+ *
+ * gimptagged.h
+ * Copyright (C) 2008 Sven Neumann <sven@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_TAGGED_H__
+#define __GIMP_TAGGED_H__
+
+
+#define GIMP_TYPE_TAGGED (gimp_tagged_get_type ())
+#define GIMP_IS_TAGGED(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_TAGGED))
+#define GIMP_TAGGED(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_TAGGED, GimpTagged))
+#define GIMP_TAGGED_GET_INTERFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), GIMP_TYPE_TAGGED, GimpTaggedInterface))
+
+
+typedef struct _GimpTaggedInterface GimpTaggedInterface;
+
+struct _GimpTaggedInterface
+{
+ GTypeInterface base_iface;
+
+ /* signals */
+ void (* tag_added) (GimpTagged *tagged,
+ GimpTag *tag);
+ void (* tag_removed) (GimpTagged *tagged,
+ GimpTag *tag);
+
+ /* virtual functions */
+ gboolean (* add_tag) (GimpTagged *tagged,
+ GimpTag *tag);
+ gboolean (* remove_tag) (GimpTagged *tagged,
+ GimpTag *tag);
+ GList * (* get_tags) (GimpTagged *tagged);
+ gchar * (* get_identifier) (GimpTagged *tagged);
+ gchar * (* get_checksum) (GimpTagged *tagged);
+};
+
+
+GType gimp_tagged_get_type (void) G_GNUC_CONST;
+
+void gimp_tagged_add_tag (GimpTagged *tagged,
+ GimpTag *tag);
+void gimp_tagged_remove_tag (GimpTagged *tagged,
+ GimpTag *tag);
+
+void gimp_tagged_set_tags (GimpTagged *tagged,
+ GList *tags);
+GList * gimp_tagged_get_tags (GimpTagged *tagged);
+
+gchar * gimp_tagged_get_identifier (GimpTagged *tagged);
+gchar * gimp_tagged_get_checksum (GimpTagged *tagged);
+
+gboolean gimp_tagged_has_tag (GimpTagged *tagged,
+ GimpTag *tag);
+
+
+#endif /* __GIMP_TAGGED_H__ */
diff --git a/app/core/gimptaggedcontainer.c b/app/core/gimptaggedcontainer.c
new file mode 100644
index 0000000..93ea58b
--- /dev/null
+++ b/app/core/gimptaggedcontainer.c
@@ -0,0 +1,483 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-1997 Spencer Kimball and Peter Mattis
+ *
+ * gimptaggedcontainer.c
+ * Copyright (C) 2008 Aurimas Juška <aurisj@svn.gnome.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gio/gio.h>
+
+#include "core-types.h"
+
+#include "gimp.h"
+#include "gimpmarshal.h"
+#include "gimptag.h"
+#include "gimptagged.h"
+#include "gimptaggedcontainer.h"
+
+
+enum
+{
+ TAG_COUNT_CHANGED,
+ LAST_SIGNAL
+};
+
+
+static void gimp_tagged_container_dispose (GObject *object);
+static gint64 gimp_tagged_container_get_memsize (GimpObject *object,
+ gint64 *gui_size);
+
+static void gimp_tagged_container_clear (GimpContainer *container);
+
+static void gimp_tagged_container_src_add (GimpFilteredContainer *filtered_container,
+ GimpObject *object);
+static void gimp_tagged_container_src_remove (GimpFilteredContainer *filtered_container,
+ GimpObject *object);
+static void gimp_tagged_container_src_freeze (GimpFilteredContainer *filtered_container);
+static void gimp_tagged_container_src_thaw (GimpFilteredContainer *filtered_container);
+
+static gboolean gimp_tagged_container_object_matches (GimpTaggedContainer *tagged_container,
+ GimpObject *object);
+
+static void gimp_tagged_container_tag_added (GimpTagged *tagged,
+ GimpTag *tag,
+ GimpTaggedContainer *tagged_container);
+static void gimp_tagged_container_tag_removed (GimpTagged *tagged,
+ GimpTag *tag,
+ GimpTaggedContainer *tagged_container);
+static void gimp_tagged_container_ref_tag (GimpTaggedContainer *tagged_container,
+ GimpTag *tag);
+static void gimp_tagged_container_unref_tag (GimpTaggedContainer *tagged_container,
+ GimpTag *tag);
+static void gimp_tagged_container_tag_count_changed (GimpTaggedContainer *tagged_container,
+ gint tag_count);
+
+
+G_DEFINE_TYPE (GimpTaggedContainer, gimp_tagged_container,
+ GIMP_TYPE_FILTERED_CONTAINER)
+
+#define parent_class gimp_tagged_container_parent_class
+
+static guint gimp_tagged_container_signals[LAST_SIGNAL] = { 0, };
+
+
+static void
+gimp_tagged_container_class_init (GimpTaggedContainerClass *klass)
+{
+ GObjectClass *g_object_class = G_OBJECT_CLASS (klass);
+ GimpObjectClass *gimp_object_class = GIMP_OBJECT_CLASS (klass);
+ GimpContainerClass *container_class = GIMP_CONTAINER_CLASS (klass);
+ GimpFilteredContainerClass *filtered_class = GIMP_FILTERED_CONTAINER_CLASS (klass);
+
+ g_object_class->dispose = gimp_tagged_container_dispose;
+
+ gimp_object_class->get_memsize = gimp_tagged_container_get_memsize;
+
+ container_class->clear = gimp_tagged_container_clear;
+
+ filtered_class->src_add = gimp_tagged_container_src_add;
+ filtered_class->src_remove = gimp_tagged_container_src_remove;
+ filtered_class->src_freeze = gimp_tagged_container_src_freeze;
+ filtered_class->src_thaw = gimp_tagged_container_src_thaw;
+
+ klass->tag_count_changed = gimp_tagged_container_tag_count_changed;
+
+ gimp_tagged_container_signals[TAG_COUNT_CHANGED] =
+ g_signal_new ("tag-count-changed",
+ GIMP_TYPE_TAGGED_CONTAINER,
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GimpTaggedContainerClass, tag_count_changed),
+ NULL, NULL,
+ gimp_marshal_VOID__INT,
+ G_TYPE_NONE, 1,
+ G_TYPE_INT);
+}
+
+static void
+gimp_tagged_container_init (GimpTaggedContainer *tagged_container)
+{
+ tagged_container->tag_ref_counts =
+ g_hash_table_new_full ((GHashFunc) gimp_tag_get_hash,
+ (GEqualFunc) gimp_tag_equals,
+ (GDestroyNotify) g_object_unref,
+ (GDestroyNotify) NULL);
+}
+
+static void
+gimp_tagged_container_dispose (GObject *object)
+{
+ GimpTaggedContainer *tagged_container = GIMP_TAGGED_CONTAINER (object);
+
+ if (tagged_container->filter)
+ {
+ g_list_free_full (tagged_container->filter,
+ (GDestroyNotify) gimp_tag_or_null_unref);
+ tagged_container->filter = NULL;
+ }
+
+ g_clear_pointer (&tagged_container->tag_ref_counts, g_hash_table_unref);
+
+ G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static gint64
+gimp_tagged_container_get_memsize (GimpObject *object,
+ gint64 *gui_size)
+{
+ gint64 memsize = 0;
+
+ /* FIXME take members into account */
+
+ return memsize + GIMP_OBJECT_CLASS (parent_class)->get_memsize (object,
+ gui_size);
+}
+
+static void
+gimp_tagged_container_clear (GimpContainer *container)
+{
+ GimpFilteredContainer *filtered_container = GIMP_FILTERED_CONTAINER (container);
+ GimpTaggedContainer *tagged_container = GIMP_TAGGED_CONTAINER (container);
+ GList *list;
+
+ for (list = GIMP_LIST (filtered_container->src_container)->queue->head;
+ list;
+ list = g_list_next (list))
+ {
+ g_signal_handlers_disconnect_by_func (list->data,
+ gimp_tagged_container_tag_added,
+ tagged_container);
+ g_signal_handlers_disconnect_by_func (list->data,
+ gimp_tagged_container_tag_removed,
+ tagged_container);
+ }
+
+ if (tagged_container->tag_ref_counts)
+ {
+ g_hash_table_remove_all (tagged_container->tag_ref_counts);
+ tagged_container->tag_count = 0;
+ }
+
+ GIMP_CONTAINER_CLASS (parent_class)->clear (container);
+}
+
+static void
+gimp_tagged_container_src_add (GimpFilteredContainer *filtered_container,
+ GimpObject *object)
+{
+ GimpTaggedContainer *tagged_container = GIMP_TAGGED_CONTAINER (filtered_container);
+ GList *list;
+
+ for (list = gimp_tagged_get_tags (GIMP_TAGGED (object));
+ list;
+ list = g_list_next (list))
+ {
+ gimp_tagged_container_ref_tag (tagged_container, list->data);
+ }
+
+ g_signal_connect (object, "tag-added",
+ G_CALLBACK (gimp_tagged_container_tag_added),
+ tagged_container);
+ g_signal_connect (object, "tag-removed",
+ G_CALLBACK (gimp_tagged_container_tag_removed),
+ tagged_container);
+
+ if (gimp_tagged_container_object_matches (tagged_container, object))
+ {
+ gimp_container_add (GIMP_CONTAINER (tagged_container), object);
+ }
+}
+
+static void
+gimp_tagged_container_src_remove (GimpFilteredContainer *filtered_container,
+ GimpObject *object)
+{
+ GimpTaggedContainer *tagged_container = GIMP_TAGGED_CONTAINER (filtered_container);
+ GList *list;
+
+ g_signal_handlers_disconnect_by_func (object,
+ gimp_tagged_container_tag_added,
+ tagged_container);
+ g_signal_handlers_disconnect_by_func (object,
+ gimp_tagged_container_tag_removed,
+ tagged_container);
+
+ for (list = gimp_tagged_get_tags (GIMP_TAGGED (object));
+ list;
+ list = g_list_next (list))
+ {
+ gimp_tagged_container_unref_tag (tagged_container, list->data);
+ }
+
+ if (gimp_tagged_container_object_matches (tagged_container, object))
+ {
+ gimp_container_remove (GIMP_CONTAINER (tagged_container), object);
+ }
+}
+
+static void
+gimp_tagged_container_src_freeze (GimpFilteredContainer *filtered_container)
+{
+ gimp_container_clear (GIMP_CONTAINER (filtered_container));
+}
+
+static void
+gimp_tagged_container_src_thaw (GimpFilteredContainer *filtered_container)
+{
+ GList *list;
+
+ for (list = GIMP_LIST (filtered_container->src_container)->queue->head;
+ list;
+ list = g_list_next (list))
+ {
+ gimp_tagged_container_src_add (filtered_container, list->data);
+ }
+}
+
+/**
+ * gimp_tagged_container_new:
+ * @src_container: container to be filtered.
+ *
+ * Creates a new #GimpTaggedContainer object which creates filtered
+ * data view of #GimpTagged objects. It filters @src_container for
+ * objects containing all of the filtering tags. Synchronization with
+ * @src_container data is performed automatically.
+ *
+ * Return value: a new #GimpTaggedContainer object.
+ **/
+GimpContainer *
+gimp_tagged_container_new (GimpContainer *src_container)
+{
+ GimpTaggedContainer *tagged_container;
+ GType children_type;
+ GCompareFunc sort_func;
+
+ g_return_val_if_fail (GIMP_IS_LIST (src_container), NULL);
+
+ children_type = gimp_container_get_children_type (src_container);
+ sort_func = GIMP_LIST (src_container)->sort_func;
+
+ tagged_container = g_object_new (GIMP_TYPE_TAGGED_CONTAINER,
+ "sort-func", sort_func,
+ "children-type", children_type,
+ "policy", GIMP_CONTAINER_POLICY_WEAK,
+ "unique-names", FALSE,
+ "src-container", src_container,
+ NULL);
+
+ return GIMP_CONTAINER (tagged_container);
+}
+
+/**
+ * gimp_tagged_container_set_filter:
+ * @tagged_container: a #GimpTaggedContainer object.
+ * @tags: list of #GimpTag objects.
+ *
+ * Sets list of tags to be used for filtering. Only objects which have
+ * all of the tags assigned match filtering criteria.
+ **/
+void
+gimp_tagged_container_set_filter (GimpTaggedContainer *tagged_container,
+ GList *tags)
+{
+ GList *new_filter;
+
+ g_return_if_fail (GIMP_IS_TAGGED_CONTAINER (tagged_container));
+
+ if (tags)
+ {
+ GList *list;
+
+ for (list = tags; list; list = g_list_next (list))
+ g_return_if_fail (list->data == NULL || GIMP_IS_TAG (list->data));
+ }
+
+ if (! gimp_container_frozen (GIMP_FILTERED_CONTAINER (tagged_container)->src_container))
+ {
+ gimp_tagged_container_src_freeze (GIMP_FILTERED_CONTAINER (tagged_container));
+ }
+
+ /* ref new tags first, they could be the same as the old ones */
+ new_filter = g_list_copy (tags);
+ g_list_foreach (new_filter, (GFunc) gimp_tag_or_null_ref, NULL);
+
+ g_list_free_full (tagged_container->filter,
+ (GDestroyNotify) gimp_tag_or_null_unref);
+ tagged_container->filter = new_filter;
+
+ if (! gimp_container_frozen (GIMP_FILTERED_CONTAINER (tagged_container)->src_container))
+ {
+ gimp_tagged_container_src_thaw (GIMP_FILTERED_CONTAINER (tagged_container));
+ }
+}
+
+/**
+ * gimp_tagged_container_get_filter:
+ * @tagged_container: a #GimpTaggedContainer object.
+ *
+ * Returns current tag filter. Tag filter is a list of GimpTag objects, which
+ * must be contained by each object matching filter criteria.
+ *
+ * Return value: a list of GimpTag objects used as filter. This value should
+ * not be modified or freed.
+ **/
+const GList *
+gimp_tagged_container_get_filter (GimpTaggedContainer *tagged_container)
+{
+ g_return_val_if_fail (GIMP_IS_TAGGED_CONTAINER (tagged_container), NULL);
+
+ return tagged_container->filter;
+}
+
+static gboolean
+gimp_tagged_container_object_matches (GimpTaggedContainer *tagged_container,
+ GimpObject *object)
+{
+ GList *filter_tags;
+
+ for (filter_tags = tagged_container->filter;
+ filter_tags;
+ filter_tags = g_list_next (filter_tags))
+ {
+ if (! filter_tags->data)
+ {
+ /* invalid tag - does not match */
+ return FALSE;
+ }
+
+ if (! gimp_tagged_has_tag (GIMP_TAGGED (object),
+ filter_tags->data))
+ {
+ /* match for the tag was not found.
+ * since query is of type AND, it whole fails.
+ */
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static void
+gimp_tagged_container_tag_added (GimpTagged *tagged,
+ GimpTag *tag,
+ GimpTaggedContainer *tagged_container)
+{
+ gimp_tagged_container_ref_tag (tagged_container, tag);
+
+ if (gimp_tagged_container_object_matches (tagged_container,
+ GIMP_OBJECT (tagged)) &&
+ ! gimp_container_have (GIMP_CONTAINER (tagged_container),
+ GIMP_OBJECT (tagged)))
+ {
+ gimp_container_add (GIMP_CONTAINER (tagged_container),
+ GIMP_OBJECT (tagged));
+ }
+}
+
+static void
+gimp_tagged_container_tag_removed (GimpTagged *tagged,
+ GimpTag *tag,
+ GimpTaggedContainer *tagged_container)
+{
+ gimp_tagged_container_unref_tag (tagged_container, tag);
+
+ if (! gimp_tagged_container_object_matches (tagged_container,
+ GIMP_OBJECT (tagged)) &&
+ gimp_container_have (GIMP_CONTAINER (tagged_container),
+ GIMP_OBJECT (tagged)))
+ {
+ gimp_container_remove (GIMP_CONTAINER (tagged_container),
+ GIMP_OBJECT (tagged));
+ }
+}
+
+static void
+gimp_tagged_container_ref_tag (GimpTaggedContainer *tagged_container,
+ GimpTag *tag)
+{
+ gint ref_count;
+
+ ref_count = GPOINTER_TO_INT (g_hash_table_lookup (tagged_container->tag_ref_counts,
+ tag));
+ ref_count++;
+ g_hash_table_insert (tagged_container->tag_ref_counts,
+ g_object_ref (tag),
+ GINT_TO_POINTER (ref_count));
+
+ if (ref_count == 1)
+ {
+ tagged_container->tag_count++;
+ g_signal_emit (tagged_container,
+ gimp_tagged_container_signals[TAG_COUNT_CHANGED], 0,
+ tagged_container->tag_count);
+ }
+}
+
+static void
+gimp_tagged_container_unref_tag (GimpTaggedContainer *tagged_container,
+ GimpTag *tag)
+{
+ gint ref_count;
+
+ ref_count = GPOINTER_TO_INT (g_hash_table_lookup (tagged_container->tag_ref_counts,
+ tag));
+ ref_count--;
+
+ if (ref_count > 0)
+ {
+ g_hash_table_insert (tagged_container->tag_ref_counts,
+ g_object_ref (tag),
+ GINT_TO_POINTER (ref_count));
+ }
+ else
+ {
+ if (g_hash_table_remove (tagged_container->tag_ref_counts, tag))
+ {
+ tagged_container->tag_count--;
+ g_signal_emit (tagged_container,
+ gimp_tagged_container_signals[TAG_COUNT_CHANGED], 0,
+ tagged_container->tag_count);
+ }
+ }
+}
+
+static void
+gimp_tagged_container_tag_count_changed (GimpTaggedContainer *container,
+ gint tag_count)
+{
+}
+
+/**
+ * gimp_tagged_container_get_tag_count:
+ * @container: a #GimpTaggedContainer object.
+ *
+ * Get number of distinct tags that are currently assigned to all
+ * objects in the container. The count is independent of currently
+ * used filter, it is provided for all available objects (ie. empty
+ * filter).
+ *
+ * Return value: number of distinct tags assigned to all objects in the
+ * container.
+ **/
+gint
+gimp_tagged_container_get_tag_count (GimpTaggedContainer *container)
+{
+ g_return_val_if_fail (GIMP_IS_TAGGED_CONTAINER (container), 0);
+
+ return container->tag_count;
+}
diff --git a/app/core/gimptaggedcontainer.h b/app/core/gimptaggedcontainer.h
new file mode 100644
index 0000000..02beeeb
--- /dev/null
+++ b/app/core/gimptaggedcontainer.h
@@ -0,0 +1,67 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-1997 Spencer Kimball and Peter Mattis
+ *
+ * gimptaggedcontainer.h
+ * Copyright (C) 2008 Aurimas Juška <aurisj@svn.gnome.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_TAGGED_CONTAINER_H__
+#define __GIMP_TAGGED_CONTAINER_H__
+
+
+#include "gimpfilteredcontainer.h"
+
+
+#define GIMP_TYPE_TAGGED_CONTAINER (gimp_tagged_container_get_type ())
+#define GIMP_TAGGED_CONTAINER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_TAGGED_CONTAINER, GimpTaggedContainer))
+#define GIMP_TAGGED_CONTAINER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_TAGGED_CONTAINER, GimpTaggedContainerClass))
+#define GIMP_IS_TAGGED_CONTAINER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_TAGGED_CONTAINER))
+#define GIMP_IS_TAGGED_CONTAINER_CLASS(class) (G_TYPE_CHECK_CLASS_TYPE ((class), GIMP_TYPE_TAGGED_CONTAINER))
+#define GIMP_TAGGED_CONTAINER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_TAGGED_CONTAINER, GimpTaggedContainerClass))
+
+
+typedef struct _GimpTaggedContainerClass GimpTaggedContainerClass;
+
+struct _GimpTaggedContainer
+{
+ GimpFilteredContainer parent_instance;
+
+ GList *filter;
+ GHashTable *tag_ref_counts;
+ gint tag_count;
+};
+
+struct _GimpTaggedContainerClass
+{
+ GimpFilteredContainerClass parent_class;
+
+ void (* tag_count_changed) (GimpTaggedContainer *container,
+ gint count);
+};
+
+
+GType gimp_tagged_container_get_type (void) G_GNUC_CONST;
+
+GimpContainer * gimp_tagged_container_new (GimpContainer *src_container);
+
+void gimp_tagged_container_set_filter (GimpTaggedContainer *tagged_container,
+ GList *tags);
+const GList * gimp_tagged_container_get_filter (GimpTaggedContainer *tagged_container);
+
+gint gimp_tagged_container_get_tag_count (GimpTaggedContainer *container);
+
+
+#endif /* __GIMP_TAGGED_CONTAINER_H__ */
diff --git a/app/core/gimptempbuf.c b/app/core/gimptempbuf.c
new file mode 100644
index 0000000..2879be9
--- /dev/null
+++ b/app/core/gimptempbuf.c
@@ -0,0 +1,450 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <cairo.h>
+#include <gegl.h>
+#include <gegl-utils.h>
+#include <glib/gstdio.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+#include "core-types.h"
+
+#include "libgimpcolor/gimpcolor.h"
+
+#include "gimptempbuf.h"
+
+
+#define LOCK_DATA_ALIGNMENT 16
+
+
+struct _GimpTempBuf
+{
+ gint ref_count;
+ gint width;
+ gint height;
+ const Babl *format;
+ guchar *data;
+};
+
+typedef struct
+{
+ const Babl *format;
+ GeglAccessMode access_mode;
+} LockData;
+
+G_STATIC_ASSERT (sizeof (LockData) <= LOCK_DATA_ALIGNMENT);
+
+
+/* local variables */
+
+static guintptr gimp_temp_buf_total_memsize = 0;
+
+
+/* public functions */
+
+GimpTempBuf *
+gimp_temp_buf_new (gint width,
+ gint height,
+ const Babl *format)
+{
+ GimpTempBuf *temp;
+ gint bpp;
+
+ g_return_val_if_fail (format != NULL, NULL);
+
+ bpp = babl_format_get_bytes_per_pixel (format);
+
+ g_return_val_if_fail (width > 0 && height > 0 && bpp > 0, NULL);
+ g_return_val_if_fail (G_MAXSIZE / width / height / bpp > 0, NULL);
+
+ temp = g_slice_new (GimpTempBuf);
+
+ temp->ref_count = 1;
+ temp->width = width;
+ temp->height = height;
+ temp->format = format;
+ temp->data = gegl_malloc ((gsize) width * height * bpp);
+
+ g_atomic_pointer_add (&gimp_temp_buf_total_memsize,
+ +gimp_temp_buf_get_memsize (temp));
+
+ return temp;
+}
+
+GimpTempBuf *
+gimp_temp_buf_new_from_pixbuf (GdkPixbuf *pixbuf,
+ const Babl *f_or_null)
+{
+ const Babl *format = f_or_null;
+ const Babl *fish = NULL;
+ GimpTempBuf *temp_buf;
+ const guchar *pixels;
+ gint width;
+ gint height;
+ gint rowstride;
+ gint bpp;
+ guchar *data;
+ gint i;
+
+ g_return_val_if_fail (GDK_IS_PIXBUF (pixbuf), NULL);
+
+ if (! format)
+ format = gimp_pixbuf_get_format (pixbuf);
+
+ pixels = gdk_pixbuf_get_pixels (pixbuf);
+ width = gdk_pixbuf_get_width (pixbuf);
+ height = gdk_pixbuf_get_height (pixbuf);
+ rowstride = gdk_pixbuf_get_rowstride (pixbuf);
+
+ temp_buf = gimp_temp_buf_new (width, height, format);
+ data = gimp_temp_buf_get_data (temp_buf);
+
+ bpp = babl_format_get_bytes_per_pixel (format);
+
+ if (gimp_pixbuf_get_format (pixbuf) != format)
+ fish = babl_fish (gimp_pixbuf_get_format (pixbuf), format);
+
+ for (i = 0; i < height; i++)
+ {
+ if (fish)
+ babl_process (fish, pixels, data, width);
+ else
+ memcpy (data, pixels, width * bpp);
+
+ data += width * bpp;
+ pixels += rowstride;
+ }
+
+ return temp_buf;
+}
+
+GimpTempBuf *
+gimp_temp_buf_copy (const GimpTempBuf *src)
+{
+ GimpTempBuf *dest;
+
+ g_return_val_if_fail (src != NULL, NULL);
+
+ dest = gimp_temp_buf_new (src->width, src->height, src->format);
+
+ memcpy (gimp_temp_buf_get_data (dest),
+ gimp_temp_buf_get_data (src),
+ gimp_temp_buf_get_data_size (src));
+
+ return dest;
+}
+
+GimpTempBuf *
+gimp_temp_buf_ref (const GimpTempBuf *buf)
+{
+ g_return_val_if_fail (buf != NULL, NULL);
+
+ g_atomic_int_inc ((gint *) &buf->ref_count);
+
+ return (GimpTempBuf *) buf;
+}
+
+void
+gimp_temp_buf_unref (const GimpTempBuf *buf)
+{
+ g_return_if_fail (buf != NULL);
+ g_return_if_fail (buf->ref_count > 0);
+
+ if (g_atomic_int_dec_and_test ((gint *) &buf->ref_count))
+ {
+ g_atomic_pointer_add (&gimp_temp_buf_total_memsize,
+ -gimp_temp_buf_get_memsize (buf));
+
+
+ if (buf->data)
+ gegl_free (buf->data);
+
+ g_slice_free (GimpTempBuf, (GimpTempBuf *) buf);
+ }
+}
+
+GimpTempBuf *
+gimp_temp_buf_scale (const GimpTempBuf *src,
+ gint new_width,
+ gint new_height)
+{
+ GimpTempBuf *dest;
+ const guchar *src_data;
+ guchar *dest_data;
+ gdouble x_ratio;
+ gdouble y_ratio;
+ gint bytes;
+ gint loop1;
+ gint loop2;
+
+ g_return_val_if_fail (src != NULL, NULL);
+ g_return_val_if_fail (new_width > 0 && new_height > 0, NULL);
+
+ if (new_width == src->width && new_height == src->height)
+ return gimp_temp_buf_copy (src);
+
+ dest = gimp_temp_buf_new (new_width,
+ new_height,
+ src->format);
+
+ src_data = gimp_temp_buf_get_data (src);
+ dest_data = gimp_temp_buf_get_data (dest);
+
+ x_ratio = (gdouble) src->width / (gdouble) new_width;
+ y_ratio = (gdouble) src->height / (gdouble) new_height;
+
+ bytes = babl_format_get_bytes_per_pixel (src->format);
+
+ for (loop1 = 0 ; loop1 < new_height ; loop1++)
+ {
+ for (loop2 = 0 ; loop2 < new_width ; loop2++)
+ {
+ const guchar *src_pixel;
+ guchar *dest_pixel;
+ gint i;
+
+ src_pixel = src_data +
+ (gint) (loop2 * x_ratio) * bytes +
+ (gint) (loop1 * y_ratio) * bytes * src->width;
+
+ dest_pixel = dest_data +
+ (loop2 + loop1 * new_width) * bytes;
+
+ for (i = 0 ; i < bytes; i++)
+ *dest_pixel++ = *src_pixel++;
+ }
+ }
+
+ return dest;
+}
+
+gint
+gimp_temp_buf_get_width (const GimpTempBuf *buf)
+{
+ return buf->width;
+}
+
+gint
+gimp_temp_buf_get_height (const GimpTempBuf *buf)
+{
+ return buf->height;
+}
+
+const Babl *
+gimp_temp_buf_get_format (const GimpTempBuf *buf)
+{
+ return buf->format;
+}
+
+void
+gimp_temp_buf_set_format (GimpTempBuf *buf,
+ const Babl *format)
+{
+ g_return_if_fail (babl_format_get_bytes_per_pixel (buf->format) ==
+ babl_format_get_bytes_per_pixel (format));
+
+ buf->format = format;
+}
+
+guchar *
+gimp_temp_buf_get_data (const GimpTempBuf *buf)
+{
+ return buf->data;
+}
+
+gsize
+gimp_temp_buf_get_data_size (const GimpTempBuf *buf)
+{
+ return (gsize) babl_format_get_bytes_per_pixel (buf->format) *
+ buf->width * buf->height;
+}
+
+guchar *
+gimp_temp_buf_data_clear (GimpTempBuf *buf)
+{
+ memset (buf->data, 0, gimp_temp_buf_get_data_size (buf));
+
+ return buf->data;
+}
+
+gpointer
+gimp_temp_buf_lock (const GimpTempBuf *buf,
+ const Babl *format,
+ GeglAccessMode access_mode)
+{
+ guchar *data;
+ LockData *lock_data;
+ gint n_pixels;
+ gint bpp;
+
+ g_return_val_if_fail (buf != NULL, NULL);
+
+ if (! format || format == buf->format)
+ return gimp_temp_buf_get_data (buf);
+
+ n_pixels = buf->width * buf->height;
+ bpp = babl_format_get_bytes_per_pixel (format);
+
+ data = gegl_scratch_alloc (LOCK_DATA_ALIGNMENT + n_pixels * bpp);
+
+ if ((guintptr) data % LOCK_DATA_ALIGNMENT)
+ {
+ g_free (data);
+
+ g_return_val_if_reached (NULL);
+ }
+
+ lock_data = (LockData *) data;
+ lock_data->format = format;
+ lock_data->access_mode = access_mode;
+
+ data += LOCK_DATA_ALIGNMENT;
+
+ if (access_mode & GEGL_ACCESS_READ)
+ {
+ babl_process (babl_fish (buf->format, format),
+ gimp_temp_buf_get_data (buf),
+ data,
+ n_pixels);
+ }
+
+ return data;
+}
+
+void
+gimp_temp_buf_unlock (const GimpTempBuf *buf,
+ gconstpointer data)
+{
+ LockData *lock_data;
+
+ g_return_if_fail (buf != NULL);
+ g_return_if_fail (data != NULL);
+
+ if (data == buf->data)
+ return;
+
+ lock_data = (LockData *) ((const guint8 *) data - LOCK_DATA_ALIGNMENT);
+
+ if (lock_data->access_mode & GEGL_ACCESS_WRITE)
+ {
+ babl_process (babl_fish (lock_data->format, buf->format),
+ data,
+ gimp_temp_buf_get_data (buf),
+ buf->width * buf->height);
+ }
+
+ gegl_scratch_free (lock_data);
+}
+
+gsize
+gimp_temp_buf_get_memsize (const GimpTempBuf *buf)
+{
+ if (buf)
+ return (sizeof (GimpTempBuf) + gimp_temp_buf_get_data_size (buf));
+
+ return 0;
+}
+
+GeglBuffer *
+gimp_temp_buf_create_buffer (const GimpTempBuf *temp_buf)
+{
+ GeglBuffer *buffer;
+
+ g_return_val_if_fail (temp_buf != NULL, NULL);
+
+ buffer =
+ gegl_buffer_linear_new_from_data (gimp_temp_buf_get_data (temp_buf),
+ temp_buf->format,
+ GEGL_RECTANGLE (0, 0,
+ temp_buf->width,
+ temp_buf->height),
+ GEGL_AUTO_ROWSTRIDE,
+ (GDestroyNotify) gimp_temp_buf_unref,
+ gimp_temp_buf_ref (temp_buf));
+
+ g_object_set_data (G_OBJECT (buffer),
+ "gimp-temp-buf", (GimpTempBuf *) temp_buf);
+
+ return buffer;
+}
+
+GdkPixbuf *
+gimp_temp_buf_create_pixbuf (const GimpTempBuf *temp_buf)
+{
+ GdkPixbuf *pixbuf;
+ const Babl *format;
+ const Babl *fish = NULL;
+ const guchar *data;
+ gint width;
+ gint height;
+ gint bpp;
+ guchar *pixels;
+ gint rowstride;
+ gint i;
+
+ g_return_val_if_fail (temp_buf != NULL, NULL);
+
+ data = gimp_temp_buf_get_data (temp_buf);
+ format = gimp_temp_buf_get_format (temp_buf);
+ width = gimp_temp_buf_get_width (temp_buf);
+ height = gimp_temp_buf_get_height (temp_buf);
+ bpp = babl_format_get_bytes_per_pixel (format);
+
+ pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB,
+ babl_format_has_alpha (format),
+ 8, width, height);
+
+ pixels = gdk_pixbuf_get_pixels (pixbuf);
+ rowstride = gdk_pixbuf_get_rowstride (pixbuf);
+
+ if (format != gimp_pixbuf_get_format (pixbuf))
+ fish = babl_fish (format, gimp_pixbuf_get_format (pixbuf));
+
+ for (i = 0; i < height; i++)
+ {
+ if (fish)
+ babl_process (fish, data, pixels, width);
+ else
+ memcpy (pixels, data, width * bpp);
+
+ data += width * bpp;
+ pixels += rowstride;
+ }
+
+ return pixbuf;
+}
+
+GimpTempBuf *
+gimp_gegl_buffer_get_temp_buf (GeglBuffer *buffer)
+{
+ g_return_val_if_fail (GEGL_IS_BUFFER (buffer), NULL);
+
+ return g_object_get_data (G_OBJECT (buffer), "gimp-temp-buf");
+}
+
+
+/* public functions (stats) */
+
+guint64
+gimp_temp_buf_get_total_memsize (void)
+{
+ return gimp_temp_buf_total_memsize;
+}
diff --git a/app/core/gimptempbuf.h b/app/core/gimptempbuf.h
new file mode 100644
index 0000000..d5228f5
--- /dev/null
+++ b/app/core/gimptempbuf.h
@@ -0,0 +1,67 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_TEMP_BUF_H__
+#define __GIMP_TEMP_BUF_H__
+
+
+GimpTempBuf * gimp_temp_buf_new (gint width,
+ gint height,
+ const Babl *format) G_GNUC_WARN_UNUSED_RESULT;
+GimpTempBuf * gimp_temp_buf_new_from_pixbuf (GdkPixbuf *pixbuf,
+ const Babl *f_or_null) G_GNUC_WARN_UNUSED_RESULT;
+GimpTempBuf * gimp_temp_buf_copy (const GimpTempBuf *src) G_GNUC_WARN_UNUSED_RESULT;
+
+GimpTempBuf * gimp_temp_buf_ref (const GimpTempBuf *buf);
+void gimp_temp_buf_unref (const GimpTempBuf *buf);
+
+GimpTempBuf * gimp_temp_buf_scale (const GimpTempBuf *buf,
+ gint width,
+ gint height) G_GNUC_WARN_UNUSED_RESULT;
+
+gint gimp_temp_buf_get_width (const GimpTempBuf *buf);
+gint gimp_temp_buf_get_height (const GimpTempBuf *buf);
+
+const Babl * gimp_temp_buf_get_format (const GimpTempBuf *buf);
+void gimp_temp_buf_set_format (GimpTempBuf *buf,
+ const Babl *format);
+
+guchar * gimp_temp_buf_get_data (const GimpTempBuf *buf);
+gsize gimp_temp_buf_get_data_size (const GimpTempBuf *buf);
+
+guchar * gimp_temp_buf_data_clear (GimpTempBuf *buf);
+
+gpointer gimp_temp_buf_lock (const GimpTempBuf *buf,
+ const Babl *format,
+ GeglAccessMode access_mode) G_GNUC_WARN_UNUSED_RESULT;
+void gimp_temp_buf_unlock (const GimpTempBuf *buf,
+ gconstpointer data);
+
+gsize gimp_temp_buf_get_memsize (const GimpTempBuf *buf);
+
+GeglBuffer * gimp_temp_buf_create_buffer (const GimpTempBuf *temp_buf) G_GNUC_WARN_UNUSED_RESULT;
+GdkPixbuf * gimp_temp_buf_create_pixbuf (const GimpTempBuf *temp_buf) G_GNUC_WARN_UNUSED_RESULT;
+
+GimpTempBuf * gimp_gegl_buffer_get_temp_buf (GeglBuffer *buffer);
+
+
+/* stats */
+
+guint64 gimp_temp_buf_get_total_memsize (void);
+
+
+#endif /* __GIMP_TEMP_BUF_H__ */
diff --git a/app/core/gimptemplate.c b/app/core/gimptemplate.c
new file mode 100644
index 0000000..1459493
--- /dev/null
+++ b/app/core/gimptemplate.c
@@ -0,0 +1,595 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-1997 Spencer Kimball and Peter Mattis
+ *
+ * gimptemplate.c
+ * Copyright (C) 2003 Michael Natterer <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+/* This file contains the definition of the image template objects.
+ */
+
+#include "config.h"
+
+#include <cairo.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpbase/gimpbase.h"
+#include "libgimpcolor/gimpcolor.h"
+#include "libgimpconfig/gimpconfig.h"
+
+#include "core-types.h"
+
+#include "gegl/gimp-babl.h"
+
+#include "gimpimage.h"
+#include "gimpprojection.h"
+#include "gimptemplate.h"
+
+#include "gimp-intl.h"
+
+
+#define DEFAULT_RESOLUTION 300.0
+
+enum
+{
+ PROP_0,
+ PROP_WIDTH,
+ PROP_HEIGHT,
+ PROP_UNIT,
+ PROP_XRESOLUTION,
+ PROP_YRESOLUTION,
+ PROP_RESOLUTION_UNIT,
+ PROP_BASE_TYPE,
+ PROP_PRECISION,
+ PROP_COMPONENT_TYPE,
+ PROP_LINEAR,
+ PROP_COLOR_MANAGED,
+ PROP_COLOR_PROFILE,
+ PROP_FILL_TYPE,
+ PROP_COMMENT,
+ PROP_FILENAME
+};
+
+
+typedef struct _GimpTemplatePrivate GimpTemplatePrivate;
+
+struct _GimpTemplatePrivate
+{
+ gint width;
+ gint height;
+ GimpUnit unit;
+
+ gdouble xresolution;
+ gdouble yresolution;
+ GimpUnit resolution_unit;
+
+ GimpImageBaseType base_type;
+ GimpPrecision precision;
+
+ gboolean color_managed;
+ GFile *color_profile;
+
+ GimpFillType fill_type;
+
+ gchar *comment;
+ gchar *filename;
+
+ guint64 initial_size;
+};
+
+#define GET_PRIVATE(template) ((GimpTemplatePrivate *) gimp_template_get_instance_private ((GimpTemplate *) (template)))
+
+
+static void gimp_template_finalize (GObject *object);
+static void gimp_template_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_template_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+static void gimp_template_notify (GObject *object,
+ GParamSpec *pspec);
+
+
+G_DEFINE_TYPE_WITH_CODE (GimpTemplate, gimp_template, GIMP_TYPE_VIEWABLE,
+ G_ADD_PRIVATE (GimpTemplate)
+ G_IMPLEMENT_INTERFACE (GIMP_TYPE_CONFIG, NULL))
+
+#define parent_class gimp_template_parent_class
+
+
+static void
+gimp_template_class_init (GimpTemplateClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpViewableClass *viewable_class = GIMP_VIEWABLE_CLASS (klass);
+
+ object_class->finalize = gimp_template_finalize;
+
+ object_class->set_property = gimp_template_set_property;
+ object_class->get_property = gimp_template_get_property;
+ object_class->notify = gimp_template_notify;
+
+ viewable_class->default_icon_name = "gimp-template";
+ viewable_class->name_editable = TRUE;
+
+ GIMP_CONFIG_PROP_INT (object_class, PROP_WIDTH,
+ "width",
+ _("Width"),
+ NULL,
+ GIMP_MIN_IMAGE_SIZE, GIMP_MAX_IMAGE_SIZE,
+ GIMP_DEFAULT_IMAGE_WIDTH,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_INT (object_class, PROP_HEIGHT,
+ "height",
+ _("Height"),
+ NULL,
+ GIMP_MIN_IMAGE_SIZE, GIMP_MAX_IMAGE_SIZE,
+ GIMP_DEFAULT_IMAGE_HEIGHT,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_UNIT (object_class, PROP_UNIT,
+ "unit",
+ _("Unit"),
+ _("The unit used for coordinate display "
+ "when not in dot-for-dot mode."),
+ TRUE, FALSE, GIMP_UNIT_PIXEL,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_RESOLUTION (object_class, PROP_XRESOLUTION,
+ "xresolution",
+ _("Resolution X"),
+ _("The horizontal image resolution."),
+ DEFAULT_RESOLUTION,
+ GIMP_PARAM_STATIC_STRINGS |
+ GIMP_TEMPLATE_PARAM_COPY_FIRST);
+
+ GIMP_CONFIG_PROP_RESOLUTION (object_class, PROP_YRESOLUTION,
+ "yresolution",
+ _("Resolution X"),
+ _("The vertical image resolution."),
+ DEFAULT_RESOLUTION,
+ GIMP_PARAM_STATIC_STRINGS |
+ GIMP_TEMPLATE_PARAM_COPY_FIRST);
+
+ GIMP_CONFIG_PROP_UNIT (object_class, PROP_RESOLUTION_UNIT,
+ "resolution-unit",
+ _("Resolution unit"),
+ NULL,
+ FALSE, FALSE, GIMP_UNIT_INCH,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_ENUM (object_class, PROP_BASE_TYPE,
+ "image-type", /* serialized name */
+ _("Image type"),
+ NULL,
+ GIMP_TYPE_IMAGE_BASE_TYPE, GIMP_RGB,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_ENUM (object_class, PROP_PRECISION,
+ "precision",
+ _("Precision"),
+ NULL,
+ GIMP_TYPE_PRECISION, GIMP_PRECISION_U8_GAMMA,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_property (object_class, PROP_COMPONENT_TYPE,
+ g_param_spec_enum ("component-type",
+ _("Precision"),
+ NULL,
+ GIMP_TYPE_COMPONENT_TYPE,
+ GIMP_COMPONENT_TYPE_U8,
+ G_PARAM_READWRITE |
+ GIMP_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (object_class, PROP_LINEAR,
+ g_param_spec_boolean ("linear",
+ _("Gamma"),
+ NULL,
+ FALSE,
+ G_PARAM_READWRITE |
+ GIMP_PARAM_STATIC_STRINGS));
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_COLOR_MANAGED,
+ "color-managed",
+ _("Color managed"),
+ _("Whether the image is color managed. "
+ "Disabling color management is equivalent to "
+ "choosing a built-in sRGB profile. Better "
+ "leave color management enabled."),
+ TRUE,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_OBJECT (object_class, PROP_COLOR_PROFILE,
+ "color-profile",
+ _("Color profile"),
+ NULL,
+ G_TYPE_FILE,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_ENUM (object_class, PROP_FILL_TYPE,
+ "fill-type",
+ _("Fill type"),
+ NULL,
+ GIMP_TYPE_FILL_TYPE, GIMP_FILL_BACKGROUND,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_STRING (object_class, PROP_COMMENT,
+ "comment",
+ _("Comment"),
+ NULL,
+ NULL,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_STRING (object_class, PROP_FILENAME,
+ "filename",
+ _("Filename"),
+ NULL,
+ NULL,
+ GIMP_PARAM_STATIC_STRINGS);
+}
+
+static void
+gimp_template_init (GimpTemplate *template)
+{
+}
+
+static void
+gimp_template_finalize (GObject *object)
+{
+ GimpTemplatePrivate *private = GET_PRIVATE (object);
+
+ g_clear_object (&private->color_profile);
+ g_clear_pointer (&private->comment, g_free);
+ g_clear_pointer (&private->filename, g_free);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gimp_template_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpTemplatePrivate *private = GET_PRIVATE (object);
+
+ switch (property_id)
+ {
+ case PROP_WIDTH:
+ private->width = g_value_get_int (value);
+ break;
+ case PROP_HEIGHT:
+ private->height = g_value_get_int (value);
+ break;
+ case PROP_UNIT:
+ private->unit = g_value_get_int (value);
+ break;
+ case PROP_XRESOLUTION:
+ private->xresolution = g_value_get_double (value);
+ break;
+ case PROP_YRESOLUTION:
+ private->yresolution = g_value_get_double (value);
+ break;
+ case PROP_RESOLUTION_UNIT:
+ private->resolution_unit = g_value_get_int (value);
+ break;
+ case PROP_BASE_TYPE:
+ private->base_type = g_value_get_enum (value);
+ break;
+ case PROP_PRECISION:
+ private->precision = g_value_get_enum (value);
+ g_object_notify (object, "component-type");
+ g_object_notify (object, "linear");
+ break;
+ case PROP_COMPONENT_TYPE:
+ private->precision =
+ gimp_babl_precision (g_value_get_enum (value),
+ gimp_babl_linear (private->precision));
+ g_object_notify (object, "precision");
+ break;
+ case PROP_LINEAR:
+ private->precision =
+ gimp_babl_precision (gimp_babl_component_type (private->precision),
+ g_value_get_boolean (value));
+ g_object_notify (object, "precision");
+ break;
+ case PROP_COLOR_MANAGED:
+ private->color_managed = g_value_get_boolean (value);
+ break;
+ case PROP_COLOR_PROFILE:
+ if (private->color_profile)
+ g_object_unref (private->color_profile);
+ private->color_profile = g_value_dup_object (value);
+ break;
+ case PROP_FILL_TYPE:
+ private->fill_type = g_value_get_enum (value);
+ break;
+ case PROP_COMMENT:
+ if (private->comment)
+ g_free (private->comment);
+ private->comment = g_value_dup_string (value);
+ break;
+ case PROP_FILENAME:
+ if (private->filename)
+ g_free (private->filename);
+ private->filename = g_value_dup_string (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_template_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpTemplatePrivate *private = GET_PRIVATE (object);
+
+ switch (property_id)
+ {
+ case PROP_WIDTH:
+ g_value_set_int (value, private->width);
+ break;
+ case PROP_HEIGHT:
+ g_value_set_int (value, private->height);
+ break;
+ case PROP_UNIT:
+ g_value_set_int (value, private->unit);
+ break;
+ case PROP_XRESOLUTION:
+ g_value_set_double (value, private->xresolution);
+ break;
+ case PROP_YRESOLUTION:
+ g_value_set_double (value, private->yresolution);
+ break;
+ case PROP_RESOLUTION_UNIT:
+ g_value_set_int (value, private->resolution_unit);
+ break;
+ case PROP_BASE_TYPE:
+ g_value_set_enum (value, private->base_type);
+ break;
+ case PROP_PRECISION:
+ g_value_set_enum (value, private->precision);
+ break;
+ case PROP_COMPONENT_TYPE:
+ g_value_set_enum (value, gimp_babl_component_type (private->precision));
+ break;
+ case PROP_LINEAR:
+ g_value_set_boolean (value, gimp_babl_linear (private->precision));
+ break;
+ case PROP_COLOR_MANAGED:
+ g_value_set_boolean (value, private->color_managed);
+ break;
+ case PROP_COLOR_PROFILE:
+ g_value_set_object (value, private->color_profile);
+ break;
+ case PROP_FILL_TYPE:
+ g_value_set_enum (value, private->fill_type);
+ break;
+ case PROP_COMMENT:
+ g_value_set_string (value, private->comment);
+ break;
+ case PROP_FILENAME:
+ g_value_set_string (value, private->filename);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_template_notify (GObject *object,
+ GParamSpec *pspec)
+{
+ GimpTemplatePrivate *private = GET_PRIVATE (object);
+ const Babl *format;
+ gint bytes;
+
+ if (G_OBJECT_CLASS (parent_class)->notify)
+ G_OBJECT_CLASS (parent_class)->notify (object, pspec);
+
+ /* the initial layer */
+ format = gimp_babl_format (private->base_type,
+ private->precision,
+ private->fill_type == GIMP_FILL_TRANSPARENT);
+ bytes = babl_format_get_bytes_per_pixel (format);
+
+ /* the selection */
+ format = gimp_babl_mask_format (private->precision);
+ bytes += babl_format_get_bytes_per_pixel (format);
+
+ private->initial_size = ((guint64) bytes *
+ (guint64) private->width *
+ (guint64) private->height);
+
+ private->initial_size +=
+ gimp_projection_estimate_memsize (private->base_type,
+ gimp_babl_component_type (private->precision),
+ private->width, private->height);
+}
+
+
+/* public functions */
+
+GimpTemplate *
+gimp_template_new (const gchar *name)
+{
+ g_return_val_if_fail (name != NULL, NULL);
+
+ return g_object_new (GIMP_TYPE_TEMPLATE,
+ "name", name,
+ NULL);
+}
+
+void
+gimp_template_set_from_image (GimpTemplate *template,
+ GimpImage *image)
+{
+ gdouble xresolution;
+ gdouble yresolution;
+ GimpImageBaseType base_type;
+ const GimpParasite *parasite;
+ gchar *comment = NULL;
+
+ g_return_if_fail (GIMP_IS_TEMPLATE (template));
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+
+ gimp_image_get_resolution (image, &xresolution, &yresolution);
+
+ base_type = gimp_image_get_base_type (image);
+
+ if (base_type == GIMP_INDEXED)
+ base_type = GIMP_RGB;
+
+ parasite = gimp_image_parasite_find (image, "gimp-comment");
+ if (parasite)
+ comment = g_strndup (gimp_parasite_data (parasite),
+ gimp_parasite_data_size (parasite));
+
+ g_object_set (template,
+ "width", gimp_image_get_width (image),
+ "height", gimp_image_get_height (image),
+ "xresolution", xresolution,
+ "yresolution", yresolution,
+ "resolution-unit", gimp_image_get_unit (image),
+ "image-type", base_type,
+ "precision", gimp_image_get_precision (image),
+ "comment", comment,
+ NULL);
+
+ if (comment)
+ g_free (comment);
+}
+
+gint
+gimp_template_get_width (GimpTemplate *template)
+{
+ g_return_val_if_fail (GIMP_IS_TEMPLATE (template), 0);
+
+ return GET_PRIVATE (template)->width;
+}
+
+gint
+gimp_template_get_height (GimpTemplate *template)
+{
+ g_return_val_if_fail (GIMP_IS_TEMPLATE (template), 0);
+
+ return GET_PRIVATE (template)->height;
+}
+
+GimpUnit
+gimp_template_get_unit (GimpTemplate *template)
+{
+ g_return_val_if_fail (GIMP_IS_TEMPLATE (template), GIMP_UNIT_INCH);
+
+ return GET_PRIVATE (template)->unit;
+}
+
+gdouble
+gimp_template_get_resolution_x (GimpTemplate *template)
+{
+ g_return_val_if_fail (GIMP_IS_TEMPLATE (template), 1.0);
+
+ return GET_PRIVATE (template)->xresolution;
+}
+
+gdouble
+gimp_template_get_resolution_y (GimpTemplate *template)
+{
+ g_return_val_if_fail (GIMP_IS_TEMPLATE (template), 1.0);
+
+ return GET_PRIVATE (template)->yresolution;
+}
+
+GimpUnit
+gimp_template_get_resolution_unit (GimpTemplate *template)
+{
+ g_return_val_if_fail (GIMP_IS_TEMPLATE (template), GIMP_UNIT_INCH);
+
+ return GET_PRIVATE (template)->resolution_unit;
+}
+
+GimpImageBaseType
+gimp_template_get_base_type (GimpTemplate *template)
+{
+ g_return_val_if_fail (GIMP_IS_TEMPLATE (template), GIMP_RGB);
+
+ return GET_PRIVATE (template)->base_type;
+}
+
+GimpPrecision
+gimp_template_get_precision (GimpTemplate *template)
+{
+ g_return_val_if_fail (GIMP_IS_TEMPLATE (template), GIMP_PRECISION_U8_GAMMA);
+
+ return GET_PRIVATE (template)->precision;
+}
+
+gboolean
+gimp_template_get_color_managed (GimpTemplate *template)
+{
+ g_return_val_if_fail (GIMP_IS_TEMPLATE (template), FALSE);
+
+ return GET_PRIVATE (template)->color_managed;
+}
+
+GimpColorProfile *
+gimp_template_get_color_profile (GimpTemplate *template)
+{
+ GimpTemplatePrivate *private;
+
+ g_return_val_if_fail (GIMP_IS_TEMPLATE (template), FALSE);
+
+ private = GET_PRIVATE (template);
+
+ if (private->color_profile)
+ return gimp_color_profile_new_from_file (private->color_profile, NULL);
+
+ return NULL;
+}
+
+GimpFillType
+gimp_template_get_fill_type (GimpTemplate *template)
+{
+ g_return_val_if_fail (GIMP_IS_TEMPLATE (template), GIMP_FILL_BACKGROUND);
+
+ return GET_PRIVATE (template)->fill_type;
+}
+
+const gchar *
+gimp_template_get_comment (GimpTemplate *template)
+{
+ g_return_val_if_fail (GIMP_IS_TEMPLATE (template), NULL);
+
+ return GET_PRIVATE (template)->comment;
+}
+
+guint64
+gimp_template_get_initial_size (GimpTemplate *template)
+{
+ g_return_val_if_fail (GIMP_IS_TEMPLATE (template), 0);
+
+ return GET_PRIVATE (template)->initial_size;
+}
diff --git a/app/core/gimptemplate.h b/app/core/gimptemplate.h
new file mode 100644
index 0000000..671a247
--- /dev/null
+++ b/app/core/gimptemplate.h
@@ -0,0 +1,97 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-1999 Spencer Kimball and Peter Mattis
+ *
+ * gimptemplate.h
+ * Copyright (C) 2003 Michael Natterer <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_TEMPLATE_H__
+#define __GIMP_TEMPLATE_H__
+
+
+#include "gimpviewable.h"
+
+
+#define GIMP_TEMPLATE_PARAM_COPY_FIRST (1 << (8 + G_PARAM_USER_SHIFT))
+
+#ifdef GIMP_UNSTABLE
+/* Uncommon ratio, with at least one odd value, to encourage testing
+ * GIMP with unusual numbers.
+ * It also has to be a higher resolution, to push GIMP a little further
+ * in tests. */
+#define GIMP_DEFAULT_IMAGE_WIDTH 2001
+#define GIMP_DEFAULT_IMAGE_HEIGHT 1984
+#else
+/* 1366x768 is the most common screen resolution in 2016.
+ * 1920x1080 is the second most common.
+ * Since GIMP targets advanced graphics artists, let's go for the
+ * highest common dimension.
+ */
+#define GIMP_DEFAULT_IMAGE_WIDTH 1920
+#define GIMP_DEFAULT_IMAGE_HEIGHT 1080
+#endif
+
+
+#define GIMP_TYPE_TEMPLATE (gimp_template_get_type ())
+#define GIMP_TEMPLATE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_TEMPLATE, GimpTemplate))
+#define GIMP_TEMPLATE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_TEMPLATE, GimpTemplateClass))
+#define GIMP_IS_TEMPLATE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_TEMPLATE))
+#define GIMP_IS_TEMPLATE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_TEMPLATE))
+#define GIMP_TEMPLATE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_TEMPLATE, GimpTemplateClass))
+
+
+typedef struct _GimpTemplateClass GimpTemplateClass;
+
+struct _GimpTemplate
+{
+ GimpViewable parent_instance;
+};
+
+struct _GimpTemplateClass
+{
+ GimpViewableClass parent_instance;
+};
+
+
+GType gimp_template_get_type (void) G_GNUC_CONST;
+
+GimpTemplate * gimp_template_new (const gchar *name);
+
+void gimp_template_set_from_image (GimpTemplate *template,
+ GimpImage *image);
+
+gint gimp_template_get_width (GimpTemplate *template);
+gint gimp_template_get_height (GimpTemplate *template);
+GimpUnit gimp_template_get_unit (GimpTemplate *template);
+
+gdouble gimp_template_get_resolution_x (GimpTemplate *template);
+gdouble gimp_template_get_resolution_y (GimpTemplate *template);
+GimpUnit gimp_template_get_resolution_unit (GimpTemplate *template);
+
+GimpImageBaseType gimp_template_get_base_type (GimpTemplate *template);
+GimpPrecision gimp_template_get_precision (GimpTemplate *template);
+
+gboolean gimp_template_get_color_managed (GimpTemplate *template);
+GimpColorProfile * gimp_template_get_color_profile (GimpTemplate *template);
+
+GimpFillType gimp_template_get_fill_type (GimpTemplate *template);
+
+const gchar * gimp_template_get_comment (GimpTemplate *template);
+
+guint64 gimp_template_get_initial_size (GimpTemplate *template);
+
+
+#endif /* __GIMP_TEMPLATE__ */
diff --git a/app/core/gimptilehandlerprojectable.c b/app/core/gimptilehandlerprojectable.c
new file mode 100644
index 0000000..4b9e86b
--- /dev/null
+++ b/app/core/gimptilehandlerprojectable.c
@@ -0,0 +1,91 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <cairo.h>
+
+#include "core-types.h"
+
+#include "gimpprojectable.h"
+
+#include "gimptilehandlerprojectable.h"
+
+
+static void gimp_tile_handler_projectable_begin_validate (GimpTileHandlerValidate *validate);
+static void gimp_tile_handler_projectable_end_validate (GimpTileHandlerValidate *validate);
+
+
+G_DEFINE_TYPE (GimpTileHandlerProjectable, gimp_tile_handler_projectable,
+ GIMP_TYPE_TILE_HANDLER_VALIDATE)
+
+#define parent_class gimp_tile_handler_projectable_parent_class
+
+
+static void
+gimp_tile_handler_projectable_class_init (GimpTileHandlerProjectableClass *klass)
+{
+ GimpTileHandlerValidateClass *validate_class;
+
+ validate_class = GIMP_TILE_HANDLER_VALIDATE_CLASS (klass);
+
+ validate_class->begin_validate = gimp_tile_handler_projectable_begin_validate;
+ validate_class->end_validate = gimp_tile_handler_projectable_end_validate;
+}
+
+static void
+gimp_tile_handler_projectable_init (GimpTileHandlerProjectable *projectable)
+{
+}
+
+static void
+gimp_tile_handler_projectable_begin_validate (GimpTileHandlerValidate *validate)
+{
+ GimpTileHandlerProjectable *handler = GIMP_TILE_HANDLER_PROJECTABLE (validate);
+
+ GIMP_TILE_HANDLER_VALIDATE_CLASS (parent_class)->begin_validate (validate);
+
+ gimp_projectable_begin_render (handler->projectable);
+}
+
+static void
+gimp_tile_handler_projectable_end_validate (GimpTileHandlerValidate *validate)
+{
+ GimpTileHandlerProjectable *handler = GIMP_TILE_HANDLER_PROJECTABLE (validate);
+
+ gimp_projectable_end_render (handler->projectable);
+
+ GIMP_TILE_HANDLER_VALIDATE_CLASS (parent_class)->end_validate (validate);
+}
+
+GeglTileHandler *
+gimp_tile_handler_projectable_new (GimpProjectable *projectable)
+{
+ GimpTileHandlerProjectable *handler;
+
+ g_return_val_if_fail (GIMP_IS_PROJECTABLE (projectable), NULL);
+
+ handler = g_object_new (GIMP_TYPE_TILE_HANDLER_PROJECTABLE, NULL);
+
+ GIMP_TILE_HANDLER_VALIDATE (handler)->graph =
+ g_object_ref (gimp_projectable_get_graph (projectable));
+
+ handler->projectable = projectable;
+
+ return GEGL_TILE_HANDLER (handler);
+}
diff --git a/app/core/gimptilehandlerprojectable.h b/app/core/gimptilehandlerprojectable.h
new file mode 100644
index 0000000..57af4b6
--- /dev/null
+++ b/app/core/gimptilehandlerprojectable.h
@@ -0,0 +1,64 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_TILE_HANDLER_PROJECTABLE_H__
+#define __GIMP_TILE_HANDLER_PROJECTABLE_H__
+
+
+#include "gegl/gimptilehandlervalidate.h"
+
+
+/***
+ * GimpTileHandlerProjectable is a GeglTileHandler that renders a
+ * projectable, calling the projectable's begin_render() and end_render()
+ * before/after the actual rendering.
+ *
+ * Note that the tile handler does not own a reference to the projectable.
+ * It's the user's responsibility to manage the handler's and projectable's
+ * lifetime.
+ */
+
+#define GIMP_TYPE_TILE_HANDLER_PROJECTABLE (gimp_tile_handler_projectable_get_type ())
+#define GIMP_TILE_HANDLER_PROJECTABLE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_TILE_HANDLER_PROJECTABLE, GimpTileHandlerProjectable))
+#define GIMP_TILE_HANDLER_PROJECTABLE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_TILE_HANDLER_PROJECTABLE, GimpTileHandlerProjectableClass))
+#define GIMP_IS_TILE_HANDLER_PROJECTABLE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_TILE_HANDLER_PROJECTABLE))
+#define GIMP_IS_TILE_HANDLER_PROJECTABLE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_TILE_HANDLER_PROJECTABLE))
+#define GIMP_TILE_HANDLER_PROJECTABLE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_TILE_HANDLER_PROJECTABLE, GimpTileHandlerProjectableClass))
+
+
+typedef struct _GimpTileHandlerProjectable GimpTileHandlerProjectable;
+typedef struct _GimpTileHandlerProjectableClass GimpTileHandlerProjectableClass;
+
+struct _GimpTileHandlerProjectable
+{
+ GimpTileHandlerValidate parent_instance;
+
+ GimpProjectable *projectable;
+};
+
+struct _GimpTileHandlerProjectableClass
+{
+ GimpTileHandlerValidateClass parent_class;
+};
+
+
+GType gimp_tile_handler_projectable_get_type (void) G_GNUC_CONST;
+
+GeglTileHandler * gimp_tile_handler_projectable_new (GimpProjectable *projectable);
+
+
+#endif /* __GIMP_TILE_HANDLER_PROJECTABLE_H__ */
diff --git a/app/core/gimptoolgroup.c b/app/core/gimptoolgroup.c
new file mode 100644
index 0000000..aa8e3bc
--- /dev/null
+++ b/app/core/gimptoolgroup.c
@@ -0,0 +1,413 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimptoolgroup.c
+ * Copyright (C) 2020 Ell
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpbase/gimpbase.h"
+#include "libgimpconfig/gimpconfig.h"
+
+#include "core-types.h"
+
+#include "gimplist.h"
+#include "gimpmarshal.h"
+#include "gimptoolgroup.h"
+#include "gimptoolinfo.h"
+
+#include "gimp-intl.h"
+
+
+enum
+{
+ ACTIVE_TOOL_CHANGED,
+ LAST_SIGNAL
+};
+
+enum
+{
+ PROP_0,
+ PROP_ACTIVE_TOOL,
+ PROP_CHILDREN
+};
+
+
+struct _GimpToolGroupPrivate
+{
+ gchar *active_tool;
+ GimpContainer *children;
+};
+
+
+/* local function prototypes */
+
+
+static void gimp_tool_group_finalize (GObject *object);
+static void gimp_tool_group_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+static void gimp_tool_group_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+
+static gint64 gimp_tool_group_get_memsize (GimpObject *object,
+ gint64 *gui_size);
+
+static gchar * gimp_tool_group_get_description (GimpViewable *viewable,
+ gchar **tooltip);
+static GimpContainer * gimp_tool_group_get_children (GimpViewable *viewable);
+static void gimp_tool_group_set_expanded (GimpViewable *viewable,
+ gboolean expand);
+static gboolean gimp_tool_group_get_expanded (GimpViewable *viewable);
+
+static void gimp_tool_group_child_add (GimpContainer *container,
+ GimpToolInfo *tool_info,
+ GimpToolGroup *tool_group);
+static void gimp_tool_group_child_remove (GimpContainer *container,
+ GimpToolInfo *tool_info,
+ GimpToolGroup *tool_group);
+
+static void gimp_tool_group_shown_changed (GimpToolItem *tool_item);
+
+
+G_DEFINE_TYPE_WITH_PRIVATE (GimpToolGroup, gimp_tool_group, GIMP_TYPE_TOOL_ITEM)
+
+#define parent_class gimp_tool_group_parent_class
+
+static guint gimp_tool_group_signals[LAST_SIGNAL] = { 0 };
+
+
+/* private functions */
+
+static void
+gimp_tool_group_class_init (GimpToolGroupClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpObjectClass *gimp_object_class = GIMP_OBJECT_CLASS (klass);
+ GimpViewableClass *viewable_class = GIMP_VIEWABLE_CLASS (klass);
+ GimpToolItemClass *tool_item_class = GIMP_TOOL_ITEM_CLASS (klass);
+
+ gimp_tool_group_signals[ACTIVE_TOOL_CHANGED] =
+ g_signal_new ("active-tool-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpToolGroupClass, active_tool_changed),
+ NULL, NULL,
+ gimp_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ object_class->finalize = gimp_tool_group_finalize;
+ object_class->get_property = gimp_tool_group_get_property;
+ object_class->set_property = gimp_tool_group_set_property;
+
+ gimp_object_class->get_memsize = gimp_tool_group_get_memsize;
+
+ viewable_class->default_icon_name = "folder";
+ viewable_class->get_description = gimp_tool_group_get_description;
+ viewable_class->get_children = gimp_tool_group_get_children;
+ viewable_class->get_expanded = gimp_tool_group_get_expanded;
+ viewable_class->set_expanded = gimp_tool_group_set_expanded;
+
+ tool_item_class->shown_changed = gimp_tool_group_shown_changed;
+
+ GIMP_CONFIG_PROP_STRING (object_class, PROP_ACTIVE_TOOL,
+ "active-tool", NULL, NULL,
+ NULL,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_OBJECT (object_class, PROP_CHILDREN,
+ "children", NULL, NULL,
+ GIMP_TYPE_CONTAINER,
+ GIMP_PARAM_STATIC_STRINGS |
+ GIMP_CONFIG_PARAM_AGGREGATE);
+}
+
+static void
+gimp_tool_group_init (GimpToolGroup *tool_group)
+{
+ tool_group->priv = gimp_tool_group_get_instance_private (tool_group);
+
+ tool_group->priv->children = g_object_new (
+ GIMP_TYPE_LIST,
+ "children-type", GIMP_TYPE_TOOL_INFO,
+ "append", TRUE,
+ NULL);
+
+ g_signal_connect (tool_group->priv->children, "add",
+ G_CALLBACK (gimp_tool_group_child_add),
+ tool_group);
+ g_signal_connect (tool_group->priv->children, "remove",
+ G_CALLBACK (gimp_tool_group_child_remove),
+ tool_group);
+}
+
+static void
+gimp_tool_group_finalize (GObject *object)
+{
+ GimpToolGroup *tool_group = GIMP_TOOL_GROUP (object);
+
+ g_clear_pointer (&tool_group->priv->active_tool, g_free);
+
+ g_clear_object (&tool_group->priv->children);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gimp_tool_group_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpToolGroup *tool_group = GIMP_TOOL_GROUP (object);
+
+ switch (property_id)
+ {
+ case PROP_ACTIVE_TOOL:
+ g_value_set_string (value, tool_group->priv->active_tool);
+ break;
+
+ case PROP_CHILDREN:
+ g_value_set_object (value, tool_group->priv->children);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_tool_group_set_property_add_tool (GimpToolInfo *tool_info,
+ GimpToolGroup *tool_group)
+{
+ gimp_container_add (tool_group->priv->children, GIMP_OBJECT (tool_info));
+}
+
+static void
+gimp_tool_group_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpToolGroup *tool_group = GIMP_TOOL_GROUP (object);
+
+ switch (property_id)
+ {
+ case PROP_ACTIVE_TOOL:
+ g_free (tool_group->priv->active_tool);
+
+ tool_group->priv->active_tool = g_value_dup_string (value);
+ break;
+
+ case PROP_CHILDREN:
+ {
+ GimpContainer *container = g_value_get_object (value);
+
+ gimp_container_clear (tool_group->priv->children);
+
+ if (! container)
+ break;
+
+ gimp_container_foreach (container,
+ (GFunc) gimp_tool_group_set_property_add_tool,
+ tool_group);
+ }
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static gint64
+gimp_tool_group_get_memsize (GimpObject *object,
+ gint64 *gui_size)
+{
+ GimpToolGroup *tool_group = GIMP_TOOL_GROUP (object);
+ gint64 memsize = 0;
+
+ memsize += gimp_object_get_memsize (GIMP_OBJECT (tool_group->priv->children),
+ gui_size);
+
+ return memsize + GIMP_OBJECT_CLASS (parent_class)->get_memsize (object,
+ gui_size);
+}
+
+static gchar *
+gimp_tool_group_get_description (GimpViewable *viewable,
+ gchar **tooltip)
+{
+ /* Translators: this is a noun */
+ return g_strdup (C_("tool-item", "Group"));
+}
+
+static GimpContainer *
+gimp_tool_group_get_children (GimpViewable *viewable)
+{
+ GimpToolGroup *tool_group = GIMP_TOOL_GROUP (viewable);
+
+ return tool_group->priv->children;
+}
+
+static void
+gimp_tool_group_set_expanded (GimpViewable *viewable,
+ gboolean expand)
+{
+ if (! expand)
+ gimp_viewable_expanded_changed (viewable);
+}
+
+static gboolean
+gimp_tool_group_get_expanded (GimpViewable *viewable)
+{
+ return TRUE;
+}
+
+static void
+gimp_tool_group_child_add (GimpContainer *container,
+ GimpToolInfo *tool_info,
+ GimpToolGroup *tool_group)
+{
+ g_return_if_fail (
+ gimp_viewable_get_parent (GIMP_VIEWABLE (tool_info)) == NULL);
+
+ gimp_viewable_set_parent (GIMP_VIEWABLE (tool_info),
+ GIMP_VIEWABLE (tool_group));
+
+ if (! tool_group->priv->active_tool)
+ gimp_tool_group_set_active_tool_info (tool_group, tool_info);
+}
+
+static void
+gimp_tool_group_child_remove (GimpContainer *container,
+ GimpToolInfo *tool_info,
+ GimpToolGroup *tool_group)
+{
+ gimp_viewable_set_parent (GIMP_VIEWABLE (tool_info), NULL);
+
+ if (! g_strcmp0 (tool_group->priv->active_tool,
+ gimp_object_get_name (GIMP_OBJECT (tool_info))))
+ {
+ GimpToolInfo *active_tool_info = NULL;
+
+ if (! gimp_container_is_empty (tool_group->priv->children))
+ {
+ active_tool_info = GIMP_TOOL_INFO (
+ gimp_container_get_first_child (tool_group->priv->children));
+ }
+
+ gimp_tool_group_set_active_tool_info (tool_group, active_tool_info);
+ }
+}
+
+static void
+gimp_tool_group_shown_changed (GimpToolItem *tool_item)
+{
+ GimpToolGroup *tool_group = GIMP_TOOL_GROUP (tool_item);
+ GList *iter;
+
+ if (GIMP_TOOL_ITEM_CLASS (parent_class)->shown_changed)
+ GIMP_TOOL_ITEM_CLASS (parent_class)->shown_changed (tool_item);
+
+ for (iter = GIMP_LIST (tool_group->priv->children)->queue->head;
+ iter;
+ iter = g_list_next (iter))
+ {
+ GimpToolItem *tool_item = iter->data;
+
+ if (gimp_tool_item_get_visible (tool_item))
+ gimp_tool_item_shown_changed (tool_item);
+ }
+}
+
+
+/* public functions */
+
+GimpToolGroup *
+gimp_tool_group_new (void)
+{
+ GimpToolGroup *tool_group;
+
+ tool_group = g_object_new (GIMP_TYPE_TOOL_GROUP, NULL);
+
+ gimp_object_set_static_name (GIMP_OBJECT (tool_group), "tool group");
+
+ return tool_group;
+}
+
+void
+gimp_tool_group_set_active_tool (GimpToolGroup *tool_group,
+ const gchar *tool_name)
+{
+ g_return_if_fail (GIMP_IS_TOOL_GROUP (tool_group));
+
+ if (g_strcmp0 (tool_group->priv->active_tool, tool_name))
+ {
+ g_return_if_fail (tool_name == NULL ||
+ gimp_container_get_child_by_name (
+ tool_group->priv->children, tool_name) != NULL);
+
+ g_free (tool_group->priv->active_tool);
+
+ tool_group->priv->active_tool = g_strdup (tool_name);;
+
+ g_signal_emit (tool_group,
+ gimp_tool_group_signals[ACTIVE_TOOL_CHANGED], 0);
+
+ g_object_notify (G_OBJECT (tool_group), "active-tool");
+ }
+}
+
+const gchar *
+gimp_tool_group_get_active_tool (GimpToolGroup *tool_group)
+{
+ g_return_val_if_fail (GIMP_IS_TOOL_GROUP (tool_group), NULL);
+
+ return tool_group->priv->active_tool;
+}
+
+void
+gimp_tool_group_set_active_tool_info (GimpToolGroup *tool_group,
+ GimpToolInfo *tool_info)
+{
+ g_return_if_fail (GIMP_IS_TOOL_GROUP (tool_group));
+ g_return_if_fail (tool_info == NULL || GIMP_IS_TOOL_INFO (tool_info));
+
+ gimp_tool_group_set_active_tool (
+ tool_group,
+ tool_info ? gimp_object_get_name (GIMP_OBJECT (tool_info)) : NULL);
+}
+
+GimpToolInfo *
+gimp_tool_group_get_active_tool_info (GimpToolGroup *tool_group)
+{
+ g_return_val_if_fail (GIMP_IS_TOOL_GROUP (tool_group), NULL);
+
+ return GIMP_TOOL_INFO (
+ gimp_container_get_child_by_name (tool_group->priv->children,
+ tool_group->priv->active_tool));
+}
diff --git a/app/core/gimptoolgroup.h b/app/core/gimptoolgroup.h
new file mode 100644
index 0000000..ef4d979
--- /dev/null
+++ b/app/core/gimptoolgroup.h
@@ -0,0 +1,68 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimptoolgroup.h
+ * Copyright (C) 2020 Ell
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_TOOL_GROUP_H__
+#define __GIMP_TOOL_GROUP_H__
+
+
+#include "gimptoolitem.h"
+
+
+#define GIMP_TYPE_TOOL_GROUP (gimp_tool_group_get_type ())
+#define GIMP_TOOL_GROUP(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_TOOL_GROUP, GimpToolGroup))
+#define GIMP_TOOL_GROUP_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_TOOL_GROUP, GimpToolGroupClass))
+#define GIMP_IS_TOOL_GROUP(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_TOOL_GROUP))
+#define GIMP_IS_TOOL_GROUP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_TOOL_GROUP))
+#define GIMP_TOOL_GROUP_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_TOOL_GROUP, GimpToolGroupClass))
+
+
+typedef struct _GimpToolGroupPrivate GimpToolGroupPrivate;
+typedef struct _GimpToolGroupClass GimpToolGroupClass;
+
+struct _GimpToolGroup
+{
+ GimpToolItem parent_instance;
+
+ GimpToolGroupPrivate *priv;
+};
+
+struct _GimpToolGroupClass
+{
+ GimpToolItemClass parent_class;
+
+ /* signals */
+ void (* active_tool_changed) (GimpToolGroup *tool_group);
+};
+
+
+GType gimp_tool_group_get_type (void) G_GNUC_CONST;
+
+GimpToolGroup * gimp_tool_group_new (void);
+
+void gimp_tool_group_set_active_tool (GimpToolGroup *tool_group,
+ const gchar *tool_name);
+const gchar * gimp_tool_group_get_active_tool (GimpToolGroup *tool_group);
+
+void gimp_tool_group_set_active_tool_info (GimpToolGroup *tool_group,
+ GimpToolInfo *tool_info);
+GimpToolInfo * gimp_tool_group_get_active_tool_info (GimpToolGroup *tool_group);
+
+
+#endif /* __GIMP_TOOL_GROUP_H__ */
diff --git a/app/core/gimptoolinfo.c b/app/core/gimptoolinfo.c
new file mode 100644
index 0000000..34d93c3
--- /dev/null
+++ b/app/core/gimptoolinfo.c
@@ -0,0 +1,263 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpbase/gimpbase.h"
+
+#include "core-types.h"
+
+#include "gimp.h"
+#include "gimpdatafactory.h"
+#include "gimpfilteredcontainer.h"
+#include "gimppaintinfo.h"
+#include "gimptoolinfo.h"
+#include "gimptooloptions.h"
+#include "gimptoolpreset.h"
+
+
+static void gimp_tool_info_dispose (GObject *object);
+static void gimp_tool_info_finalize (GObject *object);
+
+static gchar * gimp_tool_info_get_description (GimpViewable *viewable,
+ gchar **tooltip);
+
+
+G_DEFINE_TYPE (GimpToolInfo, gimp_tool_info, GIMP_TYPE_TOOL_ITEM)
+
+#define parent_class gimp_tool_info_parent_class
+
+
+static void
+gimp_tool_info_class_init (GimpToolInfoClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpViewableClass *viewable_class = GIMP_VIEWABLE_CLASS (klass);
+
+ object_class->dispose = gimp_tool_info_dispose;
+ object_class->finalize = gimp_tool_info_finalize;
+
+ viewable_class->get_description = gimp_tool_info_get_description;
+}
+
+static void
+gimp_tool_info_init (GimpToolInfo *tool_info)
+{
+}
+
+static void
+gimp_tool_info_dispose (GObject *object)
+{
+ GimpToolInfo *tool_info = GIMP_TOOL_INFO (object);
+
+ if (tool_info->tool_options)
+ {
+ g_object_run_dispose (G_OBJECT (tool_info->tool_options));
+ g_clear_object (&tool_info->tool_options);
+ }
+
+ g_clear_object (&tool_info->presets);
+
+ G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+gimp_tool_info_finalize (GObject *object)
+{
+ GimpToolInfo *tool_info = GIMP_TOOL_INFO (object);
+
+ g_clear_pointer (&tool_info->label, g_free);
+ g_clear_pointer (&tool_info->tooltip, g_free);
+ g_clear_pointer (&tool_info->menu_label, g_free);
+ g_clear_pointer (&tool_info->menu_accel, g_free);
+ g_clear_pointer (&tool_info->help_domain, g_free);
+ g_clear_pointer (&tool_info->help_id, g_free);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static gchar *
+gimp_tool_info_get_description (GimpViewable *viewable,
+ gchar **tooltip)
+{
+ GimpToolInfo *tool_info = GIMP_TOOL_INFO (viewable);
+
+ if (tooltip)
+ *tooltip = g_strdup (tool_info->tooltip);
+
+ return g_strdup (tool_info->label);
+}
+
+static gboolean
+gimp_tool_info_filter_preset (GimpObject *object,
+ gpointer user_data)
+{
+ GimpToolPreset *preset = GIMP_TOOL_PRESET (object);
+ GimpToolInfo *tool_info = user_data;
+
+ return preset->tool_options->tool_info == tool_info;
+}
+
+GimpToolInfo *
+gimp_tool_info_new (Gimp *gimp,
+ GType tool_type,
+ GType tool_options_type,
+ GimpContextPropMask context_props,
+ const gchar *identifier,
+ const gchar *label,
+ const gchar *tooltip,
+ const gchar *menu_label,
+ const gchar *menu_accel,
+ const gchar *help_domain,
+ const gchar *help_id,
+ const gchar *paint_core_name,
+ const gchar *icon_name)
+{
+ GimpPaintInfo *paint_info;
+ GimpToolInfo *tool_info;
+
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
+ g_return_val_if_fail (identifier != NULL, NULL);
+ g_return_val_if_fail (label != NULL, NULL);
+ g_return_val_if_fail (tooltip != NULL, NULL);
+ g_return_val_if_fail (help_id != NULL, NULL);
+ g_return_val_if_fail (paint_core_name != NULL, NULL);
+ g_return_val_if_fail (icon_name != NULL, NULL);
+
+ paint_info = (GimpPaintInfo *)
+ gimp_container_get_child_by_name (gimp->paint_info_list, paint_core_name);
+
+ g_return_val_if_fail (GIMP_IS_PAINT_INFO (paint_info), NULL);
+
+ tool_info = g_object_new (GIMP_TYPE_TOOL_INFO,
+ "name", identifier,
+ "icon-name", icon_name,
+ NULL);
+
+ tool_info->gimp = gimp;
+ tool_info->tool_type = tool_type;
+ tool_info->tool_options_type = tool_options_type;
+ tool_info->context_props = context_props;
+
+ tool_info->label = g_strdup (label);
+ tool_info->tooltip = g_strdup (tooltip);
+
+ tool_info->menu_label = g_strdup (menu_label);
+ tool_info->menu_accel = g_strdup (menu_accel);
+
+ tool_info->help_domain = g_strdup (help_domain);
+ tool_info->help_id = g_strdup (help_id);
+
+ tool_info->paint_info = paint_info;
+
+ if (tool_info->tool_options_type == paint_info->paint_options_type)
+ {
+ tool_info->tool_options = g_object_ref (GIMP_TOOL_OPTIONS (paint_info->paint_options));
+ }
+ else
+ {
+ tool_info->tool_options = g_object_new (tool_info->tool_options_type,
+ "gimp", gimp,
+ "name", identifier,
+ NULL);
+ }
+
+ g_object_set (tool_info->tool_options,
+ "tool", tool_info,
+ "tool-info", tool_info, NULL);
+
+ gimp_tool_options_set_gui_mode (tool_info->tool_options, TRUE);
+
+ if (tool_info->tool_options_type != GIMP_TYPE_TOOL_OPTIONS)
+ {
+ GimpContainer *presets;
+
+ presets = gimp_data_factory_get_container (gimp->tool_preset_factory);
+
+ tool_info->presets =
+ gimp_filtered_container_new (presets,
+ gimp_tool_info_filter_preset,
+ tool_info);
+ }
+
+ return tool_info;
+}
+
+void
+gimp_tool_info_set_standard (Gimp *gimp,
+ GimpToolInfo *tool_info)
+{
+ g_return_if_fail (GIMP_IS_GIMP (gimp));
+ g_return_if_fail (! tool_info || GIMP_IS_TOOL_INFO (tool_info));
+
+ g_set_object (&gimp->standard_tool_info, tool_info);
+}
+
+GimpToolInfo *
+gimp_tool_info_get_standard (Gimp *gimp)
+{
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
+
+ return gimp->standard_tool_info;
+}
+
+gchar *
+gimp_tool_info_get_action_name (GimpToolInfo *tool_info)
+{
+ const gchar *identifier;
+ gchar *tmp;
+ gchar *name;
+
+ g_return_val_if_fail (GIMP_IS_TOOL_INFO (tool_info), NULL);
+
+ identifier = gimp_object_get_name (GIMP_OBJECT (tool_info));
+
+ g_return_val_if_fail (g_str_has_prefix (identifier, "gimp-"), NULL);
+ g_return_val_if_fail (g_str_has_suffix (identifier, "-tool"), NULL);
+
+ tmp = g_strndup (identifier + strlen ("gimp-"),
+ strlen (identifier) - strlen ("gimp--tool"));
+
+ name = g_strdup_printf ("tools-%s", tmp);
+
+ g_free (tmp);
+
+ return name;
+}
+
+GFile *
+gimp_tool_info_get_options_file (GimpToolInfo *tool_info,
+ const gchar *suffix)
+{
+ gchar *basename;
+ GFile *file;
+
+ g_return_val_if_fail (GIMP_IS_TOOL_INFO (tool_info), NULL);
+
+ /* also works for a NULL suffix */
+ basename = g_strconcat (gimp_object_get_name (tool_info), suffix, NULL);
+
+ file = gimp_directory_file ("tool-options", basename, NULL);
+ g_free (basename);
+
+ return file;
+}
diff --git a/app/core/gimptoolinfo.h b/app/core/gimptoolinfo.h
new file mode 100644
index 0000000..fc028b7
--- /dev/null
+++ b/app/core/gimptoolinfo.h
@@ -0,0 +1,95 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_TOOL_INFO_H__
+#define __GIMP_TOOL_INFO_H__
+
+
+#include "gimptoolitem.h"
+
+
+#define GIMP_TYPE_TOOL_INFO (gimp_tool_info_get_type ())
+#define GIMP_TOOL_INFO(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_TOOL_INFO, GimpToolInfo))
+#define GIMP_TOOL_INFO_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_TOOL_INFO, GimpToolInfoClass))
+#define GIMP_IS_TOOL_INFO(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_TOOL_INFO))
+#define GIMP_IS_TOOL_INFO_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_TOOL_INFO))
+#define GIMP_TOOL_INFO_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_TOOL_INFO, GimpToolInfoClass))
+
+
+typedef struct _GimpToolInfoClass GimpToolInfoClass;
+
+struct _GimpToolInfo
+{
+ GimpToolItem parent_instance;
+
+ Gimp *gimp;
+
+ GType tool_type;
+ GType tool_options_type;
+ GimpContextPropMask context_props;
+
+ gchar *label;
+ gchar *tooltip;
+
+ gchar *menu_label;
+ gchar *menu_accel;
+
+ gchar *help_domain;
+ gchar *help_id;
+
+ gboolean hidden;
+ gboolean experimental;
+
+ GimpToolOptions *tool_options;
+ GimpPaintInfo *paint_info;
+
+ GimpContainer *presets;
+};
+
+struct _GimpToolInfoClass
+{
+ GimpToolItemClass parent_class;
+};
+
+
+GType gimp_tool_info_get_type (void) G_GNUC_CONST;
+
+GimpToolInfo * gimp_tool_info_new (Gimp *gimp,
+ GType tool_type,
+ GType tool_options_type,
+ GimpContextPropMask context_props,
+ const gchar *identifier,
+ const gchar *label,
+ const gchar *tooltip,
+ const gchar *menu_label,
+ const gchar *menu_accel,
+ const gchar *help_domain,
+ const gchar *help_id,
+ const gchar *paint_core_name,
+ const gchar *icon_name);
+
+void gimp_tool_info_set_standard (Gimp *gimp,
+ GimpToolInfo *tool_info);
+GimpToolInfo * gimp_tool_info_get_standard (Gimp *gimp);
+
+gchar * gimp_tool_info_get_action_name (GimpToolInfo *tool_info);
+
+GFile * gimp_tool_info_get_options_file (GimpToolInfo *tool_info,
+ const gchar *suffix);
+
+
+#endif /* __GIMP_TOOL_INFO_H__ */
diff --git a/app/core/gimptoolitem.c b/app/core/gimptoolitem.c
new file mode 100644
index 0000000..984597b
--- /dev/null
+++ b/app/core/gimptoolitem.c
@@ -0,0 +1,227 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimptoolitem.c
+ * Copyright (C) 2020 Ell
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpbase/gimpbase.h"
+#include "libgimpconfig/gimpconfig.h"
+
+#include "core-types.h"
+
+#include "core/gimpmarshal.h"
+
+#include "gimptoolitem.h"
+
+
+enum
+{
+ VISIBLE_CHANGED,
+ SHOWN_CHANGED,
+ LAST_SIGNAL
+};
+
+enum
+{
+ PROP_0,
+ PROP_VISIBLE,
+ PROP_SHOWN
+};
+
+
+struct _GimpToolItemPrivate
+{
+ gboolean visible;
+};
+
+
+/* local function prototypes */
+
+static void gimp_tool_item_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+static void gimp_tool_item_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+
+
+G_DEFINE_TYPE_WITH_PRIVATE (GimpToolItem, gimp_tool_item, GIMP_TYPE_VIEWABLE)
+
+#define parent_class gimp_tool_item_parent_class
+
+static guint gimp_tool_item_signals[LAST_SIGNAL] = { 0 };
+
+
+/* private functions */
+
+static void
+gimp_tool_item_class_init (GimpToolItemClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ gimp_tool_item_signals[VISIBLE_CHANGED] =
+ g_signal_new ("visible-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpToolItemClass, visible_changed),
+ NULL, NULL,
+ gimp_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ gimp_tool_item_signals[SHOWN_CHANGED] =
+ g_signal_new ("shown-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpToolItemClass, shown_changed),
+ NULL, NULL,
+ gimp_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ object_class->get_property = gimp_tool_item_get_property;
+ object_class->set_property = gimp_tool_item_set_property;
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_VISIBLE,
+ "visible", NULL, NULL,
+ TRUE,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_property (object_class, PROP_SHOWN,
+ g_param_spec_boolean ("shown", NULL, NULL,
+ TRUE,
+ GIMP_PARAM_READABLE));
+}
+
+static void
+gimp_tool_item_init (GimpToolItem *tool_item)
+{
+ tool_item->priv = gimp_tool_item_get_instance_private (tool_item);
+}
+
+static void
+gimp_tool_item_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpToolItem *tool_item = GIMP_TOOL_ITEM (object);
+
+ switch (property_id)
+ {
+ case PROP_VISIBLE:
+ g_value_set_boolean (value, tool_item->priv->visible);
+ break;
+ case PROP_SHOWN:
+ g_value_set_boolean (value, gimp_tool_item_get_shown (tool_item));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_tool_item_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpToolItem *tool_item = GIMP_TOOL_ITEM (object);
+
+ switch (property_id)
+ {
+ case PROP_VISIBLE:
+ gimp_tool_item_set_visible (tool_item, g_value_get_boolean (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+
+/* public functions */
+
+void
+gimp_tool_item_set_visible (GimpToolItem *tool_item,
+ gboolean visible)
+{
+ g_return_if_fail (GIMP_IS_TOOL_ITEM (tool_item));
+
+ if (visible != tool_item->priv->visible)
+ {
+ gboolean old_shown;
+
+ g_object_freeze_notify (G_OBJECT (tool_item));
+
+ old_shown = gimp_tool_item_get_shown (tool_item);
+
+ tool_item->priv->visible = visible;
+
+ g_signal_emit (tool_item, gimp_tool_item_signals[VISIBLE_CHANGED], 0);
+
+ if (gimp_tool_item_get_shown (tool_item) != old_shown)
+ gimp_tool_item_shown_changed (tool_item);
+
+ g_object_notify (G_OBJECT (tool_item), "visible");
+
+ g_object_thaw_notify (G_OBJECT (tool_item));
+ }
+}
+
+gboolean
+gimp_tool_item_get_visible (GimpToolItem *tool_item)
+{
+ g_return_val_if_fail (GIMP_IS_TOOL_ITEM (tool_item), FALSE);
+
+ return tool_item->priv->visible;
+}
+
+gboolean
+gimp_tool_item_get_shown (GimpToolItem *tool_item)
+{
+ GimpToolItem *parent;
+
+ g_return_val_if_fail (GIMP_IS_TOOL_ITEM (tool_item), FALSE);
+
+ parent = GIMP_TOOL_ITEM (
+ gimp_viewable_get_parent (GIMP_VIEWABLE (tool_item)));
+
+ return tool_item->priv->visible &&
+ (! parent || gimp_tool_item_get_shown (parent));
+}
+
+
+/* protected functions */
+
+void
+gimp_tool_item_shown_changed (GimpToolItem *tool_item)
+{
+ g_signal_emit (tool_item, gimp_tool_item_signals[SHOWN_CHANGED], 0);
+
+ g_object_notify (G_OBJECT (tool_item), "shown");
+}
diff --git a/app/core/gimptoolitem.h b/app/core/gimptoolitem.h
new file mode 100644
index 0000000..3f19f49
--- /dev/null
+++ b/app/core/gimptoolitem.h
@@ -0,0 +1,70 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimptoolitem.h
+ * Copyright (C) 2020 Ell
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_TOOL_ITEM_H__
+#define __GIMP_TOOL_ITEM_H__
+
+
+#include "gimpviewable.h"
+
+
+#define GIMP_TYPE_TOOL_ITEM (gimp_tool_item_get_type ())
+#define GIMP_TOOL_ITEM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_TOOL_ITEM, GimpToolItem))
+#define GIMP_TOOL_ITEM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_TOOL_ITEM, GimpToolItemClass))
+#define GIMP_IS_TOOL_ITEM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_TOOL_ITEM))
+#define GIMP_IS_TOOL_ITEM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_TOOL_ITEM))
+#define GIMP_TOOL_ITEM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_TOOL_ITEM, GimpToolItemClass))
+
+
+typedef struct _GimpToolItemPrivate GimpToolItemPrivate;
+typedef struct _GimpToolItemClass GimpToolItemClass;
+
+struct _GimpToolItem
+{
+ GimpViewable parent_instance;
+
+ GimpToolItemPrivate *priv;
+};
+
+struct _GimpToolItemClass
+{
+ GimpViewableClass parent_class;
+
+ /* signals */
+ void (* visible_changed) (GimpToolItem *tool_item);
+ void (* shown_changed) (GimpToolItem *tool_item);
+};
+
+
+GType gimp_tool_item_get_type (void) G_GNUC_CONST;
+
+void gimp_tool_item_set_visible (GimpToolItem *tool_item,
+ gboolean visible);
+gboolean gimp_tool_item_get_visible (GimpToolItem *tool_item);
+
+gboolean gimp_tool_item_get_shown (GimpToolItem *tool_item);
+
+
+/* protected */
+
+void gimp_tool_item_shown_changed (GimpToolItem *tool_item);
+
+
+#endif /* __GIMP_TOOL_ITEM_H__ */
diff --git a/app/core/gimptooloptions.c b/app/core/gimptooloptions.c
new file mode 100644
index 0000000..8a19d6e
--- /dev/null
+++ b/app/core/gimptooloptions.c
@@ -0,0 +1,378 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-1999 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpbase/gimpbase.h"
+#include "libgimpconfig/gimpconfig.h"
+
+#include "core-types.h"
+
+#include "gimp.h"
+#include "gimperror.h"
+#include "gimptoolinfo.h"
+#include "gimptooloptions.h"
+
+#include "gimp-intl.h"
+
+
+enum
+{
+ PROP_0,
+ PROP_TOOL,
+ PROP_TOOL_INFO
+};
+
+
+static void gimp_tool_options_config_iface_init (GimpConfigInterface *iface);
+
+static void gimp_tool_options_dispose (GObject *object);
+static void gimp_tool_options_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_tool_options_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static void gimp_tool_options_config_reset (GimpConfig *config);
+
+static void gimp_tool_options_tool_notify (GimpToolOptions *options,
+ GParamSpec *pspec);
+
+
+G_DEFINE_TYPE_WITH_CODE (GimpToolOptions, gimp_tool_options, GIMP_TYPE_CONTEXT,
+ G_IMPLEMENT_INTERFACE (GIMP_TYPE_CONFIG,
+ gimp_tool_options_config_iface_init))
+
+#define parent_class gimp_tool_options_parent_class
+
+
+static void
+gimp_tool_options_class_init (GimpToolOptionsClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->dispose = gimp_tool_options_dispose;
+ object_class->set_property = gimp_tool_options_set_property;
+ object_class->get_property = gimp_tool_options_get_property;
+
+ g_object_class_override_property (object_class, PROP_TOOL, "tool");
+
+ g_object_class_install_property (object_class, PROP_TOOL_INFO,
+ g_param_spec_object ("tool-info",
+ NULL, NULL,
+ GIMP_TYPE_TOOL_INFO,
+ GIMP_PARAM_READWRITE));
+
+}
+
+static void
+gimp_tool_options_init (GimpToolOptions *options)
+{
+ options->tool_info = NULL;
+
+ g_signal_connect (options, "notify::tool",
+ G_CALLBACK (gimp_tool_options_tool_notify),
+ NULL);
+}
+
+static void
+gimp_tool_options_config_iface_init (GimpConfigInterface *iface)
+{
+ iface->reset = gimp_tool_options_config_reset;
+}
+
+static void
+gimp_tool_options_dispose (GObject *object)
+{
+ GimpToolOptions *options = GIMP_TOOL_OPTIONS (object);
+
+ g_clear_object (&options->tool_info);
+
+ G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+/* This is such a horrible hack, but necessary because we
+ * a) load an option's tool-info from disk in many cases
+ * b) screwed up in the past and saved the wrong tool-info in some cases
+ */
+static GimpToolInfo *
+gimp_tool_options_check_tool_info (GimpToolOptions *options,
+ GimpToolInfo *tool_info,
+ gboolean warn)
+{
+ if (tool_info && G_OBJECT_TYPE (options) == tool_info->tool_options_type)
+ {
+ return tool_info;
+ }
+ else
+ {
+ GList *list;
+
+ for (list = gimp_get_tool_info_iter (GIMP_CONTEXT (options)->gimp);
+ list;
+ list = g_list_next (list))
+ {
+ GimpToolInfo *new_info = list->data;
+
+ if (G_OBJECT_TYPE (options) == new_info->tool_options_type)
+ {
+ if (warn)
+ g_printerr ("%s: correcting bogus deserialized tool "
+ "type '%s' with right type '%s'\n",
+ g_type_name (G_OBJECT_TYPE (options)),
+ tool_info ? gimp_object_get_name (tool_info) : "NULL",
+ gimp_object_get_name (new_info));
+
+ return new_info;
+ }
+ }
+
+ g_return_val_if_reached (NULL);
+ }
+}
+
+static void
+gimp_tool_options_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpToolOptions *options = GIMP_TOOL_OPTIONS (object);
+
+ switch (property_id)
+ {
+ case PROP_TOOL:
+ {
+ GimpToolInfo *tool_info = g_value_get_object (value);
+ GimpToolInfo *context_tool;
+
+ context_tool = gimp_context_get_tool (GIMP_CONTEXT (options));
+
+ g_return_if_fail (context_tool == NULL ||
+ context_tool == tool_info);
+
+ tool_info = gimp_tool_options_check_tool_info (options, tool_info, TRUE);
+
+ if (! context_tool)
+ gimp_context_set_tool (GIMP_CONTEXT (options), tool_info);
+ }
+ break;
+
+ case PROP_TOOL_INFO:
+ {
+ GimpToolInfo *tool_info = g_value_get_object (value);
+
+ g_return_if_fail (options->tool_info == NULL ||
+ options->tool_info == tool_info);
+
+ tool_info = gimp_tool_options_check_tool_info (options, tool_info, TRUE);
+
+ if (! options->tool_info)
+ {
+ options->tool_info = g_object_ref (tool_info);
+
+ gimp_context_set_serialize_properties (GIMP_CONTEXT (options),
+ tool_info->context_props);
+ }
+ }
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_tool_options_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpToolOptions *options = GIMP_TOOL_OPTIONS (object);
+
+ switch (property_id)
+ {
+ case PROP_TOOL:
+ g_value_set_object (value, gimp_context_get_tool (GIMP_CONTEXT (options)));
+ break;
+
+ case PROP_TOOL_INFO:
+ g_value_set_object (value, options->tool_info);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_tool_options_config_reset (GimpConfig *config)
+{
+ gchar *name = g_strdup (gimp_object_get_name (config));
+
+ gimp_config_reset_properties (G_OBJECT (config));
+
+ gimp_object_take_name (GIMP_OBJECT (config), name);
+}
+
+static void
+gimp_tool_options_tool_notify (GimpToolOptions *options,
+ GParamSpec *pspec)
+{
+ GimpToolInfo *tool_info = gimp_context_get_tool (GIMP_CONTEXT (options));
+ GimpToolInfo *new_info;
+
+ new_info = gimp_tool_options_check_tool_info (options, tool_info, FALSE);
+
+ if (tool_info && new_info != tool_info)
+ g_warning ("%s: 'tool' property on %s was set to bogus value "
+ "'%s', it MUST be '%s'.",
+ G_STRFUNC,
+ g_type_name (G_OBJECT_TYPE (options)),
+ gimp_object_get_name (tool_info),
+ gimp_object_get_name (new_info));
+}
+
+
+/* public functions */
+
+void
+gimp_tool_options_set_gui_mode (GimpToolOptions *tool_options,
+ gboolean gui_mode)
+{
+ g_return_if_fail (GIMP_IS_TOOL_OPTIONS (tool_options));
+
+ tool_options->gui_mode = gui_mode ? TRUE : FALSE;
+}
+
+gboolean
+gimp_tool_options_get_gui_mode (GimpToolOptions *tool_options)
+{
+ g_return_val_if_fail (GIMP_IS_TOOL_OPTIONS (tool_options), FALSE);
+
+ return tool_options->gui_mode;
+}
+
+gboolean
+gimp_tool_options_serialize (GimpToolOptions *tool_options,
+ GError **error)
+{
+ GFile *file;
+ gchar *header;
+ gchar *footer;
+ gboolean retval;
+
+ g_return_val_if_fail (GIMP_IS_TOOL_OPTIONS (tool_options), FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ file = gimp_tool_info_get_options_file (tool_options->tool_info, NULL);
+
+ if (tool_options->tool_info->gimp->be_verbose)
+ g_print ("Writing '%s'\n", gimp_file_get_utf8_name (file));
+
+ header = g_strdup_printf ("GIMP %s options",
+ gimp_object_get_name (tool_options->tool_info));
+ footer = g_strdup_printf ("end of %s options",
+ gimp_object_get_name (tool_options->tool_info));
+
+ retval = gimp_config_serialize_to_gfile (GIMP_CONFIG (tool_options),
+ file,
+ header, footer,
+ NULL,
+ error);
+
+ g_free (header);
+ g_free (footer);
+
+ g_object_unref (file);
+
+ return retval;
+}
+
+gboolean
+gimp_tool_options_deserialize (GimpToolOptions *tool_options,
+ GError **error)
+{
+ GFile *file;
+ gboolean retval;
+
+ g_return_val_if_fail (GIMP_IS_TOOL_OPTIONS (tool_options), FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ file = gimp_tool_info_get_options_file (tool_options->tool_info, NULL);
+
+ if (tool_options->tool_info->gimp->be_verbose)
+ g_print ("Parsing '%s'\n", gimp_file_get_utf8_name (file));
+
+ retval = gimp_config_deserialize_gfile (GIMP_CONFIG (tool_options),
+ file,
+ NULL,
+ error);
+
+ g_object_unref (file);
+
+ return retval;
+}
+
+gboolean
+gimp_tool_options_delete (GimpToolOptions *tool_options,
+ GError **error)
+{
+ GFile *file;
+ GError *my_error = NULL;
+ gboolean success = TRUE;
+
+ g_return_val_if_fail (GIMP_IS_TOOL_OPTIONS (tool_options), FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ file = gimp_tool_info_get_options_file (tool_options->tool_info, NULL);
+
+ if (tool_options->tool_info->gimp->be_verbose)
+ g_print ("Deleting '%s'\n", gimp_file_get_utf8_name (file));
+
+ if (! g_file_delete (file, NULL, &my_error) &&
+ my_error->code != G_IO_ERROR_NOT_FOUND)
+ {
+ success = FALSE;
+
+ g_set_error (error, GIMP_ERROR, GIMP_FAILED,
+ _("Deleting \"%s\" failed: %s"),
+ gimp_file_get_utf8_name (file), my_error->message);
+ }
+
+ g_clear_error (&my_error);
+ g_object_unref (file);
+
+ return success;
+}
+
+void
+gimp_tool_options_create_folder (void)
+{
+ GFile *file = gimp_directory_file ("tool-options", NULL);
+
+ g_file_make_directory (file, NULL, NULL);
+ g_object_unref (file);
+}
diff --git a/app/core/gimptooloptions.h b/app/core/gimptooloptions.h
new file mode 100644
index 0000000..f59aa52
--- /dev/null
+++ b/app/core/gimptooloptions.h
@@ -0,0 +1,73 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-1999 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_TOOL_OPTIONS_H__
+#define __GIMP_TOOL_OPTIONS_H__
+
+
+#include "gimpcontext.h"
+
+
+#define GIMP_TYPE_TOOL_OPTIONS (gimp_tool_options_get_type ())
+#define GIMP_TOOL_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_TOOL_OPTIONS, GimpToolOptions))
+#define GIMP_TOOL_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_TOOL_OPTIONS, GimpToolOptionsClass))
+#define GIMP_IS_TOOL_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_TOOL_OPTIONS))
+#define GIMP_IS_TOOL_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_TOOL_OPTIONS))
+#define GIMP_TOOL_OPTIONS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_TOOL_OPTIONS, GimpToolOptionsClass))
+
+
+typedef struct _GimpToolOptionsClass GimpToolOptionsClass;
+
+struct _GimpToolOptions
+{
+ GimpContext parent_instance;
+
+ GimpToolInfo *tool_info;
+
+ /* if TRUE this instance is the main tool options object used for
+ * the GUI, this is not exactly clean, but there are some things
+ * (like linking brush properties to the active brush, or properly
+ * maintaining global brush, pattern etc.) that can only be done
+ * right in the object, and not by signal connections from the GUI,
+ * or upon switching tools, all of which was much more horrible.
+ */
+ gboolean gui_mode;
+};
+
+struct _GimpToolOptionsClass
+{
+ GimpContextClass parent_class;
+};
+
+
+GType gimp_tool_options_get_type (void) G_GNUC_CONST;
+
+void gimp_tool_options_set_gui_mode (GimpToolOptions *tool_options,
+ gboolean gui_mode);
+gboolean gimp_tool_options_get_gui_mode (GimpToolOptions *tool_options);
+
+gboolean gimp_tool_options_serialize (GimpToolOptions *tool_options,
+ GError **error);
+gboolean gimp_tool_options_deserialize (GimpToolOptions *tool_options,
+ GError **error);
+
+gboolean gimp_tool_options_delete (GimpToolOptions *tool_options,
+ GError **error);
+void gimp_tool_options_create_folder (void);
+
+
+#endif /* __GIMP_TOOL_OPTIONS_H__ */
diff --git a/app/core/gimptoolpreset-load.c b/app/core/gimptoolpreset-load.c
new file mode 100644
index 0000000..386ac09
--- /dev/null
+++ b/app/core/gimptoolpreset-load.c
@@ -0,0 +1,71 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpbase/gimpbase.h"
+#include "libgimpconfig/gimpconfig.h"
+
+#include "core-types.h"
+
+#include "gimpcontext.h"
+#include "gimptoolpreset.h"
+#include "gimptoolpreset-load.h"
+
+#include "gimp-intl.h"
+
+
+GList *
+gimp_tool_preset_load (GimpContext *context,
+ GFile *file,
+ GInputStream *input,
+ GError **error)
+{
+ GimpToolPreset *tool_preset;
+
+ g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL);
+ g_return_val_if_fail (G_IS_FILE (file), NULL);
+ g_return_val_if_fail (G_IS_INPUT_STREAM (input), NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ tool_preset = g_object_new (GIMP_TYPE_TOOL_PRESET,
+ "gimp", context->gimp,
+ NULL);
+
+ if (gimp_config_deserialize_stream (GIMP_CONFIG (tool_preset),
+ input,
+ NULL, error))
+ {
+ if (GIMP_IS_CONTEXT (tool_preset->tool_options))
+ {
+ return g_list_prepend (NULL, tool_preset);
+ }
+ else
+ {
+ g_set_error_literal (error,
+ GIMP_CONFIG_ERROR, GIMP_CONFIG_ERROR_PARSE,
+ _("Tool preset file is corrupt."));
+ }
+ }
+
+ g_object_unref (tool_preset);
+
+ return NULL;
+}
diff --git a/app/core/gimptoolpreset-load.h b/app/core/gimptoolpreset-load.h
new file mode 100644
index 0000000..9a016df
--- /dev/null
+++ b/app/core/gimptoolpreset-load.h
@@ -0,0 +1,31 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_TOOL_PRESET_LOAD_H__
+#define __GIMP_TOOL_PRESET_LOAD_H__
+
+
+#define GIMP_TOOL_PRESET_FILE_EXTENSION ".gtp"
+
+
+GList * gimp_tool_preset_load (GimpContext *context,
+ GFile *file,
+ GInputStream *input,
+ GError **error);
+
+
+#endif /* __GIMP_TOOL_PRESET_LOAD_H__ */
diff --git a/app/core/gimptoolpreset-save.c b/app/core/gimptoolpreset-save.c
new file mode 100644
index 0000000..0afd024
--- /dev/null
+++ b/app/core/gimptoolpreset-save.c
@@ -0,0 +1,44 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpconfig/gimpconfig.h"
+
+#include "core-types.h"
+
+#include "gimptoolpreset.h"
+#include "gimptoolpreset-save.h"
+
+
+gboolean
+gimp_tool_preset_save (GimpData *data,
+ GOutputStream *output,
+ GError **error)
+{
+ g_return_val_if_fail (GIMP_IS_TOOL_PRESET (data), FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ return gimp_config_serialize_to_stream (GIMP_CONFIG (data),
+ output,
+ "GIMP tool preset file",
+ "end of GIMP tool preset file",
+ NULL, error);
+}
diff --git a/app/core/gimptoolpreset-save.h b/app/core/gimptoolpreset-save.h
new file mode 100644
index 0000000..d78ef64
--- /dev/null
+++ b/app/core/gimptoolpreset-save.h
@@ -0,0 +1,28 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_TOOL_PRESET_SAVE_H__
+#define __GIMP_TOOL_PRESET_SAVE_H__
+
+
+/* don't call this function directly, use gimp_data_save() instead */
+gboolean gimp_tool_preset_save (GimpData *data,
+ GOutputStream *output,
+ GError **error);
+
+
+#endif /* __GIMP_TOOL_PRESET_SAVE_H__ */
diff --git a/app/core/gimptoolpreset.c b/app/core/gimptoolpreset.c
new file mode 100644
index 0000000..6ab2eb7
--- /dev/null
+++ b/app/core/gimptoolpreset.c
@@ -0,0 +1,705 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-1999 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpmath/gimpmath.h"
+#include "libgimpconfig/gimpconfig.h"
+
+#include "core-types.h"
+
+#include "gimp.h"
+#include "gimptoolinfo.h"
+#include "gimptooloptions.h"
+#include "gimptoolpreset.h"
+#include "gimptoolpreset-load.h"
+#include "gimptoolpreset-save.h"
+
+#include "gimp-intl.h"
+
+
+/* The defaults are "everything except color", which is problematic
+ * with gradients, which is why we special case the gradient tool in
+ * gimp_tool_preset_set_options().
+ */
+#define DEFAULT_USE_FG_BG FALSE
+#define DEFAULT_USE_OPACITY_PAINT_MODE TRUE
+#define DEFAULT_USE_BRUSH TRUE
+#define DEFAULT_USE_DYNAMICS TRUE
+#define DEFAULT_USE_MYBRUSH TRUE
+#define DEFAULT_USE_GRADIENT FALSE
+#define DEFAULT_USE_PATTERN TRUE
+#define DEFAULT_USE_PALETTE FALSE
+#define DEFAULT_USE_FONT TRUE
+
+enum
+{
+ PROP_0,
+ PROP_NAME,
+ PROP_GIMP,
+ PROP_TOOL_OPTIONS,
+ PROP_USE_FG_BG,
+ PROP_USE_OPACITY_PAINT_MODE,
+ PROP_USE_BRUSH,
+ PROP_USE_DYNAMICS,
+ PROP_USE_MYBRUSH,
+ PROP_USE_GRADIENT,
+ PROP_USE_PATTERN,
+ PROP_USE_PALETTE,
+ PROP_USE_FONT
+};
+
+
+static void gimp_tool_preset_config_iface_init (GimpConfigInterface *iface);
+
+static void gimp_tool_preset_constructed (GObject *object);
+static void gimp_tool_preset_finalize (GObject *object);
+static void gimp_tool_preset_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_tool_preset_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+static void
+ gimp_tool_preset_dispatch_properties_changed (GObject *object,
+ guint n_pspecs,
+ GParamSpec **pspecs);
+
+static const gchar * gimp_tool_preset_get_extension (GimpData *data);
+
+static gboolean gimp_tool_preset_deserialize_property (GimpConfig *config,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec,
+ GScanner *scanner,
+ GTokenType *expected);
+
+static void gimp_tool_preset_set_options (GimpToolPreset *preset,
+ GimpToolOptions *options);
+static void gimp_tool_preset_options_notify (GObject *tool_options,
+ const GParamSpec *pspec,
+ GimpToolPreset *preset);
+static void gimp_tool_preset_options_prop_name_changed (GimpContext *tool_options,
+ GimpContextPropType prop,
+ GimpToolPreset *preset);
+
+
+G_DEFINE_TYPE_WITH_CODE (GimpToolPreset, gimp_tool_preset, GIMP_TYPE_DATA,
+ G_IMPLEMENT_INTERFACE (GIMP_TYPE_CONFIG,
+ gimp_tool_preset_config_iface_init))
+
+#define parent_class gimp_tool_preset_parent_class
+
+
+static void
+gimp_tool_preset_class_init (GimpToolPresetClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpDataClass *data_class = GIMP_DATA_CLASS (klass);
+
+ object_class->constructed = gimp_tool_preset_constructed;
+ object_class->finalize = gimp_tool_preset_finalize;
+ object_class->set_property = gimp_tool_preset_set_property;
+ object_class->get_property = gimp_tool_preset_get_property;
+ object_class->dispatch_properties_changed = gimp_tool_preset_dispatch_properties_changed;
+
+ data_class->save = gimp_tool_preset_save;
+ data_class->get_extension = gimp_tool_preset_get_extension;
+
+ GIMP_CONFIG_PROP_STRING (object_class, PROP_NAME,
+ "name",
+ NULL, NULL,
+ "Unnamed",
+ GIMP_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_property (object_class, PROP_GIMP,
+ g_param_spec_object ("gimp",
+ NULL, NULL,
+ GIMP_TYPE_GIMP,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+
+ GIMP_CONFIG_PROP_OBJECT (object_class, PROP_TOOL_OPTIONS,
+ "tool-options",
+ NULL, NULL,
+ GIMP_TYPE_TOOL_OPTIONS,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_USE_FG_BG,
+ "use-fg-bg",
+ _("Apply stored FG/BG"),
+ NULL,
+ DEFAULT_USE_FG_BG,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_USE_OPACITY_PAINT_MODE,
+ "use-opacity-paint-mode",
+ _("Apply stored opacity/paint mode"),
+ NULL,
+ DEFAULT_USE_OPACITY_PAINT_MODE,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_USE_BRUSH,
+ "use-brush",
+ _("Apply stored brush"),
+ NULL,
+ DEFAULT_USE_BRUSH,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_USE_DYNAMICS,
+ "use-dynamics",
+ _("Apply stored dynamics"),
+ NULL,
+ DEFAULT_USE_DYNAMICS,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_USE_MYBRUSH,
+ "use-mypaint-brush",
+ _("Apply stored MyPaint brush"),
+ NULL,
+ DEFAULT_USE_MYBRUSH,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_USE_PATTERN,
+ "use-pattern",
+ _("Apply stored pattern"),
+ NULL,
+ DEFAULT_USE_PATTERN,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_USE_PALETTE,
+ "use-palette",
+ _("Apply stored palette"),
+ NULL,
+ DEFAULT_USE_PALETTE,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_USE_GRADIENT,
+ "use-gradient",
+ _("Apply stored gradient"),
+ NULL,
+ DEFAULT_USE_GRADIENT,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_USE_FONT,
+ "use-font",
+ _("Apply stored font"),
+ NULL,
+ DEFAULT_USE_FONT,
+ GIMP_PARAM_STATIC_STRINGS);
+}
+
+static void
+gimp_tool_preset_config_iface_init (GimpConfigInterface *iface)
+{
+ iface->deserialize_property = gimp_tool_preset_deserialize_property;
+}
+
+static void
+gimp_tool_preset_init (GimpToolPreset *tool_preset)
+{
+}
+
+static void
+gimp_tool_preset_constructed (GObject *object)
+{
+ GimpToolPreset *preset = GIMP_TOOL_PRESET (object);
+
+ G_OBJECT_CLASS (parent_class)->constructed (object);
+
+ g_return_if_fail (GIMP_IS_GIMP (preset->gimp));
+}
+
+static void
+gimp_tool_preset_finalize (GObject *object)
+{
+ GimpToolPreset *tool_preset = GIMP_TOOL_PRESET (object);
+
+ gimp_tool_preset_set_options (tool_preset, NULL);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gimp_tool_preset_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpToolPreset *tool_preset = GIMP_TOOL_PRESET (object);
+
+ switch (property_id)
+ {
+ case PROP_NAME:
+ gimp_object_set_name (GIMP_OBJECT (tool_preset),
+ g_value_get_string (value));
+ break;
+
+ case PROP_GIMP:
+ tool_preset->gimp = g_value_get_object (value); /* don't ref */
+ break;
+
+ case PROP_TOOL_OPTIONS:
+ gimp_tool_preset_set_options (tool_preset,
+ GIMP_TOOL_OPTIONS (g_value_get_object (value)));
+ break;
+
+ case PROP_USE_FG_BG:
+ tool_preset->use_fg_bg = g_value_get_boolean (value);
+ break;
+ case PROP_USE_OPACITY_PAINT_MODE:
+ tool_preset->use_opacity_paint_mode = g_value_get_boolean (value);
+ break;
+ case PROP_USE_BRUSH:
+ tool_preset->use_brush = g_value_get_boolean (value);
+ break;
+ case PROP_USE_DYNAMICS:
+ tool_preset->use_dynamics = g_value_get_boolean (value);
+ break;
+ case PROP_USE_MYBRUSH:
+ tool_preset->use_mybrush = g_value_get_boolean (value);
+ break;
+ case PROP_USE_PATTERN:
+ tool_preset->use_pattern = g_value_get_boolean (value);
+ break;
+ case PROP_USE_PALETTE:
+ tool_preset->use_palette = g_value_get_boolean (value);
+ break;
+ case PROP_USE_GRADIENT:
+ tool_preset->use_gradient = g_value_get_boolean (value);
+ break;
+ case PROP_USE_FONT:
+ tool_preset->use_font = g_value_get_boolean (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_tool_preset_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpToolPreset *tool_preset = GIMP_TOOL_PRESET (object);
+
+ switch (property_id)
+ {
+ case PROP_NAME:
+ g_value_set_string (value, gimp_object_get_name (tool_preset));
+ break;
+
+ case PROP_GIMP:
+ g_value_set_object (value, tool_preset->gimp);
+ break;
+
+ case PROP_TOOL_OPTIONS:
+ g_value_set_object (value, tool_preset->tool_options);
+ break;
+
+ case PROP_USE_FG_BG:
+ g_value_set_boolean (value, tool_preset->use_fg_bg);
+ break;
+ case PROP_USE_OPACITY_PAINT_MODE:
+ g_value_set_boolean (value, tool_preset->use_opacity_paint_mode);
+ break;
+ case PROP_USE_BRUSH:
+ g_value_set_boolean (value, tool_preset->use_brush);
+ break;
+ case PROP_USE_MYBRUSH:
+ g_value_set_boolean (value, tool_preset->use_mybrush);
+ break;
+ case PROP_USE_DYNAMICS:
+ g_value_set_boolean (value, tool_preset->use_dynamics);
+ break;
+ case PROP_USE_PATTERN:
+ g_value_set_boolean (value, tool_preset->use_pattern);
+ break;
+ case PROP_USE_PALETTE:
+ g_value_set_boolean (value, tool_preset->use_palette);
+ break;
+ case PROP_USE_GRADIENT:
+ g_value_set_boolean (value, tool_preset->use_gradient);
+ break;
+ case PROP_USE_FONT:
+ g_value_set_boolean (value, tool_preset->use_font);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_tool_preset_dispatch_properties_changed (GObject *object,
+ guint n_pspecs,
+ GParamSpec **pspecs)
+{
+ gint i;
+
+ G_OBJECT_CLASS (parent_class)->dispatch_properties_changed (object,
+ n_pspecs, pspecs);
+
+ for (i = 0; i < n_pspecs; i++)
+ {
+ if (pspecs[i]->flags & GIMP_CONFIG_PARAM_SERIALIZE)
+ {
+ gimp_data_dirty (GIMP_DATA (object));
+ break;
+ }
+ }
+}
+
+static const gchar *
+gimp_tool_preset_get_extension (GimpData *data)
+{
+ return GIMP_TOOL_PRESET_FILE_EXTENSION;
+}
+
+static gboolean
+gimp_tool_preset_deserialize_property (GimpConfig *config,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec,
+ GScanner *scanner,
+ GTokenType *expected)
+{
+ GimpToolPreset *tool_preset = GIMP_TOOL_PRESET (config);
+
+ switch (property_id)
+ {
+ case PROP_TOOL_OPTIONS:
+ {
+ GObject *options;
+ gchar *type_name;
+ GType type;
+ GimpContextPropMask serialize_props;
+
+ if (! gimp_scanner_parse_string (scanner, &type_name))
+ {
+ *expected = G_TOKEN_STRING;
+ break;
+ }
+
+ if (! (type_name && *type_name))
+ {
+ g_scanner_error (scanner, "GimpToolOptions type name is empty");
+ *expected = G_TOKEN_NONE;
+ g_free (type_name);
+ break;
+ }
+
+ if (! strcmp (type_name, "GimpTransformOptions"))
+ {
+ g_printerr ("Correcting tool options type GimpTransformOptions "
+ "to GimpTransformGridOptions\n");
+ g_free (type_name);
+ type_name = g_strdup ("GimpTransformGridOptions");
+ }
+
+ type = g_type_from_name (type_name);
+
+ if (! type)
+ {
+ g_scanner_error (scanner,
+ "unable to determine type of '%s'",
+ type_name);
+ *expected = G_TOKEN_NONE;
+ g_free (type_name);
+ break;
+ }
+
+ if (! g_type_is_a (type, GIMP_TYPE_TOOL_OPTIONS))
+ {
+ g_scanner_error (scanner,
+ "'%s' is not a subclass of GimpToolOptions",
+ type_name);
+ *expected = G_TOKEN_NONE;
+ g_free (type_name);
+ break;
+ }
+
+ g_free (type_name);
+
+ options = g_object_new (type,
+ "gimp", tool_preset->gimp,
+ NULL);
+
+ /* Initialize all GimpContext object properties that can be
+ * used by presets with some non-NULL object, so loading a
+ * broken preset won't leave us with NULL objects that have
+ * bad effects. See bug #742159.
+ */
+ gimp_context_copy_properties (gimp_get_user_context (tool_preset->gimp),
+ GIMP_CONTEXT (options),
+ GIMP_CONTEXT_PROP_MASK_BRUSH |
+ GIMP_CONTEXT_PROP_MASK_DYNAMICS |
+ GIMP_CONTEXT_PROP_MASK_MYBRUSH |
+ GIMP_CONTEXT_PROP_MASK_PATTERN |
+ GIMP_CONTEXT_PROP_MASK_GRADIENT |
+ GIMP_CONTEXT_PROP_MASK_PALETTE |
+ GIMP_CONTEXT_PROP_MASK_FONT);
+
+ if (! GIMP_CONFIG_GET_INTERFACE (options)->deserialize (GIMP_CONFIG (options),
+ scanner, 1,
+ NULL))
+ {
+ *expected = G_TOKEN_NONE;
+ g_object_unref (options);
+ break;
+ }
+
+ /* we need both tool and tool-info on the options */
+ if (gimp_context_get_tool (GIMP_CONTEXT (options)))
+ {
+ g_object_set (options,
+ "tool-info",
+ gimp_context_get_tool (GIMP_CONTEXT (options)),
+ NULL);
+ }
+ else if (GIMP_TOOL_OPTIONS (options)->tool_info)
+ {
+ g_object_set (options,
+ "tool", GIMP_TOOL_OPTIONS (options)->tool_info,
+ NULL);
+ }
+ else
+ {
+ /* if we have none, the options set_property() logic will
+ * replace the NULL with its best guess
+ */
+ g_object_set (options,
+ "tool", NULL,
+ "tool-info", NULL,
+ NULL);
+ }
+
+ serialize_props =
+ gimp_context_get_serialize_properties (GIMP_CONTEXT (options));
+
+ gimp_context_set_serialize_properties (GIMP_CONTEXT (options),
+ serialize_props |
+ GIMP_CONTEXT_PROP_MASK_TOOL);
+
+ g_value_take_object (value, options);
+ }
+ break;
+
+ default:
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void
+gimp_tool_preset_set_options (GimpToolPreset *preset,
+ GimpToolOptions *options)
+{
+ if (preset->tool_options)
+ {
+ g_signal_handlers_disconnect_by_func (preset->tool_options,
+ gimp_tool_preset_options_notify,
+ preset);
+
+ g_signal_handlers_disconnect_by_func (preset->tool_options,
+ gimp_tool_preset_options_prop_name_changed,
+ preset);
+
+ g_clear_object (&preset->tool_options);
+ }
+
+ if (options)
+ {
+ GimpContextPropMask serialize_props;
+
+ preset->tool_options =
+ GIMP_TOOL_OPTIONS (gimp_config_duplicate (GIMP_CONFIG (options)));
+
+ serialize_props =
+ gimp_context_get_serialize_properties (GIMP_CONTEXT (preset->tool_options));
+
+ gimp_context_set_serialize_properties (GIMP_CONTEXT (preset->tool_options),
+ serialize_props |
+ GIMP_CONTEXT_PROP_MASK_TOOL);
+
+ if (! (serialize_props & GIMP_CONTEXT_PROP_MASK_FOREGROUND) &&
+ ! (serialize_props & GIMP_CONTEXT_PROP_MASK_BACKGROUND))
+ g_object_set (preset, "use-fg-bg", FALSE, NULL);
+
+ if (! (serialize_props & GIMP_CONTEXT_PROP_MASK_OPACITY) &&
+ ! (serialize_props & GIMP_CONTEXT_PROP_MASK_PAINT_MODE))
+ g_object_set (preset, "use-opacity-paint-mode", FALSE, NULL);
+
+ if (! (serialize_props & GIMP_CONTEXT_PROP_MASK_BRUSH))
+ g_object_set (preset, "use-brush", FALSE, NULL);
+
+ if (! (serialize_props & GIMP_CONTEXT_PROP_MASK_DYNAMICS))
+ g_object_set (preset, "use-dynamics", FALSE, NULL);
+
+ if (! (serialize_props & GIMP_CONTEXT_PROP_MASK_MYBRUSH))
+ g_object_set (preset, "use-mypaint-brush", FALSE, NULL);
+
+ if (! (serialize_props & GIMP_CONTEXT_PROP_MASK_GRADIENT))
+ g_object_set (preset, "use-gradient", FALSE, NULL);
+
+ if (! (serialize_props & GIMP_CONTEXT_PROP_MASK_PATTERN))
+ g_object_set (preset, "use-pattern", FALSE, NULL);
+
+ if (! (serialize_props & GIMP_CONTEXT_PROP_MASK_PALETTE))
+ g_object_set (preset, "use-palette", FALSE, NULL);
+
+ if (! (serialize_props & GIMP_CONTEXT_PROP_MASK_FONT))
+ g_object_set (preset, "use-font", FALSE, NULL);
+
+ /* see comment above the DEFAULT defines at the top of the file */
+ if (! g_strcmp0 ("gimp-gradient-tool",
+ gimp_object_get_name (preset->tool_options->tool_info)))
+ g_object_set (preset, "use-gradient", TRUE, NULL);
+
+ g_signal_connect (preset->tool_options, "notify",
+ G_CALLBACK (gimp_tool_preset_options_notify),
+ preset);
+
+ g_signal_connect (preset->tool_options, "prop-name-changed",
+ G_CALLBACK (gimp_tool_preset_options_prop_name_changed),
+ preset);
+ }
+
+ g_object_notify (G_OBJECT (preset), "tool-options");
+}
+
+static void
+gimp_tool_preset_options_notify (GObject *tool_options,
+ const GParamSpec *pspec,
+ GimpToolPreset *preset)
+{
+ if (pspec->owner_type == GIMP_TYPE_CONTEXT)
+ {
+ GimpContextPropMask serialize_props;
+
+ serialize_props =
+ gimp_context_get_serialize_properties (GIMP_CONTEXT (tool_options));
+
+ if ((1 << pspec->param_id) & serialize_props)
+ {
+ g_object_notify (G_OBJECT (preset), "tool-options");
+ }
+ }
+ else if (pspec->flags & GIMP_CONFIG_PARAM_SERIALIZE)
+ {
+ g_object_notify (G_OBJECT (preset), "tool-options");
+ }
+}
+
+static void
+gimp_tool_preset_options_prop_name_changed (GimpContext *tool_options,
+ GimpContextPropType prop,
+ GimpToolPreset *preset)
+{
+ GimpContextPropMask serialize_props;
+
+ serialize_props =
+ gimp_context_get_serialize_properties (GIMP_CONTEXT (preset->tool_options));
+
+ if ((1 << prop) & serialize_props)
+ {
+ g_object_notify (G_OBJECT (preset), "tool-options");
+ }
+}
+
+
+/* public functions */
+
+GimpData *
+gimp_tool_preset_new (GimpContext *context,
+ const gchar *unused)
+{
+ GimpToolInfo *tool_info;
+ const gchar *icon_name;
+
+ g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL);
+
+ tool_info = gimp_context_get_tool (context);
+
+ g_return_val_if_fail (tool_info != NULL, NULL);
+
+ icon_name = gimp_viewable_get_icon_name (GIMP_VIEWABLE (tool_info));
+
+ return g_object_new (GIMP_TYPE_TOOL_PRESET,
+ "name", tool_info->label,
+ "icon-name", icon_name,
+ "gimp", context->gimp,
+ "tool-options", tool_info->tool_options,
+ NULL);
+}
+
+GimpContextPropMask
+gimp_tool_preset_get_prop_mask (GimpToolPreset *preset)
+{
+ GimpContextPropMask serialize_props;
+ GimpContextPropMask use_props = 0;
+
+ g_return_val_if_fail (GIMP_IS_TOOL_PRESET (preset), 0);
+
+ serialize_props =
+ gimp_context_get_serialize_properties (GIMP_CONTEXT (preset->tool_options));
+
+ if (preset->use_fg_bg)
+ {
+ use_props |= (GIMP_CONTEXT_PROP_MASK_FOREGROUND & serialize_props);
+ use_props |= (GIMP_CONTEXT_PROP_MASK_BACKGROUND & serialize_props);
+ }
+
+ if (preset->use_opacity_paint_mode)
+ {
+ use_props |= (GIMP_CONTEXT_PROP_MASK_OPACITY & serialize_props);
+ use_props |= (GIMP_CONTEXT_PROP_MASK_PAINT_MODE & serialize_props);
+ }
+
+ if (preset->use_brush)
+ use_props |= (GIMP_CONTEXT_PROP_MASK_BRUSH & serialize_props);
+
+ if (preset->use_dynamics)
+ use_props |= (GIMP_CONTEXT_PROP_MASK_DYNAMICS & serialize_props);
+
+ if (preset->use_mybrush)
+ use_props |= (GIMP_CONTEXT_PROP_MASK_MYBRUSH & serialize_props);
+
+ if (preset->use_pattern)
+ use_props |= (GIMP_CONTEXT_PROP_MASK_PATTERN & serialize_props);
+
+ if (preset->use_palette)
+ use_props |= (GIMP_CONTEXT_PROP_MASK_PALETTE & serialize_props);
+
+ if (preset->use_gradient)
+ use_props |= (GIMP_CONTEXT_PROP_MASK_GRADIENT & serialize_props);
+
+ if (preset->use_font)
+ use_props |= (GIMP_CONTEXT_PROP_MASK_FONT & serialize_props);
+
+ return use_props;
+}
diff --git a/app/core/gimptoolpreset.h b/app/core/gimptoolpreset.h
new file mode 100644
index 0000000..bf2cceb
--- /dev/null
+++ b/app/core/gimptoolpreset.h
@@ -0,0 +1,67 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-1999 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_TOOL_PRESET_H__
+#define __GIMP_TOOL_PRESET_H__
+
+
+#include "gimpdata.h"
+
+
+#define GIMP_TYPE_TOOL_PRESET (gimp_tool_preset_get_type ())
+#define GIMP_TOOL_PRESET(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_TOOL_PRESET, GimpToolPreset))
+#define GIMP_TOOL_PRESET_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_TOOL_PRESET, GimpToolPresetClass))
+#define GIMP_IS_TOOL_PRESET(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_TOOL_PRESET))
+#define GIMP_IS_TOOL_PRESET_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_TOOL_PRESET))
+#define GIMP_TOOL_PRESET_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_TOOL_PRESET, GimpToolPresetClass))
+
+
+typedef struct _GimpToolPresetClass GimpToolPresetClass;
+
+struct _GimpToolPreset
+{
+ GimpData parent_instance;
+
+ Gimp *gimp;
+ GimpToolOptions *tool_options;
+
+ gboolean use_fg_bg;
+ gboolean use_opacity_paint_mode;
+ gboolean use_brush;
+ gboolean use_dynamics;
+ gboolean use_mybrush;
+ gboolean use_gradient;
+ gboolean use_pattern;
+ gboolean use_palette;
+ gboolean use_font;
+};
+
+struct _GimpToolPresetClass
+{
+ GimpDataClass parent_class;
+};
+
+
+GType gimp_tool_preset_get_type (void) G_GNUC_CONST;
+
+GimpData * gimp_tool_preset_new (GimpContext *context,
+ const gchar *unused);
+
+GimpContextPropMask gimp_tool_preset_get_prop_mask (GimpToolPreset *preset);
+
+
+#endif /* __GIMP_TOOL_PRESET_H__ */
diff --git a/app/core/gimptreehandler.c b/app/core/gimptreehandler.c
new file mode 100644
index 0000000..b26768e
--- /dev/null
+++ b/app/core/gimptreehandler.c
@@ -0,0 +1,238 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * GimpTreeHandler
+ * Copyright (C) 2009 Michael Natterer <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "core-types.h"
+
+#include "gimpcontainer.h"
+#include "gimptreehandler.h"
+#include "gimpviewable.h"
+
+
+static void gimp_tree_handler_dispose (GObject *object);
+
+static void gimp_tree_handler_freeze (GimpTreeHandler *handler,
+ GimpContainer *container);
+static void gimp_tree_handler_thaw (GimpTreeHandler *handler,
+ GimpContainer *container);
+
+static void gimp_tree_handler_add_container (GimpTreeHandler *handler,
+ GimpContainer *container);
+static void gimp_tree_handler_add_foreach (GimpViewable *viewable,
+ GimpTreeHandler *handler);
+static void gimp_tree_handler_add (GimpTreeHandler *handler,
+ GimpViewable *viewable,
+ GimpContainer *container);
+
+static void gimp_tree_handler_remove_container (GimpTreeHandler *handler,
+ GimpContainer *container);
+static void gimp_tree_handler_remove_foreach (GimpViewable *viewable,
+ GimpTreeHandler *handler);
+static void gimp_tree_handler_remove (GimpTreeHandler *handler,
+ GimpViewable *viewable,
+ GimpContainer *container);
+
+
+G_DEFINE_TYPE (GimpTreeHandler, gimp_tree_handler, GIMP_TYPE_OBJECT)
+
+#define parent_class gimp_tree_handler_parent_class
+
+
+static void
+gimp_tree_handler_class_init (GimpTreeHandlerClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->dispose = gimp_tree_handler_dispose;
+}
+
+static void
+gimp_tree_handler_init (GimpTreeHandler *handler)
+{
+}
+
+static void
+gimp_tree_handler_dispose (GObject *object)
+{
+ GimpTreeHandler *handler = GIMP_TREE_HANDLER (object);
+
+ if (handler->container)
+ {
+ g_signal_handlers_disconnect_by_func (handler->container,
+ gimp_tree_handler_freeze,
+ handler);
+ g_signal_handlers_disconnect_by_func (handler->container,
+ gimp_tree_handler_thaw,
+ handler);
+
+ if (! gimp_container_frozen (handler->container))
+ gimp_tree_handler_remove_container (handler, handler->container);
+
+ g_clear_object (&handler->container);
+ g_clear_pointer (&handler->signal_name, g_free);
+ }
+
+ G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+
+/* public functions */
+
+GimpTreeHandler *
+gimp_tree_handler_connect (GimpContainer *container,
+ const gchar *signal_name,
+ GCallback callback,
+ gpointer user_data)
+{
+ GimpTreeHandler *handler;
+
+ g_return_val_if_fail (GIMP_IS_CONTAINER (container), NULL);
+ g_return_val_if_fail (signal_name != NULL, NULL);
+
+ handler = g_object_new (GIMP_TYPE_TREE_HANDLER, NULL);
+
+ handler->container = g_object_ref (container);
+ handler->signal_name = g_strdup (signal_name);
+ handler->callback = callback;
+ handler->user_data = user_data;
+
+ if (! gimp_container_frozen (container))
+ gimp_tree_handler_add_container (handler, container);
+
+ g_signal_connect_object (container, "freeze",
+ G_CALLBACK (gimp_tree_handler_freeze),
+ handler,
+ G_CONNECT_SWAPPED);
+ g_signal_connect_object (container, "thaw",
+ G_CALLBACK (gimp_tree_handler_thaw),
+ handler,
+ G_CONNECT_SWAPPED);
+
+ return handler;
+}
+
+void
+gimp_tree_handler_disconnect (GimpTreeHandler *handler)
+{
+ g_return_if_fail (GIMP_IS_TREE_HANDLER (handler));
+
+ g_object_run_dispose (G_OBJECT (handler));
+ g_object_unref (handler);
+}
+
+
+/* private functions */
+
+static void
+gimp_tree_handler_freeze (GimpTreeHandler *handler,
+ GimpContainer *container)
+{
+ gimp_tree_handler_remove_container (handler, container);
+}
+
+static void
+gimp_tree_handler_thaw (GimpTreeHandler *handler,
+ GimpContainer *container)
+{
+ gimp_tree_handler_add_container (handler, container);
+}
+
+static void
+gimp_tree_handler_add_container (GimpTreeHandler *handler,
+ GimpContainer *container)
+{
+ gimp_container_foreach (container,
+ (GFunc) gimp_tree_handler_add_foreach,
+ handler);
+
+ g_signal_connect_object (container, "add",
+ G_CALLBACK (gimp_tree_handler_add),
+ handler,
+ G_CONNECT_SWAPPED);
+ g_signal_connect_object (container, "remove",
+ G_CALLBACK (gimp_tree_handler_remove),
+ handler,
+ G_CONNECT_SWAPPED);
+}
+
+static void
+gimp_tree_handler_add_foreach (GimpViewable *viewable,
+ GimpTreeHandler *handler)
+{
+ gimp_tree_handler_add (handler, viewable, NULL);
+}
+
+static void
+gimp_tree_handler_add (GimpTreeHandler *handler,
+ GimpViewable *viewable,
+ GimpContainer *unused)
+{
+ GimpContainer *children = gimp_viewable_get_children (viewable);
+
+ g_signal_connect (viewable,
+ handler->signal_name,
+ handler->callback,
+ handler->user_data);
+
+ if (children)
+ gimp_tree_handler_add_container (handler, children);
+}
+
+static void
+gimp_tree_handler_remove_container (GimpTreeHandler *handler,
+ GimpContainer *container)
+{
+ g_signal_handlers_disconnect_by_func (container,
+ gimp_tree_handler_add,
+ handler);
+ g_signal_handlers_disconnect_by_func (container,
+ gimp_tree_handler_remove,
+ handler);
+
+ gimp_container_foreach (container,
+ (GFunc) gimp_tree_handler_remove_foreach,
+ handler);
+}
+
+static void
+gimp_tree_handler_remove_foreach (GimpViewable *viewable,
+ GimpTreeHandler *handler)
+{
+ gimp_tree_handler_remove (handler, viewable, NULL);
+}
+
+static void
+gimp_tree_handler_remove (GimpTreeHandler *handler,
+ GimpViewable *viewable,
+ GimpContainer *unused)
+{
+ GimpContainer *children = gimp_viewable_get_children (viewable);
+
+ if (children)
+ gimp_tree_handler_remove_container (handler, children);
+
+ g_signal_handlers_disconnect_by_func (viewable,
+ handler->callback,
+ handler->user_data);
+}
diff --git a/app/core/gimptreehandler.h b/app/core/gimptreehandler.h
new file mode 100644
index 0000000..27497c4
--- /dev/null
+++ b/app/core/gimptreehandler.h
@@ -0,0 +1,64 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * GimpTreeHandler
+ * Copyright (C) 2009 Michael Natterer <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_TREE_HANDLER_H__
+#define __GIMP_TREE_HANDLER_H__
+
+
+#include "core/gimpobject.h"
+
+
+#define GIMP_TYPE_TREE_HANDLER (gimp_tree_handler_get_type ())
+#define GIMP_TREE_HANDLER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_TREE_HANDLER, GimpTreeHandler))
+#define GIMP_TREE_HANDLER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_TREE_HANDLER, GimpTreeHandlerClass))
+#define GIMP_IS_TREE_HANDLER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_TREE_HANDLER))
+#define GIMP_IS_TREE_HANDLER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_TREE_HANDLER))
+#define GIMP_TREE_HANDLER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_TREE_HANDLER, GimpTreeHandlerClass))
+
+
+typedef struct _GimpTreeHandlerClass GimpTreeHandlerClass;
+
+struct _GimpTreeHandler
+{
+ GimpObject parent_instance;
+
+ GimpContainer *container;
+
+ gchar *signal_name;
+ GCallback callback;
+ gpointer user_data;
+};
+
+struct _GimpTreeHandlerClass
+{
+ GimpObjectClass parent_class;
+};
+
+
+GType gimp_tree_handler_get_type (void) G_GNUC_CONST;
+
+GimpTreeHandler * gimp_tree_handler_connect (GimpContainer *container,
+ const gchar *signal_name,
+ GCallback callback,
+ gpointer user_data);
+void gimp_tree_handler_disconnect (GimpTreeHandler *handler);
+
+
+#endif /* __GIMP_TREE_HANDLER_H__ */
diff --git a/app/core/gimptreeproxy.c b/app/core/gimptreeproxy.c
new file mode 100644
index 0000000..76fad91
--- /dev/null
+++ b/app/core/gimptreeproxy.c
@@ -0,0 +1,634 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimptreeproxy.c
+ * Copyright (C) 2020 Ell
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpbase/gimpbase.h"
+
+#include "core-types.h"
+
+#include "gimpviewable.h"
+#include "gimptreeproxy.h"
+
+
+enum
+{
+ PROP_0,
+ PROP_CONTAINER,
+ PROP_FLAT
+};
+
+
+struct _GimpTreeProxyPrivate
+{
+ GimpContainer *container;
+ gboolean flat;
+};
+
+
+/* local function prototypes */
+
+static void gimp_tree_proxy_dispose (GObject *object);
+static void gimp_tree_proxy_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_tree_proxy_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static void gimp_tree_proxy_container_add (GimpContainer *container,
+ GimpObject *object,
+ GimpTreeProxy *tree_proxy);
+static void gimp_tree_proxy_container_remove (GimpContainer *container,
+ GimpObject *object,
+ GimpTreeProxy *tree_proxy);
+static void gimp_tree_proxy_container_reorder (GimpContainer *container,
+ GimpObject *object,
+ gint new_index,
+ GimpTreeProxy *tree_proxy);
+static void gimp_tree_proxy_container_freeze (GimpContainer *container,
+ GimpTreeProxy *tree_proxy);
+static void gimp_tree_proxy_container_thaw (GimpContainer *container,
+ GimpTreeProxy *tree_proxy);
+
+static gint gimp_tree_proxy_add_container (GimpTreeProxy *tree_proxy,
+ GimpContainer *container,
+ gint index);
+static void gimp_tree_proxy_remove_container (GimpTreeProxy *tree_proxy,
+ GimpContainer *container);
+
+static gint gimp_tree_proxy_add_object (GimpTreeProxy *tree_proxy,
+ GimpObject *object,
+ gint index);
+static void gimp_tree_proxy_remove_object (GimpTreeProxy *tree_proxy,
+ GimpObject *object);
+
+static gint gimp_tree_proxy_find_container (GimpTreeProxy *tree_proxy,
+ GimpContainer *container);
+static gint gimp_tree_proxy_find_object (GimpContainer *container,
+ GimpObject *object);
+
+
+G_DEFINE_TYPE_WITH_PRIVATE (GimpTreeProxy, gimp_tree_proxy, GIMP_TYPE_LIST)
+
+#define parent_class gimp_tree_proxy_parent_class
+
+
+/* private functions */
+
+static void
+gimp_tree_proxy_class_init (GimpTreeProxyClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->dispose = gimp_tree_proxy_dispose;
+ object_class->set_property = gimp_tree_proxy_set_property;
+ object_class->get_property = gimp_tree_proxy_get_property;
+
+ g_object_class_install_property (object_class, PROP_CONTAINER,
+ g_param_spec_object ("container", NULL, NULL,
+ GIMP_TYPE_CONTAINER,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_FLAT,
+ g_param_spec_boolean ("flat", NULL, NULL,
+ FALSE,
+ GIMP_PARAM_READWRITE));
+}
+
+static void
+gimp_tree_proxy_init (GimpTreeProxy *tree_proxy)
+{
+ tree_proxy->priv = gimp_tree_proxy_get_instance_private (tree_proxy);
+}
+
+static void
+gimp_tree_proxy_dispose (GObject *object)
+{
+ GimpTreeProxy *tree_proxy = GIMP_TREE_PROXY (object);
+
+ gimp_tree_proxy_set_container (tree_proxy, NULL);
+
+ G_OBJECT_CLASS (parent_class)->dispose (object);;
+}
+
+static void
+gimp_tree_proxy_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpTreeProxy *tree_proxy = GIMP_TREE_PROXY (object);
+
+ switch (property_id)
+ {
+ case PROP_CONTAINER:
+ gimp_tree_proxy_set_container (tree_proxy, g_value_get_object (value));
+ break;
+
+ case PROP_FLAT:
+ gimp_tree_proxy_set_flat (tree_proxy, g_value_get_boolean (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_tree_proxy_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpTreeProxy *tree_proxy = GIMP_TREE_PROXY (object);
+
+ switch (property_id)
+ {
+ case PROP_CONTAINER:
+ g_value_set_object (value, tree_proxy->priv->container);
+ break;
+
+ case PROP_FLAT:
+ g_value_set_boolean (value, tree_proxy->priv->flat);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_tree_proxy_container_add (GimpContainer *container,
+ GimpObject *object,
+ GimpTreeProxy *tree_proxy)
+{
+ gint index;
+
+ if (tree_proxy->priv->flat)
+ {
+ index = gimp_tree_proxy_find_container (tree_proxy, container) +
+ gimp_tree_proxy_find_object (container, object);
+ }
+ else
+ {
+ index = gimp_container_get_child_index (container, object);
+ }
+
+ gimp_tree_proxy_add_object (tree_proxy, object, index);
+}
+
+static void
+gimp_tree_proxy_container_remove (GimpContainer *container,
+ GimpObject *object,
+ GimpTreeProxy *tree_proxy)
+{
+ gimp_tree_proxy_remove_object (tree_proxy, object);
+}
+
+static void
+gimp_tree_proxy_container_reorder (GimpContainer *container,
+ GimpObject *object,
+ gint new_index,
+ GimpTreeProxy *tree_proxy)
+{
+ gint index;
+
+ if (tree_proxy->priv->flat)
+ {
+ index = gimp_tree_proxy_find_container (tree_proxy, container) +
+ gimp_tree_proxy_find_object (container, object);
+
+ if (gimp_viewable_get_children (GIMP_VIEWABLE (object)))
+ {
+ gimp_container_freeze (GIMP_CONTAINER (tree_proxy));
+
+ gimp_tree_proxy_remove_object (tree_proxy, object);
+ gimp_tree_proxy_add_object (tree_proxy, object, index);
+
+ gimp_container_thaw (GIMP_CONTAINER (tree_proxy));
+
+ return;
+ }
+ }
+ else
+ {
+ index = new_index;
+ }
+
+ gimp_container_reorder (GIMP_CONTAINER (tree_proxy), object, index);
+}
+
+static void
+gimp_tree_proxy_container_freeze (GimpContainer *container,
+ GimpTreeProxy *tree_proxy)
+{
+ gimp_container_freeze (GIMP_CONTAINER (tree_proxy));
+}
+
+static void
+gimp_tree_proxy_container_thaw (GimpContainer *container,
+ GimpTreeProxy *tree_proxy)
+{
+ gimp_container_thaw (GIMP_CONTAINER (tree_proxy));
+}
+
+typedef struct
+{
+ GimpTreeProxy *tree_proxy;
+ gint index;
+} AddContainerData;
+
+static void
+gimp_tree_proxy_add_container_func (GimpObject *object,
+ AddContainerData *data)
+{
+ data->index = gimp_tree_proxy_add_object (data->tree_proxy,
+ object, data->index);
+}
+
+static gint
+gimp_tree_proxy_add_container (GimpTreeProxy *tree_proxy,
+ GimpContainer *container,
+ gint index)
+{
+ AddContainerData data;
+
+ g_signal_connect (container, "add",
+ G_CALLBACK (gimp_tree_proxy_container_add),
+ tree_proxy);
+ g_signal_connect (container, "remove",
+ G_CALLBACK (gimp_tree_proxy_container_remove),
+ tree_proxy);
+ g_signal_connect (container, "reorder",
+ G_CALLBACK (gimp_tree_proxy_container_reorder),
+ tree_proxy);
+ g_signal_connect (container, "freeze",
+ G_CALLBACK (gimp_tree_proxy_container_freeze),
+ tree_proxy);
+ g_signal_connect (container, "thaw",
+ G_CALLBACK (gimp_tree_proxy_container_thaw),
+ tree_proxy);
+
+ data.tree_proxy = tree_proxy;
+ data.index = index;
+
+ gimp_container_freeze (GIMP_CONTAINER (tree_proxy));
+
+ gimp_container_foreach (container,
+ (GFunc) gimp_tree_proxy_add_container_func,
+ &data);
+
+ gimp_container_thaw (GIMP_CONTAINER (tree_proxy));
+
+ return data.index;
+}
+
+static void
+gimp_tree_proxy_remove_container_func (GimpObject *object,
+ GimpTreeProxy *tree_proxy)
+{
+ gimp_tree_proxy_remove_object (tree_proxy, object);
+}
+
+static void
+gimp_tree_proxy_remove_container (GimpTreeProxy *tree_proxy,
+ GimpContainer *container)
+{
+ gimp_container_freeze (GIMP_CONTAINER (tree_proxy));
+
+ gimp_container_foreach (container,
+ (GFunc) gimp_tree_proxy_remove_container_func,
+ tree_proxy);
+
+ gimp_container_thaw (GIMP_CONTAINER (tree_proxy));
+
+ g_signal_handlers_disconnect_by_func (
+ container,
+ gimp_tree_proxy_container_add,
+ tree_proxy);
+ g_signal_handlers_disconnect_by_func (
+ container,
+ gimp_tree_proxy_container_remove,
+ tree_proxy);
+ g_signal_handlers_disconnect_by_func (
+ container,
+ gimp_tree_proxy_container_reorder,
+ tree_proxy);
+ g_signal_handlers_disconnect_by_func (
+ container,
+ gimp_tree_proxy_container_freeze,
+ tree_proxy);
+ g_signal_handlers_disconnect_by_func (
+ container,
+ gimp_tree_proxy_container_thaw,
+ tree_proxy);
+}
+
+static gint
+gimp_tree_proxy_add_object (GimpTreeProxy *tree_proxy,
+ GimpObject *object,
+ gint index)
+{
+ if (index == gimp_container_get_n_children (GIMP_CONTAINER (tree_proxy)))
+ index = -1;
+
+ if (tree_proxy->priv->flat)
+ {
+ GimpContainer *children;
+
+ children = gimp_viewable_get_children (GIMP_VIEWABLE (object));
+
+ if (children)
+ return gimp_tree_proxy_add_container (tree_proxy, children, index);
+ }
+
+ if (index >= 0)
+ {
+ gimp_container_insert (GIMP_CONTAINER (tree_proxy), object, index);
+
+ return index + 1;
+ }
+ else
+ {
+ gimp_container_add (GIMP_CONTAINER (tree_proxy), object);
+
+ return index;
+ }
+}
+
+static void
+gimp_tree_proxy_remove_object (GimpTreeProxy *tree_proxy,
+ GimpObject *object)
+{
+ if (tree_proxy->priv->flat)
+ {
+ GimpContainer *children;
+
+ children = gimp_viewable_get_children (GIMP_VIEWABLE (object));
+
+ if (children)
+ return gimp_tree_proxy_remove_container (tree_proxy, children);
+ }
+
+ gimp_container_remove (GIMP_CONTAINER (tree_proxy), object);
+}
+
+typedef struct
+{
+ GimpContainer *container;
+ gint index;
+} FindContainerData;
+
+static gboolean
+gimp_tree_proxy_find_container_search_func (GimpObject *object,
+ FindContainerData *data)
+{
+ GimpContainer *children;
+
+ children = gimp_viewable_get_children (GIMP_VIEWABLE (object));
+
+ if (children)
+ {
+ if (children == data->container)
+ return TRUE;
+
+ return gimp_container_search (
+ children,
+ (GimpContainerSearchFunc) gimp_tree_proxy_find_container_search_func,
+ data) != NULL;
+ }
+
+ data->index++;
+
+ return FALSE;
+}
+
+static gint
+gimp_tree_proxy_find_container (GimpTreeProxy *tree_proxy,
+ GimpContainer *container)
+{
+ FindContainerData data;
+
+ if (container == tree_proxy->priv->container)
+ return 0;
+
+ data.container = container;
+ data.index = 0;
+
+ if (gimp_container_search (
+ tree_proxy->priv->container,
+ (GimpContainerSearchFunc) gimp_tree_proxy_find_container_search_func,
+ &data))
+ {
+ return data.index;
+ }
+
+ g_return_val_if_reached (0);
+}
+
+typedef struct
+{
+ GimpObject *object;
+ gint index;
+} FindObjectData;
+
+static gboolean
+gimp_tree_proxy_find_object_search_func (GimpObject *object,
+ FindObjectData *data)
+{
+ GimpContainer *children;
+
+ if (object == data->object)
+ return TRUE;
+
+ children = gimp_viewable_get_children (GIMP_VIEWABLE (object));
+
+ if (children)
+ {
+ return gimp_container_search (
+ children,
+ (GimpContainerSearchFunc) gimp_tree_proxy_find_object_search_func,
+ data) != NULL;
+ }
+
+ data->index++;
+
+ return FALSE;
+}
+
+static gint
+gimp_tree_proxy_find_object (GimpContainer *container,
+ GimpObject *object)
+{
+ FindObjectData data;
+
+ data.object = object;
+ data.index = 0;
+
+ if (gimp_container_search (
+ container,
+ (GimpContainerSearchFunc) gimp_tree_proxy_find_object_search_func,
+ &data))
+ {
+ return data.index;
+ }
+
+ g_return_val_if_reached (0);
+}
+
+
+/* public functions */
+
+GimpContainer *
+gimp_tree_proxy_new (GType children_type)
+{
+ GTypeClass *children_class;
+
+ children_class = g_type_class_ref (children_type);
+
+ g_return_val_if_fail (G_TYPE_CHECK_CLASS_TYPE (children_class,
+ GIMP_TYPE_VIEWABLE),
+ NULL);
+
+ g_type_class_unref (children_class);
+
+ return g_object_new (GIMP_TYPE_TREE_PROXY,
+ "children-type", children_type,
+ "policy", GIMP_CONTAINER_POLICY_WEAK,
+ "append", TRUE,
+ NULL);
+}
+
+GimpContainer *
+gimp_tree_proxy_new_for_container (GimpContainer *container)
+{
+ GimpTreeProxy *tree_proxy;
+
+ g_return_val_if_fail (GIMP_IS_CONTAINER (container), NULL);
+
+ tree_proxy = GIMP_TREE_PROXY (
+ gimp_tree_proxy_new (gimp_container_get_children_type (container)));
+
+ gimp_tree_proxy_set_container (tree_proxy, container);
+
+ return GIMP_CONTAINER (tree_proxy);
+}
+
+void
+gimp_tree_proxy_set_container (GimpTreeProxy *tree_proxy,
+ GimpContainer *container)
+{
+ g_return_if_fail (GIMP_IS_TREE_PROXY (tree_proxy));
+ g_return_if_fail (container == NULL || GIMP_IS_CONTAINER (container));
+
+ if (container)
+ {
+ GTypeClass *children_class;
+
+ children_class = g_type_class_ref (
+ gimp_container_get_children_type (container));
+
+ g_return_if_fail (
+ G_TYPE_CHECK_CLASS_TYPE (
+ children_class,
+ gimp_container_get_children_type (GIMP_CONTAINER (tree_proxy))));
+
+ g_type_class_unref (children_class);
+ }
+
+ if (container != tree_proxy->priv->container)
+ {
+ gimp_container_freeze (GIMP_CONTAINER (tree_proxy));
+
+ if (tree_proxy->priv->container)
+ {
+ gimp_tree_proxy_remove_container (tree_proxy,
+ tree_proxy->priv->container);
+ }
+
+ g_set_object (&tree_proxy->priv->container, container);
+
+ if (tree_proxy->priv->container)
+ {
+ gimp_tree_proxy_add_container (tree_proxy,
+ tree_proxy->priv->container,
+ -1);
+ }
+
+ gimp_container_thaw (GIMP_CONTAINER (tree_proxy));
+
+ g_object_notify (G_OBJECT (tree_proxy), "container");
+ }
+}
+
+GimpContainer *
+gimp_tree_proxy_get_container (GimpTreeProxy *tree_proxy)
+{
+ g_return_val_if_fail (GIMP_IS_TREE_PROXY (tree_proxy), NULL);
+
+ return tree_proxy->priv->container;
+}
+
+void
+gimp_tree_proxy_set_flat (GimpTreeProxy *tree_proxy,
+ gboolean flat)
+{
+ g_return_if_fail (GIMP_IS_TREE_PROXY (tree_proxy));
+
+ if (flat != tree_proxy->priv->flat)
+ {
+ gimp_container_freeze (GIMP_CONTAINER (tree_proxy));
+
+ if (tree_proxy->priv->container)
+ {
+ gimp_tree_proxy_remove_container (tree_proxy,
+ tree_proxy->priv->container);
+ }
+
+ tree_proxy->priv->flat = flat;
+
+ if (tree_proxy->priv->container)
+ {
+ gimp_tree_proxy_add_container (tree_proxy,
+ tree_proxy->priv->container,
+ -1);
+ }
+
+ gimp_container_thaw (GIMP_CONTAINER (tree_proxy));
+
+ g_object_notify (G_OBJECT (tree_proxy), "flat");
+ }
+}
+
+gboolean
+gimp_tree_proxy_get_flat (GimpTreeProxy *tree_proxy)
+{
+ g_return_val_if_fail (GIMP_IS_TREE_PROXY (tree_proxy), FALSE);
+
+ return tree_proxy->priv->flat;
+}
diff --git a/app/core/gimptreeproxy.h b/app/core/gimptreeproxy.h
new file mode 100644
index 0000000..8ae39f8
--- /dev/null
+++ b/app/core/gimptreeproxy.h
@@ -0,0 +1,66 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimptreeproxy.h
+ * Copyright (C) 2020 Ell
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_TREE_PROXY_H__
+#define __GIMP_TREE_PROXY_H__
+
+
+#include "gimplist.h"
+
+
+#define GIMP_TYPE_TREE_PROXY (gimp_tree_proxy_get_type ())
+#define GIMP_TREE_PROXY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_TREE_PROXY, GimpTreeProxy))
+#define GIMP_TREE_PROXY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_TREE_PROXY, GimpTreeProxyClass))
+#define GIMP_IS_TREE_PROXY(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_TREE_PROXY))
+#define GIMP_IS_TREE_PROXY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_TREE_PROXY))
+#define GIMP_TREE_PROXY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_TREE_PROXY, GimpTreeProxyClass))
+
+
+typedef struct _GimpTreeProxyPrivate GimpTreeProxyPrivate;
+typedef struct _GimpTreeProxyClass GimpTreeProxyClass;
+
+struct _GimpTreeProxy
+{
+ GimpList parent_instance;
+
+ GimpTreeProxyPrivate *priv;
+};
+
+struct _GimpTreeProxyClass
+{
+ GimpListClass parent_class;
+};
+
+
+GType gimp_tree_proxy_get_type (void) G_GNUC_CONST;
+
+GimpContainer * gimp_tree_proxy_new (GType children_type);
+GimpContainer * gimp_tree_proxy_new_for_container (GimpContainer *container);
+
+void gimp_tree_proxy_set_container (GimpTreeProxy *tree_proxy,
+ GimpContainer *container);
+GimpContainer * gimp_tree_proxy_get_container (GimpTreeProxy *tree_proxy);
+
+void gimp_tree_proxy_set_flat (GimpTreeProxy *tree_proxy,
+ gboolean flat);
+gboolean gimp_tree_proxy_get_flat (GimpTreeProxy *tree_proxy);
+
+
+#endif /* __GIMP_TREE_PROXY_H__ */
diff --git a/app/core/gimptriviallycancelablewaitable.c b/app/core/gimptriviallycancelablewaitable.c
new file mode 100644
index 0000000..d90a9cb
--- /dev/null
+++ b/app/core/gimptriviallycancelablewaitable.c
@@ -0,0 +1,92 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimptriviallycancelablewaitable.c
+ * Copyright (C) 2018 Ell
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "core-types.h"
+
+#include "gimpcancelable.h"
+#include "gimptriviallycancelablewaitable.h"
+#include "gimpwaitable.h"
+
+
+/* local function prototypes */
+
+static void gimp_trivially_cancelable_waitable_cancelable_iface_init (GimpCancelableInterface *iface);
+
+static void gimp_trivially_cancelable_waitable_cancel (GimpCancelable *cancelable);
+
+
+G_DEFINE_TYPE_WITH_CODE (GimpTriviallyCancelableWaitable, gimp_trivially_cancelable_waitable, GIMP_TYPE_UNCANCELABLE_WAITABLE,
+ G_IMPLEMENT_INTERFACE (GIMP_TYPE_CANCELABLE,
+ gimp_trivially_cancelable_waitable_cancelable_iface_init))
+
+#define parent_class gimp_trivially_cancelable_waitable_parent_class
+
+
+/* private functions */
+
+
+static void
+gimp_trivially_cancelable_waitable_class_init (GimpTriviallyCancelableWaitableClass *klass)
+{
+}
+
+static void
+gimp_trivially_cancelable_waitable_cancelable_iface_init (GimpCancelableInterface *iface)
+{
+ iface->cancel = gimp_trivially_cancelable_waitable_cancel;
+}
+
+static void
+gimp_trivially_cancelable_waitable_init (GimpTriviallyCancelableWaitable *trivially_cancelable_waitable)
+{
+}
+
+static void
+gimp_trivially_cancelable_waitable_cancel (GimpCancelable *cancelable)
+{
+ GimpUncancelableWaitable *uncancelable_waitable =
+ GIMP_UNCANCELABLE_WAITABLE (cancelable);
+
+ g_clear_object (&uncancelable_waitable->waitable);
+}
+
+
+/* public functions */
+
+
+GimpWaitable *
+gimp_trivially_cancelable_waitable_new (GimpWaitable *waitable)
+{
+ GimpUncancelableWaitable *uncancelable_waitable;
+
+ g_return_val_if_fail (GIMP_IS_WAITABLE (waitable), NULL);
+
+ uncancelable_waitable = g_object_new (GIMP_TYPE_TRIVIALLY_CANCELABLE_WAITABLE,
+ NULL);
+
+ uncancelable_waitable->waitable = g_object_ref (waitable);
+
+ return GIMP_WAITABLE (uncancelable_waitable);
+}
diff --git a/app/core/gimptriviallycancelablewaitable.h b/app/core/gimptriviallycancelablewaitable.h
new file mode 100644
index 0000000..bb09a52
--- /dev/null
+++ b/app/core/gimptriviallycancelablewaitable.h
@@ -0,0 +1,57 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimptriviallycancelablewaitable.h
+ * Copyright (C) 2018 Ell
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_TRIVIALLY_CANCELABLE_WAITABLE_H__
+#define __GIMP_TRIVIALLY_CANCELABLE_WAITABLE_H__
+
+
+#include "gimpuncancelablewaitable.h"
+
+
+#define GIMP_TYPE_TRIVIALLY_CANCELABLE_WAITABLE (gimp_trivially_cancelable_waitable_get_type ())
+#define GIMP_TRIVIALLY_CANCELABLE_WAITABLE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_TRIVIALLY_CANCELABLE_WAITABLE, GimpTriviallyCancelableWaitable))
+#define GIMP_TRIVIALLY_CANCELABLE_WAITABLE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_TRIVIALLY_CANCELABLE_WAITABLE, GimpTriviallyCancelableWaitableClass))
+#define GIMP_IS_TRIVIALLY_CANCELABLE_WAITABLE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_TRIVIALLY_CANCELABLE_WAITABLE))
+#define GIMP_IS_TRIVIALLY_CANCELABLE_WAITABLE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_TRIVIALLY_CANCELABLE_WAITABLE))
+#define GIMP_TRIVIALLY_CANCELABLE_WAITABLE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_TRIVIALLY_CANCELABLE_WAITABLE, GimpTriviallyCancelableWaitableClass))
+
+
+typedef struct _GimpTriviallyCancelableWaitablePrivate GimpTriviallyCancelableWaitablePrivate;
+typedef struct _GimpTriviallyCancelableWaitableClass GimpTriviallyCancelableWaitableClass;
+
+struct _GimpTriviallyCancelableWaitable
+{
+ GimpUncancelableWaitable parent_instance;
+
+ GimpTriviallyCancelableWaitablePrivate *priv;
+};
+
+struct _GimpTriviallyCancelableWaitableClass
+{
+ GimpUncancelableWaitableClass parent_class;
+};
+
+
+GType gimp_trivially_cancelable_waitable_get_type (void) G_GNUC_CONST;
+
+GimpWaitable * gimp_trivially_cancelable_waitable_new (GimpWaitable *waitable);
+
+
+#endif /* __GIMP_TRIVIALLY_CANCELABLE_WAITABLE_H__ */
diff --git a/app/core/gimpuncancelablewaitable.c b/app/core/gimpuncancelablewaitable.c
new file mode 100644
index 0000000..596efed
--- /dev/null
+++ b/app/core/gimpuncancelablewaitable.c
@@ -0,0 +1,137 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpuncancelablewaitable.c
+ * Copyright (C) 2018 Ell
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "core-types.h"
+
+#include "gimpuncancelablewaitable.h"
+#include "gimpwaitable.h"
+
+
+/* local function prototypes */
+
+static void gimp_uncancelable_waitable_waitable_iface_init (GimpWaitableInterface *iface);
+
+static void gimp_uncancelable_waitable_finalize (GObject *object);
+
+static void gimp_uncancelable_waitable_wait (GimpWaitable *waitable);
+static gboolean gimp_uncancelable_waitable_try_wait (GimpWaitable *waitable);
+static gboolean gimp_uncancelable_waitable_wait_until (GimpWaitable *waitable,
+ gint64 end_time);
+
+
+G_DEFINE_TYPE_WITH_CODE (GimpUncancelableWaitable, gimp_uncancelable_waitable, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (GIMP_TYPE_WAITABLE,
+ gimp_uncancelable_waitable_waitable_iface_init))
+
+#define parent_class gimp_uncancelable_waitable_parent_class
+
+
+/* private functions */
+
+
+static void
+gimp_uncancelable_waitable_class_init (GimpUncancelableWaitableClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = gimp_uncancelable_waitable_finalize;
+}
+
+static void
+gimp_uncancelable_waitable_waitable_iface_init (GimpWaitableInterface *iface)
+{
+ iface->wait = gimp_uncancelable_waitable_wait;
+ iface->try_wait = gimp_uncancelable_waitable_try_wait;
+ iface->wait_until = gimp_uncancelable_waitable_wait_until;
+}
+
+static void
+gimp_uncancelable_waitable_init (GimpUncancelableWaitable *uncancelable_waitable)
+{
+}
+
+static void
+gimp_uncancelable_waitable_finalize (GObject *object)
+{
+ GimpUncancelableWaitable *uncancelable_waitable =
+ GIMP_UNCANCELABLE_WAITABLE (object);
+
+ g_clear_object (&uncancelable_waitable->waitable);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gimp_uncancelable_waitable_wait (GimpWaitable *waitable)
+{
+ GimpUncancelableWaitable *uncancelable_waitable =
+ GIMP_UNCANCELABLE_WAITABLE (waitable);
+
+ if (uncancelable_waitable->waitable)
+ gimp_waitable_wait (uncancelable_waitable->waitable);
+}
+
+static gboolean
+gimp_uncancelable_waitable_try_wait (GimpWaitable *waitable)
+{
+ GimpUncancelableWaitable *uncancelable_waitable =
+ GIMP_UNCANCELABLE_WAITABLE (waitable);
+
+ if (uncancelable_waitable->waitable)
+ return gimp_waitable_try_wait (uncancelable_waitable->waitable);
+ else
+ return TRUE;
+}
+
+static gboolean
+gimp_uncancelable_waitable_wait_until (GimpWaitable *waitable,
+ gint64 end_time)
+{
+ GimpUncancelableWaitable *uncancelable_waitable =
+ GIMP_UNCANCELABLE_WAITABLE (waitable);
+
+ if (uncancelable_waitable->waitable)
+ return gimp_waitable_wait_until (uncancelable_waitable->waitable, end_time);
+ else
+ return TRUE;
+}
+
+
+/* public functions */
+
+
+GimpWaitable *
+gimp_uncancelable_waitable_new (GimpWaitable *waitable)
+{
+ GimpUncancelableWaitable *uncancelable_waitable;
+
+ g_return_val_if_fail (GIMP_IS_WAITABLE (waitable), NULL);
+
+ uncancelable_waitable = g_object_new (GIMP_TYPE_UNCANCELABLE_WAITABLE, NULL);
+
+ uncancelable_waitable->waitable = g_object_ref (waitable);
+
+ return GIMP_WAITABLE (uncancelable_waitable);
+}
diff --git a/app/core/gimpuncancelablewaitable.h b/app/core/gimpuncancelablewaitable.h
new file mode 100644
index 0000000..90b26ff
--- /dev/null
+++ b/app/core/gimpuncancelablewaitable.h
@@ -0,0 +1,54 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpuncancelablewaitable.h
+ * Copyright (C) 2018 Ell
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_UNCANCELABLE_WAITABLE_H__
+#define __GIMP_UNCANCELABLE_WAITABLE_H__
+
+
+#define GIMP_TYPE_UNCANCELABLE_WAITABLE (gimp_uncancelable_waitable_get_type ())
+#define GIMP_UNCANCELABLE_WAITABLE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_UNCANCELABLE_WAITABLE, GimpUncancelableWaitable))
+#define GIMP_UNCANCELABLE_WAITABLE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_UNCANCELABLE_WAITABLE, GimpUncancelableWaitableClass))
+#define GIMP_IS_UNCANCELABLE_WAITABLE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_UNCANCELABLE_WAITABLE))
+#define GIMP_IS_UNCANCELABLE_WAITABLE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_UNCANCELABLE_WAITABLE))
+#define GIMP_UNCANCELABLE_WAITABLE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_UNCANCELABLE_WAITABLE, GimpUncancelableWaitableClass))
+
+
+typedef struct _GimpUncancelableWaitablePrivate GimpUncancelableWaitablePrivate;
+typedef struct _GimpUncancelableWaitableClass GimpUncancelableWaitableClass;
+
+struct _GimpUncancelableWaitable
+{
+ GObject parent_instance;
+
+ GimpWaitable *waitable;
+};
+
+struct _GimpUncancelableWaitableClass
+{
+ GObjectClass parent_class;
+};
+
+
+GType gimp_uncancelable_waitable_get_type (void) G_GNUC_CONST;
+
+GimpWaitable * gimp_uncancelable_waitable_new (GimpWaitable *waitable);
+
+
+#endif /* __GIMP_UNCANCELABLE_WAITABLE_H__ */
diff --git a/app/core/gimpundo.c b/app/core/gimpundo.c
new file mode 100644
index 0000000..68bbae0
--- /dev/null
+++ b/app/core/gimpundo.c
@@ -0,0 +1,585 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <time.h>
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpbase/gimpbase.h"
+
+#include "core-types.h"
+
+#include "config/gimpcoreconfig.h"
+
+#include "gimp.h"
+#include "gimpcontext.h"
+#include "gimpimage.h"
+#include "gimpimage-undo.h"
+#include "gimpmarshal.h"
+#include "gimptempbuf.h"
+#include "gimpundo.h"
+#include "gimpundostack.h"
+
+#include "gimp-priorities.h"
+
+#include "gimp-intl.h"
+
+
+enum
+{
+ POP,
+ FREE,
+ LAST_SIGNAL
+};
+
+enum
+{
+ PROP_0,
+ PROP_IMAGE,
+ PROP_TIME,
+ PROP_UNDO_TYPE,
+ PROP_DIRTY_MASK
+};
+
+
+static void gimp_undo_constructed (GObject *object);
+static void gimp_undo_finalize (GObject *object);
+static void gimp_undo_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_undo_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static gint64 gimp_undo_get_memsize (GimpObject *object,
+ gint64 *gui_size);
+
+static gboolean gimp_undo_get_popup_size (GimpViewable *viewable,
+ gint width,
+ gint height,
+ gboolean dot_for_dot,
+ gint *popup_width,
+ gint *popup_height);
+static GimpTempBuf * gimp_undo_get_new_preview (GimpViewable *viewable,
+ GimpContext *context,
+ gint width,
+ gint height);
+
+static void gimp_undo_real_pop (GimpUndo *undo,
+ GimpUndoMode undo_mode,
+ GimpUndoAccumulator *accum);
+static void gimp_undo_real_free (GimpUndo *undo,
+ GimpUndoMode undo_mode);
+
+static gboolean gimp_undo_create_preview_idle (gpointer data);
+static void gimp_undo_create_preview_private (GimpUndo *undo,
+ GimpContext *context);
+
+
+G_DEFINE_TYPE (GimpUndo, gimp_undo, GIMP_TYPE_VIEWABLE)
+
+#define parent_class gimp_undo_parent_class
+
+static guint undo_signals[LAST_SIGNAL] = { 0 };
+
+
+static void
+gimp_undo_class_init (GimpUndoClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpObjectClass *gimp_object_class = GIMP_OBJECT_CLASS (klass);
+ GimpViewableClass *viewable_class = GIMP_VIEWABLE_CLASS (klass);
+
+ undo_signals[POP] =
+ g_signal_new ("pop",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpUndoClass, pop),
+ NULL, NULL,
+ gimp_marshal_VOID__ENUM_POINTER,
+ G_TYPE_NONE, 2,
+ GIMP_TYPE_UNDO_MODE,
+ G_TYPE_POINTER);
+
+ undo_signals[FREE] =
+ g_signal_new ("free",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpUndoClass, free),
+ NULL, NULL,
+ gimp_marshal_VOID__ENUM,
+ G_TYPE_NONE, 1,
+ GIMP_TYPE_UNDO_MODE);
+
+ object_class->constructed = gimp_undo_constructed;
+ object_class->finalize = gimp_undo_finalize;
+ object_class->set_property = gimp_undo_set_property;
+ object_class->get_property = gimp_undo_get_property;
+
+ gimp_object_class->get_memsize = gimp_undo_get_memsize;
+
+ viewable_class->default_icon_name = "edit-undo";
+ viewable_class->get_popup_size = gimp_undo_get_popup_size;
+ viewable_class->get_new_preview = gimp_undo_get_new_preview;
+
+ klass->pop = gimp_undo_real_pop;
+ klass->free = gimp_undo_real_free;
+
+ g_object_class_install_property (object_class, PROP_IMAGE,
+ g_param_spec_object ("image", NULL, NULL,
+ GIMP_TYPE_IMAGE,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+
+ g_object_class_install_property (object_class, PROP_TIME,
+ g_param_spec_uint ("time", NULL, NULL,
+ 0, G_MAXUINT, 0,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_UNDO_TYPE,
+ g_param_spec_enum ("undo-type", NULL, NULL,
+ GIMP_TYPE_UNDO_TYPE,
+ GIMP_UNDO_GROUP_NONE,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+
+ g_object_class_install_property (object_class, PROP_DIRTY_MASK,
+ g_param_spec_flags ("dirty-mask",
+ NULL, NULL,
+ GIMP_TYPE_DIRTY_MASK,
+ GIMP_DIRTY_NONE,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+}
+
+static void
+gimp_undo_init (GimpUndo *undo)
+{
+ undo->time = time (NULL);
+}
+
+static void
+gimp_undo_constructed (GObject *object)
+{
+ GimpUndo *undo = GIMP_UNDO (object);
+
+ G_OBJECT_CLASS (parent_class)->constructed (object);
+
+ gimp_assert (GIMP_IS_IMAGE (undo->image));
+}
+
+static void
+gimp_undo_finalize (GObject *object)
+{
+ GimpUndo *undo = GIMP_UNDO (object);
+
+ if (undo->preview_idle_id)
+ {
+ g_source_remove (undo->preview_idle_id);
+ undo->preview_idle_id = 0;
+ }
+
+ g_clear_pointer (&undo->preview, gimp_temp_buf_unref);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gimp_undo_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpUndo *undo = GIMP_UNDO (object);
+
+ switch (property_id)
+ {
+ case PROP_IMAGE:
+ /* don't ref */
+ undo->image = g_value_get_object (value);
+ break;
+ case PROP_TIME:
+ undo->time = g_value_get_uint (value);
+ break;
+ case PROP_UNDO_TYPE:
+ undo->undo_type = g_value_get_enum (value);
+ break;
+ case PROP_DIRTY_MASK:
+ undo->dirty_mask = g_value_get_flags (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_undo_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpUndo *undo = GIMP_UNDO (object);
+
+ switch (property_id)
+ {
+ case PROP_IMAGE:
+ g_value_set_object (value, undo->image);
+ break;
+ case PROP_TIME:
+ g_value_set_uint (value, undo->time);
+ break;
+ case PROP_UNDO_TYPE:
+ g_value_set_enum (value, undo->undo_type);
+ break;
+ case PROP_DIRTY_MASK:
+ g_value_set_flags (value, undo->dirty_mask);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static gint64
+gimp_undo_get_memsize (GimpObject *object,
+ gint64 *gui_size)
+{
+ GimpUndo *undo = GIMP_UNDO (object);
+ gint64 memsize = 0;
+
+ *gui_size += gimp_temp_buf_get_memsize (undo->preview);
+
+ return memsize + GIMP_OBJECT_CLASS (parent_class)->get_memsize (object,
+ gui_size);
+}
+
+static gboolean
+gimp_undo_get_popup_size (GimpViewable *viewable,
+ gint width,
+ gint height,
+ gboolean dot_for_dot,
+ gint *popup_width,
+ gint *popup_height)
+{
+ GimpUndo *undo = GIMP_UNDO (viewable);
+
+ if (undo->preview &&
+ (gimp_temp_buf_get_width (undo->preview) > width ||
+ gimp_temp_buf_get_height (undo->preview) > height))
+ {
+ *popup_width = gimp_temp_buf_get_width (undo->preview);
+ *popup_height = gimp_temp_buf_get_height (undo->preview);
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static GimpTempBuf *
+gimp_undo_get_new_preview (GimpViewable *viewable,
+ GimpContext *context,
+ gint width,
+ gint height)
+{
+ GimpUndo *undo = GIMP_UNDO (viewable);
+
+ if (undo->preview)
+ {
+ gint preview_width;
+ gint preview_height;
+
+ gimp_viewable_calc_preview_size (gimp_temp_buf_get_width (undo->preview),
+ gimp_temp_buf_get_height (undo->preview),
+ width,
+ height,
+ TRUE, 1.0, 1.0,
+ &preview_width,
+ &preview_height,
+ NULL);
+
+ if (preview_width < gimp_temp_buf_get_width (undo->preview) &&
+ preview_height < gimp_temp_buf_get_height (undo->preview))
+ {
+ return gimp_temp_buf_scale (undo->preview,
+ preview_width, preview_height);
+ }
+
+ return gimp_temp_buf_copy (undo->preview);
+ }
+
+ return NULL;
+}
+
+static void
+gimp_undo_real_pop (GimpUndo *undo,
+ GimpUndoMode undo_mode,
+ GimpUndoAccumulator *accum)
+{
+}
+
+static void
+gimp_undo_real_free (GimpUndo *undo,
+ GimpUndoMode undo_mode)
+{
+}
+
+void
+gimp_undo_pop (GimpUndo *undo,
+ GimpUndoMode undo_mode,
+ GimpUndoAccumulator *accum)
+{
+ g_return_if_fail (GIMP_IS_UNDO (undo));
+ g_return_if_fail (accum != NULL);
+
+ if (undo->dirty_mask != GIMP_DIRTY_NONE)
+ {
+ switch (undo_mode)
+ {
+ case GIMP_UNDO_MODE_UNDO:
+ gimp_image_clean (undo->image, undo->dirty_mask);
+ break;
+
+ case GIMP_UNDO_MODE_REDO:
+ gimp_image_dirty (undo->image, undo->dirty_mask);
+ break;
+ }
+ }
+
+ g_signal_emit (undo, undo_signals[POP], 0, undo_mode, accum);
+}
+
+void
+gimp_undo_free (GimpUndo *undo,
+ GimpUndoMode undo_mode)
+{
+ g_return_if_fail (GIMP_IS_UNDO (undo));
+
+ g_signal_emit (undo, undo_signals[FREE], 0, undo_mode);
+}
+
+typedef struct _GimpUndoIdle GimpUndoIdle;
+
+struct _GimpUndoIdle
+{
+ GimpUndo *undo;
+ GimpContext *context;
+};
+
+static void
+gimp_undo_idle_free (GimpUndoIdle *idle)
+{
+ if (idle->context)
+ g_object_unref (idle->context);
+
+ g_slice_free (GimpUndoIdle, idle);
+}
+
+void
+gimp_undo_create_preview (GimpUndo *undo,
+ GimpContext *context,
+ gboolean create_now)
+{
+ g_return_if_fail (GIMP_IS_UNDO (undo));
+ g_return_if_fail (context == NULL || GIMP_IS_CONTEXT (context));
+
+ if (undo->preview || undo->preview_idle_id)
+ return;
+
+ if (create_now)
+ {
+ gimp_undo_create_preview_private (undo, context);
+ }
+ else
+ {
+ GimpUndoIdle *idle = g_slice_new0 (GimpUndoIdle);
+
+ idle->undo = undo;
+
+ if (context)
+ idle->context = g_object_ref (context);
+
+ undo->preview_idle_id =
+ g_idle_add_full (GIMP_PRIORITY_VIEWABLE_IDLE,
+ gimp_undo_create_preview_idle, idle,
+ (GDestroyNotify) gimp_undo_idle_free);
+ }
+}
+
+static gboolean
+gimp_undo_create_preview_idle (gpointer data)
+{
+ GimpUndoIdle *idle = data;
+ GimpUndoStack *stack = gimp_image_get_undo_stack (idle->undo->image);
+
+ if (idle->undo == gimp_undo_stack_peek (stack))
+ {
+ gimp_undo_create_preview_private (idle->undo, idle->context);
+ }
+
+ idle->undo->preview_idle_id = 0;
+
+ return FALSE;
+}
+
+static void
+gimp_undo_create_preview_private (GimpUndo *undo,
+ GimpContext *context)
+{
+ GimpImage *image = undo->image;
+ GimpViewable *preview_viewable;
+ GimpViewSize preview_size;
+ gint width;
+ gint height;
+
+ switch (undo->undo_type)
+ {
+ case GIMP_UNDO_GROUP_IMAGE_QUICK_MASK:
+ case GIMP_UNDO_GROUP_MASK:
+ case GIMP_UNDO_MASK:
+ preview_viewable = GIMP_VIEWABLE (gimp_image_get_mask (image));
+ break;
+
+ default:
+ preview_viewable = GIMP_VIEWABLE (image);
+ break;
+ }
+
+ preview_size = image->gimp->config->undo_preview_size;
+
+ if (gimp_image_get_width (image) <= preview_size &&
+ gimp_image_get_height (image) <= preview_size)
+ {
+ width = gimp_image_get_width (image);
+ height = gimp_image_get_height (image);
+ }
+ else
+ {
+ if (gimp_image_get_width (image) > gimp_image_get_height (image))
+ {
+ width = preview_size;
+ height = MAX (1, (gimp_image_get_height (image) * preview_size /
+ gimp_image_get_width (image)));
+ }
+ else
+ {
+ height = preview_size;
+ width = MAX (1, (gimp_image_get_width (image) * preview_size /
+ gimp_image_get_height (image)));
+ }
+ }
+
+ undo->preview = gimp_viewable_get_new_preview (preview_viewable, context,
+ width, height);
+
+ gimp_viewable_invalidate_preview (GIMP_VIEWABLE (undo));
+}
+
+void
+gimp_undo_refresh_preview (GimpUndo *undo,
+ GimpContext *context)
+{
+ g_return_if_fail (GIMP_IS_UNDO (undo));
+ g_return_if_fail (context == NULL || GIMP_IS_CONTEXT (context));
+
+ if (undo->preview_idle_id)
+ return;
+
+ if (undo->preview)
+ {
+ g_clear_pointer (&undo->preview, gimp_temp_buf_unref);
+ gimp_undo_create_preview (undo, context, FALSE);
+ }
+}
+
+const gchar *
+gimp_undo_type_to_name (GimpUndoType type)
+{
+ const gchar *desc;
+
+ if (gimp_enum_get_value (GIMP_TYPE_UNDO_TYPE, type, NULL, NULL, &desc, NULL))
+ return desc;
+ else
+ return "";
+}
+
+gboolean
+gimp_undo_is_weak (GimpUndo *undo)
+{
+ if (! undo)
+ return FALSE;
+
+ switch (undo->undo_type)
+ {
+ case GIMP_UNDO_GROUP_ITEM_VISIBILITY:
+ case GIMP_UNDO_GROUP_ITEM_PROPERTIES:
+ case GIMP_UNDO_GROUP_LAYER_APPLY_MASK:
+ case GIMP_UNDO_ITEM_VISIBILITY:
+ case GIMP_UNDO_LAYER_MODE:
+ case GIMP_UNDO_LAYER_OPACITY:
+ case GIMP_UNDO_LAYER_MASK_APPLY:
+ case GIMP_UNDO_LAYER_MASK_SHOW:
+ return TRUE;
+ break;
+
+ default:
+ break;
+ }
+
+ return FALSE;
+}
+
+/**
+ * gimp_undo_get_age:
+ * @undo:
+ *
+ * Return value: the time in seconds since this undo item was created
+ */
+gint
+gimp_undo_get_age (GimpUndo *undo)
+{
+ guint now = time (NULL);
+
+ g_return_val_if_fail (GIMP_IS_UNDO (undo), 0);
+ g_return_val_if_fail (now >= undo->time, 0);
+
+ return now - undo->time;
+}
+
+/**
+ * gimp_undo_reset_age:
+ * @undo:
+ *
+ * Changes the creation time of this undo item to the current time.
+ */
+void
+gimp_undo_reset_age (GimpUndo *undo)
+{
+ g_return_if_fail (GIMP_IS_UNDO (undo));
+
+ undo->time = time (NULL);
+
+ g_object_notify (G_OBJECT (undo), "time");
+}
diff --git a/app/core/gimpundo.h b/app/core/gimpundo.h
new file mode 100644
index 0000000..03d03c2
--- /dev/null
+++ b/app/core/gimpundo.h
@@ -0,0 +1,99 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_UNDO_H__
+#define __GIMP_UNDO_H__
+
+
+#include "gimpviewable.h"
+
+
+struct _GimpUndoAccumulator
+{
+ gboolean mode_changed;
+ gboolean precision_changed;
+
+ gboolean size_changed;
+ gint previous_origin_x;
+ gint previous_origin_y;
+ gint previous_width;
+ gint previous_height;
+
+ gboolean resolution_changed;
+
+ gboolean unit_changed;
+};
+
+
+#define GIMP_TYPE_UNDO (gimp_undo_get_type ())
+#define GIMP_UNDO(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_UNDO, GimpUndo))
+#define GIMP_UNDO_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_UNDO, GimpUndoClass))
+#define GIMP_IS_UNDO(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_UNDO))
+#define GIMP_IS_UNDO_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_UNDO))
+#define GIMP_UNDO_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_UNDO, GimpUndoClass))
+
+
+typedef struct _GimpUndoClass GimpUndoClass;
+
+struct _GimpUndo
+{
+ GimpViewable parent_instance;
+
+ GimpImage *image; /* the image this undo is part of */
+ guint time; /* time of undo step construction */
+
+ GimpUndoType undo_type; /* undo type */
+ GimpDirtyMask dirty_mask; /* affected parts of the image */
+
+ GimpTempBuf *preview;
+ guint preview_idle_id;
+};
+
+struct _GimpUndoClass
+{
+ GimpViewableClass parent_class;
+
+ void (* pop) (GimpUndo *undo,
+ GimpUndoMode undo_mode,
+ GimpUndoAccumulator *accum);
+ void (* free) (GimpUndo *undo,
+ GimpUndoMode undo_mode);
+};
+
+
+GType gimp_undo_get_type (void) G_GNUC_CONST;
+
+void gimp_undo_pop (GimpUndo *undo,
+ GimpUndoMode undo_mode,
+ GimpUndoAccumulator *accum);
+void gimp_undo_free (GimpUndo *undo,
+ GimpUndoMode undo_mode);
+
+void gimp_undo_create_preview (GimpUndo *undo,
+ GimpContext *context,
+ gboolean create_now);
+void gimp_undo_refresh_preview (GimpUndo *undo,
+ GimpContext *context);
+
+const gchar * gimp_undo_type_to_name (GimpUndoType type);
+
+gboolean gimp_undo_is_weak (GimpUndo *undo);
+gint gimp_undo_get_age (GimpUndo *undo);
+void gimp_undo_reset_age (GimpUndo *undo);
+
+
+#endif /* __GIMP_UNDO_H__ */
diff --git a/app/core/gimpundostack.c b/app/core/gimpundostack.c
new file mode 100644
index 0000000..666ed9b
--- /dev/null
+++ b/app/core/gimpundostack.c
@@ -0,0 +1,208 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "core-types.h"
+
+#include "gimpimage.h"
+#include "gimplist.h"
+#include "gimpundo.h"
+#include "gimpundostack.h"
+
+
+static void gimp_undo_stack_finalize (GObject *object);
+
+static gint64 gimp_undo_stack_get_memsize (GimpObject *object,
+ gint64 *gui_size);
+
+static void gimp_undo_stack_pop (GimpUndo *undo,
+ GimpUndoMode undo_mode,
+ GimpUndoAccumulator *accum);
+static void gimp_undo_stack_free (GimpUndo *undo,
+ GimpUndoMode undo_mode);
+
+
+G_DEFINE_TYPE (GimpUndoStack, gimp_undo_stack, GIMP_TYPE_UNDO)
+
+#define parent_class gimp_undo_stack_parent_class
+
+
+static void
+gimp_undo_stack_class_init (GimpUndoStackClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpObjectClass *gimp_object_class = GIMP_OBJECT_CLASS (klass);
+ GimpUndoClass *undo_class = GIMP_UNDO_CLASS (klass);
+
+ object_class->finalize = gimp_undo_stack_finalize;
+
+ gimp_object_class->get_memsize = gimp_undo_stack_get_memsize;
+
+ undo_class->pop = gimp_undo_stack_pop;
+ undo_class->free = gimp_undo_stack_free;
+}
+
+static void
+gimp_undo_stack_init (GimpUndoStack *stack)
+{
+ stack->undos = gimp_list_new (GIMP_TYPE_UNDO, FALSE);
+}
+
+static void
+gimp_undo_stack_finalize (GObject *object)
+{
+ GimpUndoStack *stack = GIMP_UNDO_STACK (object);
+
+ g_clear_object (&stack->undos);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static gint64
+gimp_undo_stack_get_memsize (GimpObject *object,
+ gint64 *gui_size)
+{
+ GimpUndoStack *stack = GIMP_UNDO_STACK (object);
+ gint64 memsize = 0;
+
+ memsize += gimp_object_get_memsize (GIMP_OBJECT (stack->undos), gui_size);
+
+ return memsize + GIMP_OBJECT_CLASS (parent_class)->get_memsize (object,
+ gui_size);
+}
+
+static void
+gimp_undo_stack_pop (GimpUndo *undo,
+ GimpUndoMode undo_mode,
+ GimpUndoAccumulator *accum)
+{
+ GimpUndoStack *stack = GIMP_UNDO_STACK (undo);
+ GList *list;
+
+ for (list = GIMP_LIST (stack->undos)->queue->head;
+ list;
+ list = g_list_next (list))
+ {
+ GimpUndo *child = list->data;
+
+ gimp_undo_pop (child, undo_mode, accum);
+ }
+}
+
+static void
+gimp_undo_stack_free (GimpUndo *undo,
+ GimpUndoMode undo_mode)
+{
+ GimpUndoStack *stack = GIMP_UNDO_STACK (undo);
+ GList *list;
+
+ for (list = GIMP_LIST (stack->undos)->queue->head;
+ list;
+ list = g_list_next (list))
+ {
+ GimpUndo *child = list->data;
+
+ gimp_undo_free (child, undo_mode);
+ g_object_unref (child);
+ }
+
+ gimp_container_clear (stack->undos);
+}
+
+GimpUndoStack *
+gimp_undo_stack_new (GimpImage *image)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+
+ return g_object_new (GIMP_TYPE_UNDO_STACK,
+ "image", image,
+ NULL);
+}
+
+void
+gimp_undo_stack_push_undo (GimpUndoStack *stack,
+ GimpUndo *undo)
+{
+ g_return_if_fail (GIMP_IS_UNDO_STACK (stack));
+ g_return_if_fail (GIMP_IS_UNDO (undo));
+
+ gimp_container_add (stack->undos, GIMP_OBJECT (undo));
+}
+
+GimpUndo *
+gimp_undo_stack_pop_undo (GimpUndoStack *stack,
+ GimpUndoMode undo_mode,
+ GimpUndoAccumulator *accum)
+{
+ GimpUndo *undo;
+
+ g_return_val_if_fail (GIMP_IS_UNDO_STACK (stack), NULL);
+ g_return_val_if_fail (accum != NULL, NULL);
+
+ undo = GIMP_UNDO (gimp_container_get_first_child (stack->undos));
+
+ if (undo)
+ {
+ gimp_container_remove (stack->undos, GIMP_OBJECT (undo));
+ gimp_undo_pop (undo, undo_mode, accum);
+
+ return undo;
+ }
+
+ return NULL;
+}
+
+GimpUndo *
+gimp_undo_stack_free_bottom (GimpUndoStack *stack,
+ GimpUndoMode undo_mode)
+{
+ GimpUndo *undo;
+
+ g_return_val_if_fail (GIMP_IS_UNDO_STACK (stack), NULL);
+
+ undo = GIMP_UNDO (gimp_container_get_last_child (stack->undos));
+
+ if (undo)
+ {
+ gimp_container_remove (stack->undos, GIMP_OBJECT (undo));
+ gimp_undo_free (undo, undo_mode);
+
+ return undo;
+ }
+
+ return NULL;
+}
+
+GimpUndo *
+gimp_undo_stack_peek (GimpUndoStack *stack)
+{
+ g_return_val_if_fail (GIMP_IS_UNDO_STACK (stack), NULL);
+
+ return GIMP_UNDO (gimp_container_get_first_child (stack->undos));
+}
+
+gint
+gimp_undo_stack_get_depth (GimpUndoStack *stack)
+{
+ g_return_val_if_fail (GIMP_IS_UNDO_STACK (stack), 0);
+
+ return gimp_container_get_n_children (stack->undos);
+}
diff --git a/app/core/gimpundostack.h b/app/core/gimpundostack.h
new file mode 100644
index 0000000..8432df0
--- /dev/null
+++ b/app/core/gimpundostack.h
@@ -0,0 +1,64 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_UNDO_STACK_H__
+#define __GIMP_UNDO_STACK_H__
+
+
+#include "gimpundo.h"
+
+
+#define GIMP_TYPE_UNDO_STACK (gimp_undo_stack_get_type ())
+#define GIMP_UNDO_STACK(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_UNDO_STACK, GimpUndoStack))
+#define GIMP_UNDO_STACK_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_UNDO_STACK, GimpUndoStackClass))
+#define GIMP_IS_UNDO_STACK(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_UNDO_STACK))
+#define GIMP_IS_UNDO_STACK_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_UNDO_STACK))
+#define GIMP_UNDO_STACK_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_UNDO_STACK, GimpUndoStackClass))
+
+
+typedef struct _GimpUndoStackClass GimpUndoStackClass;
+
+struct _GimpUndoStack
+{
+ GimpUndo parent_instance;
+
+ GimpContainer *undos;
+};
+
+struct _GimpUndoStackClass
+{
+ GimpUndoClass parent_class;
+};
+
+
+GType gimp_undo_stack_get_type (void) G_GNUC_CONST;
+
+GimpUndoStack * gimp_undo_stack_new (GimpImage *image);
+
+void gimp_undo_stack_push_undo (GimpUndoStack *stack,
+ GimpUndo *undo);
+GimpUndo * gimp_undo_stack_pop_undo (GimpUndoStack *stack,
+ GimpUndoMode undo_mode,
+ GimpUndoAccumulator *accum);
+
+GimpUndo * gimp_undo_stack_free_bottom (GimpUndoStack *stack,
+ GimpUndoMode undo_mode);
+GimpUndo * gimp_undo_stack_peek (GimpUndoStack *stack);
+gint gimp_undo_stack_get_depth (GimpUndoStack *stack);
+
+
+#endif /* __GIMP_UNDO_STACK_H__ */
diff --git a/app/core/gimpunit.c b/app/core/gimpunit.c
new file mode 100644
index 0000000..3fad305
--- /dev/null
+++ b/app/core/gimpunit.c
@@ -0,0 +1,305 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-1999 Spencer Kimball and Peter Mattis
+ *
+ * gimpunit.c
+ * Copyright (C) 1999-2000 Michael Natterer <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+/* This file contains the definition of the size unit objects. The
+ * factor of the units is relative to inches (which have a factor of 1).
+ */
+
+#include "config.h"
+
+#include <gio/gio.h>
+
+#include "libgimpbase/gimpbase.h"
+
+#include "core-types.h"
+
+#include "gimp.h"
+#include "gimpunit.h"
+
+#include "gimp-intl.h"
+
+
+/* internal structures */
+
+typedef struct
+{
+ gboolean delete_on_exit;
+ gdouble factor;
+ gint digits;
+ gchar *identifier;
+ gchar *symbol;
+ gchar *abbreviation;
+ gchar *singular;
+ gchar *plural;
+} GimpUnitDef;
+
+
+/* these are the built-in units
+ */
+static const GimpUnitDef gimp_unit_defs[GIMP_UNIT_END] =
+{
+ /* pseudo unit */
+ { FALSE, 0.0, 0, "pixels", "px", "px",
+ NC_("unit-singular", "pixel"), NC_("unit-plural", "pixels") },
+
+ /* standard units */
+ { FALSE, 1.0, 2, "inches", "''", "in",
+ NC_("unit-singular", "inch"), NC_("unit-plural", "inches") },
+
+ { FALSE, 25.4, 1, "millimeters", "mm", "mm",
+ NC_("unit-singular", "millimeter"), NC_("unit-plural", "millimeters") },
+
+ /* professional units */
+ { FALSE, 72.0, 0, "points", "pt", "pt",
+ NC_("unit-singular", "point"), NC_("unit-plural", "points") },
+
+ { FALSE, 6.0, 1, "picas", "pc", "pc",
+ NC_("unit-singular", "pica"), NC_("unit-plural", "picas") }
+};
+
+/* not a unit at all but kept here to have the strings in one place
+ */
+static const GimpUnitDef gimp_unit_percent =
+{
+ FALSE, 0.0, 0, "percent", "%", "%",
+ NC_("singular", "percent"), NC_("plural", "percent")
+};
+
+
+/* private functions */
+
+static GimpUnitDef *
+_gimp_unit_get_user_unit (Gimp *gimp,
+ GimpUnit unit)
+{
+ return g_list_nth_data (gimp->user_units, unit - GIMP_UNIT_END);
+}
+
+
+/* public functions */
+
+gint
+_gimp_unit_get_number_of_units (Gimp *gimp)
+{
+ return GIMP_UNIT_END + gimp->n_user_units;
+}
+
+gint
+_gimp_unit_get_number_of_built_in_units (Gimp *gimp)
+{
+ return GIMP_UNIT_END;
+}
+
+GimpUnit
+_gimp_unit_new (Gimp *gimp,
+ const gchar *identifier,
+ gdouble factor,
+ gint digits,
+ const gchar *symbol,
+ const gchar *abbreviation,
+ const gchar *singular,
+ const gchar *plural)
+{
+ GimpUnitDef *user_unit = g_slice_new0 (GimpUnitDef);
+
+ user_unit->delete_on_exit = TRUE;
+ user_unit->factor = factor;
+ user_unit->digits = digits;
+ user_unit->identifier = g_strdup (identifier);
+ user_unit->symbol = g_strdup (symbol);
+ user_unit->abbreviation = g_strdup (abbreviation);
+ user_unit->singular = g_strdup (singular);
+ user_unit->plural = g_strdup (plural);
+
+ gimp->user_units = g_list_append (gimp->user_units, user_unit);
+ gimp->n_user_units++;
+
+ return GIMP_UNIT_END + gimp->n_user_units - 1;
+}
+
+gboolean
+_gimp_unit_get_deletion_flag (Gimp *gimp,
+ GimpUnit unit)
+{
+ g_return_val_if_fail (unit < (GIMP_UNIT_END + gimp->n_user_units), FALSE);
+
+ if (unit < GIMP_UNIT_END)
+ return FALSE;
+
+ return _gimp_unit_get_user_unit (gimp, unit)->delete_on_exit;
+}
+
+void
+_gimp_unit_set_deletion_flag (Gimp *gimp,
+ GimpUnit unit,
+ gboolean deletion_flag)
+{
+ g_return_if_fail ((unit >= GIMP_UNIT_END) &&
+ (unit < (GIMP_UNIT_END + gimp->n_user_units)));
+
+ _gimp_unit_get_user_unit (gimp, unit)->delete_on_exit =
+ deletion_flag ? TRUE : FALSE;
+}
+
+gdouble
+_gimp_unit_get_factor (Gimp *gimp,
+ GimpUnit unit)
+{
+ g_return_val_if_fail (unit < (GIMP_UNIT_END + gimp->n_user_units) ||
+ (unit == GIMP_UNIT_PERCENT),
+ gimp_unit_defs[GIMP_UNIT_INCH].factor);
+
+ if (unit < GIMP_UNIT_END)
+ return gimp_unit_defs[unit].factor;
+
+ if (unit == GIMP_UNIT_PERCENT)
+ return gimp_unit_percent.factor;
+
+ return _gimp_unit_get_user_unit (gimp, unit)->factor;
+}
+
+gint
+_gimp_unit_get_digits (Gimp *gimp,
+ GimpUnit unit)
+{
+ g_return_val_if_fail (unit < (GIMP_UNIT_END + gimp->n_user_units) ||
+ (unit == GIMP_UNIT_PERCENT),
+ gimp_unit_defs[GIMP_UNIT_INCH].digits);
+
+ if (unit < GIMP_UNIT_END)
+ return gimp_unit_defs[unit].digits;
+
+ if (unit == GIMP_UNIT_PERCENT)
+ return gimp_unit_percent.digits;
+
+ return _gimp_unit_get_user_unit (gimp, unit)->digits;
+}
+
+const gchar *
+_gimp_unit_get_identifier (Gimp *gimp,
+ GimpUnit unit)
+{
+ g_return_val_if_fail ((unit < (GIMP_UNIT_END + gimp->n_user_units)) ||
+ (unit == GIMP_UNIT_PERCENT),
+ gimp_unit_defs[GIMP_UNIT_INCH].identifier);
+
+ if (unit < GIMP_UNIT_END)
+ return gimp_unit_defs[unit].identifier;
+
+ if (unit == GIMP_UNIT_PERCENT)
+ return gimp_unit_percent.identifier;
+
+ return _gimp_unit_get_user_unit (gimp, unit)->identifier;
+}
+
+const gchar *
+_gimp_unit_get_symbol (Gimp *gimp,
+ GimpUnit unit)
+{
+ g_return_val_if_fail ((unit < (GIMP_UNIT_END + gimp->n_user_units)) ||
+ (unit == GIMP_UNIT_PERCENT),
+ gimp_unit_defs[GIMP_UNIT_INCH].symbol);
+
+ if (unit < GIMP_UNIT_END)
+ return gimp_unit_defs[unit].symbol;
+
+ if (unit == GIMP_UNIT_PERCENT)
+ return gimp_unit_percent.symbol;
+
+ return _gimp_unit_get_user_unit (gimp, unit)->symbol;
+}
+
+const gchar *
+_gimp_unit_get_abbreviation (Gimp *gimp,
+ GimpUnit unit)
+{
+ g_return_val_if_fail ((unit < (GIMP_UNIT_END + gimp->n_user_units)) ||
+ (unit == GIMP_UNIT_PERCENT),
+ gimp_unit_defs[GIMP_UNIT_INCH].abbreviation);
+
+ if (unit < GIMP_UNIT_END)
+ return gimp_unit_defs[unit].abbreviation;
+
+ if (unit == GIMP_UNIT_PERCENT)
+ return gimp_unit_percent.abbreviation;
+
+ return _gimp_unit_get_user_unit (gimp, unit)->abbreviation;
+}
+
+const gchar *
+_gimp_unit_get_singular (Gimp *gimp,
+ GimpUnit unit)
+{
+ g_return_val_if_fail ((unit < (GIMP_UNIT_END + gimp->n_user_units)) ||
+ (unit == GIMP_UNIT_PERCENT),
+ gimp_unit_defs[GIMP_UNIT_INCH].singular);
+
+ if (unit < GIMP_UNIT_END)
+ return g_dpgettext2 (NULL, "unit-singular", gimp_unit_defs[unit].singular);
+
+ if (unit == GIMP_UNIT_PERCENT)
+ return g_dpgettext2 (NULL, "unit-singular", gimp_unit_percent.singular);
+
+ return _gimp_unit_get_user_unit (gimp, unit)->singular;
+}
+
+const gchar *
+_gimp_unit_get_plural (Gimp *gimp,
+ GimpUnit unit)
+{
+ g_return_val_if_fail ((unit < (GIMP_UNIT_END + gimp->n_user_units)) ||
+ (unit == GIMP_UNIT_PERCENT),
+ gimp_unit_defs[GIMP_UNIT_INCH].plural);
+
+ if (unit < GIMP_UNIT_END)
+ return g_dpgettext2 (NULL, "unit-plural", gimp_unit_defs[unit].plural);
+
+ if (unit == GIMP_UNIT_PERCENT)
+ return g_dpgettext2 (NULL, "unit-plural", gimp_unit_percent.plural);
+
+ return _gimp_unit_get_user_unit (gimp, unit)->plural;
+}
+
+
+/* The sole purpose of this function is to release the allocated
+ * memory. It must only be used from gimp_units_exit().
+ */
+void
+gimp_user_units_free (Gimp *gimp)
+{
+ GList *list;
+
+ for (list = gimp->user_units; list; list = g_list_next (list))
+ {
+ GimpUnitDef *user_unit = list->data;
+
+ g_free (user_unit->identifier);
+ g_free (user_unit->symbol);
+ g_free (user_unit->abbreviation);
+ g_free (user_unit->singular);
+ g_free (user_unit->plural);
+
+ g_slice_free (GimpUnitDef, user_unit);
+ }
+
+ g_list_free (gimp->user_units);
+ gimp->user_units = NULL;
+ gimp->n_user_units = 0;
+}
diff --git a/app/core/gimpunit.h b/app/core/gimpunit.h
new file mode 100644
index 0000000..60dbc75
--- /dev/null
+++ b/app/core/gimpunit.h
@@ -0,0 +1,61 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __APP_GIMP_UNIT_H__
+#define __APP_GIMP_UNIT_H__
+
+
+gint _gimp_unit_get_number_of_units (Gimp *gimp);
+gint _gimp_unit_get_number_of_built_in_units (Gimp *gimp) G_GNUC_CONST;
+
+GimpUnit _gimp_unit_new (Gimp *gimp,
+ const gchar *identifier,
+ gdouble factor,
+ gint digits,
+ const gchar *symbol,
+ const gchar *abbreviation,
+ const gchar *singular,
+ const gchar *plural);
+
+gboolean _gimp_unit_get_deletion_flag (Gimp *gimp,
+ GimpUnit unit);
+void _gimp_unit_set_deletion_flag (Gimp *gimp,
+ GimpUnit unit,
+ gboolean deletion_flag);
+
+gdouble _gimp_unit_get_factor (Gimp *gimp,
+ GimpUnit unit);
+
+gint _gimp_unit_get_digits (Gimp *gimp,
+ GimpUnit unit);
+
+const gchar * _gimp_unit_get_identifier (Gimp *gimp,
+ GimpUnit unit);
+
+const gchar * _gimp_unit_get_symbol (Gimp *gimp,
+ GimpUnit unit);
+const gchar * _gimp_unit_get_abbreviation (Gimp *gimp,
+ GimpUnit unit);
+const gchar * _gimp_unit_get_singular (Gimp *gimp,
+ GimpUnit unit);
+const gchar * _gimp_unit_get_plural (Gimp *gimp,
+ GimpUnit unit);
+
+void gimp_user_units_free (Gimp *gimp);
+
+
+#endif /* __APP_GIMP_UNIT_H__ */
diff --git a/app/core/gimpviewable.c b/app/core/gimpviewable.c
new file mode 100644
index 0000000..5b547a2
--- /dev/null
+++ b/app/core/gimpviewable.c
@@ -0,0 +1,1430 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-1997 Spencer Kimball and Peter Mattis
+ *
+ * gimpviewable.c
+ * Copyright (C) 2001 Michael Natterer <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <cairo.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpcolor/gimpcolor.h"
+#include "libgimpmath/gimpmath.h"
+#include "libgimpconfig/gimpconfig.h"
+
+#include "core-types.h"
+
+#include "gimp-memsize.h"
+#include "gimpcontainer.h"
+#include "gimpcontext.h"
+#include "gimpmarshal.h"
+#include "gimptempbuf.h"
+#include "gimpviewable.h"
+
+#include "icons/Color/gimp-core-pixbufs.c"
+
+
+enum
+{
+ PROP_0,
+ PROP_STOCK_ID, /* compat */
+ PROP_ICON_NAME,
+ PROP_ICON_PIXBUF,
+ PROP_FROZEN
+};
+
+enum
+{
+ INVALIDATE_PREVIEW,
+ SIZE_CHANGED,
+ EXPANDED_CHANGED,
+ ANCESTRY_CHANGED,
+ LAST_SIGNAL
+};
+
+
+typedef struct _GimpViewablePrivate GimpViewablePrivate;
+
+struct _GimpViewablePrivate
+{
+ gchar *icon_name;
+ GdkPixbuf *icon_pixbuf;
+ gint freeze_count;
+ gboolean invalidate_pending;
+ gboolean size_changed_prending;
+ GimpViewable *parent;
+ gint depth;
+
+ GimpTempBuf *preview_temp_buf;
+ GdkPixbuf *preview_pixbuf;
+};
+
+#define GET_PRIVATE(viewable) ((GimpViewablePrivate *) gimp_viewable_get_instance_private ((GimpViewable *) (viewable)))
+
+
+static void gimp_viewable_config_iface_init (GimpConfigInterface *iface);
+
+static void gimp_viewable_finalize (GObject *object);
+static void gimp_viewable_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_viewable_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static gint64 gimp_viewable_get_memsize (GimpObject *object,
+ gint64 *gui_size);
+
+static void gimp_viewable_real_invalidate_preview (GimpViewable *viewable);
+static void gimp_viewable_real_ancestry_changed (GimpViewable *viewable);
+
+static GdkPixbuf * gimp_viewable_real_get_new_pixbuf (GimpViewable *viewable,
+ GimpContext *context,
+ gint width,
+ gint height);
+static void gimp_viewable_real_get_preview_size (GimpViewable *viewable,
+ gint size,
+ gboolean popup,
+ gboolean dot_for_dot,
+ gint *width,
+ gint *height);
+static gboolean gimp_viewable_real_get_popup_size (GimpViewable *viewable,
+ gint width,
+ gint height,
+ gboolean dot_for_dot,
+ gint *popup_width,
+ gint *popup_height);
+static gchar * gimp_viewable_real_get_description (GimpViewable *viewable,
+ gchar **tooltip);
+static gboolean gimp_viewable_real_is_name_editable (GimpViewable *viewable);
+static GimpContainer * gimp_viewable_real_get_children (GimpViewable *viewable);
+
+static gboolean gimp_viewable_serialize_property (GimpConfig *config,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec,
+ GimpConfigWriter *writer);
+static gboolean gimp_viewable_deserialize_property (GimpConfig *config,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec,
+ GScanner *scanner,
+ GTokenType *expected);
+
+
+G_DEFINE_TYPE_WITH_CODE (GimpViewable, gimp_viewable, GIMP_TYPE_OBJECT,
+ G_ADD_PRIVATE (GimpViewable)
+ G_IMPLEMENT_INTERFACE (GIMP_TYPE_CONFIG,
+ gimp_viewable_config_iface_init))
+
+#define parent_class gimp_viewable_parent_class
+
+static guint viewable_signals[LAST_SIGNAL] = { 0 };
+
+
+static void
+gimp_viewable_class_init (GimpViewableClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpObjectClass *gimp_object_class = GIMP_OBJECT_CLASS (klass);
+
+ viewable_signals[INVALIDATE_PREVIEW] =
+ g_signal_new ("invalidate-preview",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpViewableClass, invalidate_preview),
+ NULL, NULL,
+ gimp_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ viewable_signals[SIZE_CHANGED] =
+ g_signal_new ("size-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpViewableClass, size_changed),
+ NULL, NULL,
+ gimp_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ viewable_signals[EXPANDED_CHANGED] =
+ g_signal_new ("expanded-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpViewableClass, expanded_changed),
+ NULL, NULL,
+ gimp_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ viewable_signals[ANCESTRY_CHANGED] =
+ g_signal_new ("ancestry-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpViewableClass, ancestry_changed),
+ NULL, NULL,
+ gimp_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ object_class->finalize = gimp_viewable_finalize;
+ object_class->get_property = gimp_viewable_get_property;
+ object_class->set_property = gimp_viewable_set_property;
+
+ gimp_object_class->get_memsize = gimp_viewable_get_memsize;
+
+ klass->default_icon_name = "gimp-question";
+ klass->name_changed_signal = "name-changed";
+ klass->name_editable = FALSE;
+
+ klass->invalidate_preview = gimp_viewable_real_invalidate_preview;
+ klass->size_changed = NULL;
+ klass->expanded_changed = NULL;
+ klass->ancestry_changed = gimp_viewable_real_ancestry_changed;
+
+ klass->get_size = NULL;
+ klass->get_preview_size = gimp_viewable_real_get_preview_size;
+ klass->get_popup_size = gimp_viewable_real_get_popup_size;
+ klass->get_preview = NULL;
+ klass->get_new_preview = NULL;
+ klass->get_pixbuf = NULL;
+ klass->get_new_pixbuf = gimp_viewable_real_get_new_pixbuf;
+ klass->get_description = gimp_viewable_real_get_description;
+ klass->is_name_editable = gimp_viewable_real_is_name_editable;
+ klass->preview_freeze = NULL;
+ klass->preview_thaw = NULL;
+ klass->get_children = gimp_viewable_real_get_children;
+ klass->set_expanded = NULL;
+ klass->get_expanded = NULL;
+
+ /* compat property */
+ GIMP_CONFIG_PROP_STRING (object_class, PROP_STOCK_ID, "stock-id",
+ NULL, NULL,
+ NULL,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_STRING (object_class, PROP_ICON_NAME, "icon-name",
+ NULL, NULL,
+ NULL,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_OBJECT (object_class, PROP_ICON_PIXBUF,
+ "icon-pixbuf",
+ NULL, NULL,
+ GDK_TYPE_PIXBUF,
+ G_PARAM_CONSTRUCT |
+ GIMP_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_property (object_class, PROP_FROZEN,
+ g_param_spec_boolean ("frozen",
+ NULL, NULL,
+ FALSE,
+ GIMP_PARAM_READABLE));
+}
+
+static void
+gimp_viewable_init (GimpViewable *viewable)
+{
+}
+
+static void
+gimp_viewable_config_iface_init (GimpConfigInterface *iface)
+{
+ iface->deserialize_property = gimp_viewable_deserialize_property;
+ iface->serialize_property = gimp_viewable_serialize_property;
+}
+
+static void
+gimp_viewable_finalize (GObject *object)
+{
+ GimpViewablePrivate *private = GET_PRIVATE (object);
+
+ g_clear_pointer (&private->icon_name, g_free);
+ g_clear_object (&private->icon_pixbuf);
+ g_clear_pointer (&private->preview_temp_buf, gimp_temp_buf_unref);
+ g_clear_object (&private->preview_pixbuf);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gimp_viewable_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpViewable *viewable = GIMP_VIEWABLE (object);
+ GimpViewablePrivate *private = GET_PRIVATE (object);
+
+ switch (property_id)
+ {
+ case PROP_STOCK_ID:
+ if (! g_value_get_string (value))
+ break;
+ case PROP_ICON_NAME:
+ gimp_viewable_set_icon_name (viewable, g_value_get_string (value));
+ break;
+ case PROP_ICON_PIXBUF:
+ if (private->icon_pixbuf)
+ g_object_unref (private->icon_pixbuf);
+ private->icon_pixbuf = g_value_dup_object (value);
+ gimp_viewable_invalidate_preview (viewable);
+ break;
+ case PROP_FROZEN:
+ /* read-only, fall through */
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_viewable_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpViewable *viewable = GIMP_VIEWABLE (object);
+ GimpViewablePrivate *private = GET_PRIVATE (object);
+
+ switch (property_id)
+ {
+ case PROP_STOCK_ID:
+ case PROP_ICON_NAME:
+ g_value_set_string (value, gimp_viewable_get_icon_name (viewable));
+ break;
+ case PROP_ICON_PIXBUF:
+ g_value_set_object (value, private->icon_pixbuf);
+ break;
+ case PROP_FROZEN:
+ g_value_set_boolean (value, gimp_viewable_preview_is_frozen (viewable));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static gint64
+gimp_viewable_get_memsize (GimpObject *object,
+ gint64 *gui_size)
+{
+ GimpViewablePrivate *private = GET_PRIVATE (object);
+
+ *gui_size += gimp_temp_buf_get_memsize (private->preview_temp_buf);
+
+ if (private->preview_pixbuf)
+ {
+ *gui_size +=
+ (gimp_g_object_get_memsize (G_OBJECT (private->preview_pixbuf)) +
+ (gsize) gdk_pixbuf_get_height (private->preview_pixbuf) *
+ gdk_pixbuf_get_rowstride (private->preview_pixbuf));
+ }
+
+ return GIMP_OBJECT_CLASS (parent_class)->get_memsize (object, gui_size);
+}
+
+static void
+gimp_viewable_real_invalidate_preview (GimpViewable *viewable)
+{
+ GimpViewablePrivate *private = GET_PRIVATE (viewable);
+
+ g_clear_pointer (&private->preview_temp_buf, gimp_temp_buf_unref);
+ g_clear_object (&private->preview_pixbuf);
+}
+
+static void
+gimp_viewable_real_ancestry_changed_propagate (GimpViewable *viewable,
+ GimpViewable *parent)
+{
+ GimpViewablePrivate *private = GET_PRIVATE (viewable);
+
+ private->depth = gimp_viewable_get_depth (parent) + 1;
+
+ g_signal_emit (viewable, viewable_signals[ANCESTRY_CHANGED], 0);
+}
+
+static void
+gimp_viewable_real_ancestry_changed (GimpViewable *viewable)
+{
+ GimpContainer *children;
+
+ children = gimp_viewable_get_children (viewable);
+
+ if (children)
+ {
+ gimp_container_foreach (children,
+ (GFunc) gimp_viewable_real_ancestry_changed_propagate,
+ viewable);
+ }
+}
+
+static void
+gimp_viewable_real_get_preview_size (GimpViewable *viewable,
+ gint size,
+ gboolean popup,
+ gboolean dot_for_dot,
+ gint *width,
+ gint *height)
+{
+ *width = size;
+ *height = size;
+}
+
+static gboolean
+gimp_viewable_real_get_popup_size (GimpViewable *viewable,
+ gint width,
+ gint height,
+ gboolean dot_for_dot,
+ gint *popup_width,
+ gint *popup_height)
+{
+ gint w, h;
+
+ if (gimp_viewable_get_size (viewable, &w, &h))
+ {
+ if (w > width || h > height)
+ {
+ *popup_width = w;
+ *popup_height = h;
+
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+static GdkPixbuf *
+gimp_viewable_real_get_new_pixbuf (GimpViewable *viewable,
+ GimpContext *context,
+ gint width,
+ gint height)
+{
+ GimpViewablePrivate *private = GET_PRIVATE (viewable);
+ GdkPixbuf *pixbuf = NULL;
+ GimpTempBuf *temp_buf;
+
+ temp_buf = gimp_viewable_get_preview (viewable, context, width, height);
+
+ if (temp_buf)
+ {
+ pixbuf = gimp_temp_buf_create_pixbuf (temp_buf);
+ }
+ else if (private->icon_pixbuf)
+ {
+ pixbuf = gdk_pixbuf_scale_simple (private->icon_pixbuf,
+ width,
+ height,
+ GDK_INTERP_BILINEAR);
+ }
+
+ return pixbuf;
+}
+
+static gchar *
+gimp_viewable_real_get_description (GimpViewable *viewable,
+ gchar **tooltip)
+{
+ return g_strdup (gimp_object_get_name (viewable));
+}
+
+static gboolean
+gimp_viewable_real_is_name_editable (GimpViewable *viewable)
+{
+ return GIMP_VIEWABLE_GET_CLASS (viewable)->name_editable;
+}
+
+static GimpContainer *
+gimp_viewable_real_get_children (GimpViewable *viewable)
+{
+ return NULL;
+}
+
+static gboolean
+gimp_viewable_serialize_property (GimpConfig *config,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec,
+ GimpConfigWriter *writer)
+{
+ GimpViewablePrivate *private = GET_PRIVATE (config);
+
+ switch (property_id)
+ {
+ case PROP_STOCK_ID:
+ return TRUE;
+
+ case PROP_ICON_NAME:
+ if (private->icon_name)
+ {
+ gimp_config_writer_open (writer, pspec->name);
+ gimp_config_writer_string (writer, private->icon_name);
+ gimp_config_writer_close (writer);
+ }
+ return TRUE;
+
+ case PROP_ICON_PIXBUF:
+ {
+ GdkPixbuf *icon_pixbuf = g_value_get_object (value);
+
+ if (icon_pixbuf)
+ {
+ gchar *pixbuffer;
+ gsize pixbuffer_size;
+ GError *error = NULL;
+
+ if (gdk_pixbuf_save_to_buffer (icon_pixbuf,
+ &pixbuffer,
+ &pixbuffer_size,
+ "png", &error, NULL))
+ {
+ gchar *pixbuffer_enc;
+
+ pixbuffer_enc = g_base64_encode ((guchar *)pixbuffer,
+ pixbuffer_size);
+ gimp_config_writer_open (writer, "icon-pixbuf");
+ gimp_config_writer_string (writer, pixbuffer_enc);
+ gimp_config_writer_close (writer);
+
+ g_free (pixbuffer_enc);
+ g_free (pixbuffer);
+ }
+ }
+ }
+ return TRUE;
+
+ default:
+ break;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+gimp_viewable_deserialize_property (GimpConfig *config,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec,
+ GScanner *scanner,
+ GTokenType *expected)
+{
+ switch (property_id)
+ {
+ case PROP_ICON_PIXBUF:
+ {
+ GdkPixbuf *icon_pixbuf = NULL;
+ gchar *encoded_image;
+
+ if (! gimp_scanner_parse_string (scanner, &encoded_image))
+ {
+ *expected = G_TOKEN_STRING;
+ return TRUE;
+ }
+
+ if (encoded_image && strlen (encoded_image) > 0)
+ {
+ gsize out_len;
+ guchar *decoded_image = g_base64_decode (encoded_image, &out_len);
+
+ if (decoded_image)
+ {
+ GInputStream *stream;
+
+ stream = g_memory_input_stream_new_from_data (decoded_image,
+ out_len, NULL);
+ icon_pixbuf = gdk_pixbuf_new_from_stream (stream, NULL, NULL);
+ g_object_unref (stream);
+
+ g_free (decoded_image);
+ }
+ }
+
+ g_free (encoded_image);
+
+ g_value_take_object (value, icon_pixbuf);
+ }
+ return TRUE;
+
+ default:
+ break;
+ }
+
+ return FALSE;
+}
+
+/**
+ * gimp_viewable_invalidate_preview:
+ * @viewable: a viewable object
+ *
+ * Causes any cached preview to be marked as invalid, so that a new
+ * preview will be generated at the next attempt to display one.
+ **/
+void
+gimp_viewable_invalidate_preview (GimpViewable *viewable)
+{
+ GimpViewablePrivate *private;
+
+ g_return_if_fail (GIMP_IS_VIEWABLE (viewable));
+
+ private = GET_PRIVATE (viewable);
+
+ if (private->freeze_count == 0)
+ g_signal_emit (viewable, viewable_signals[INVALIDATE_PREVIEW], 0);
+ else
+ private->invalidate_pending = TRUE;
+}
+
+/**
+ * gimp_viewable_size_changed:
+ * @viewable: a viewable object
+ *
+ * This function sends a signal that is handled at a lower level in the
+ * object hierarchy, and provides a mechanism by which objects derived
+ * from #GimpViewable can respond to size changes.
+ **/
+void
+gimp_viewable_size_changed (GimpViewable *viewable)
+{
+ GimpViewablePrivate *private;
+
+ g_return_if_fail (GIMP_IS_VIEWABLE (viewable));
+
+ private = GET_PRIVATE (viewable);
+
+ if (private->freeze_count == 0)
+ g_signal_emit (viewable, viewable_signals[SIZE_CHANGED], 0);
+ else
+ private->size_changed_prending = TRUE;
+}
+
+/**
+ * gimp_viewable_expanded_changed:
+ * @viewable: a viewable object
+ *
+ * This function sends a signal that is handled at a lower level in the
+ * object hierarchy, and provides a mechanism by which objects derived
+ * from #GimpViewable can respond to expanded state changes.
+ **/
+void
+gimp_viewable_expanded_changed (GimpViewable *viewable)
+{
+ g_return_if_fail (GIMP_IS_VIEWABLE (viewable));
+
+ g_signal_emit (viewable, viewable_signals[EXPANDED_CHANGED], 0);
+}
+
+/**
+ * gimp_viewable_calc_preview_size:
+ * @aspect_width: unscaled width of the preview for an item.
+ * @aspect_height: unscaled height of the preview for an item.
+ * @width: maximum available width for scaled preview.
+ * @height: maximum available height for scaled preview.
+ * @dot_for_dot: if #TRUE, ignore any differences in axis resolution.
+ * @xresolution: resolution in the horizontal direction.
+ * @yresolution: resolution in the vertical direction.
+ * @return_width: place to return the calculated preview width.
+ * @return_height: place to return the calculated preview height.
+ * @scaling_up: returns #TRUE here if the calculated preview size
+ * is larger than the viewable itself.
+ *
+ * A utility function, for calculating the dimensions of a preview
+ * based on the information specified in the arguments. The arguments
+ * @aspect_width and @aspect_height are the dimensions of the unscaled
+ * preview. The arguments @width and @height represent the maximum
+ * width and height that the scaled preview must fit into. The
+ * preview is scaled to be as large as possible without exceeding
+ * these constraints.
+ *
+ * If @dot_for_dot is #TRUE, and @xresolution and @yresolution are
+ * different, then these results are corrected for the difference in
+ * resolution on the two axes, so that the requested aspect ratio
+ * applies to the appearance of the display rather than to pixel
+ * counts.
+ **/
+void
+gimp_viewable_calc_preview_size (gint aspect_width,
+ gint aspect_height,
+ gint width,
+ gint height,
+ gboolean dot_for_dot,
+ gdouble xresolution,
+ gdouble yresolution,
+ gint *return_width,
+ gint *return_height,
+ gboolean *scaling_up)
+{
+ gdouble xratio;
+ gdouble yratio;
+
+ if (aspect_width > aspect_height)
+ {
+ xratio = yratio = (gdouble) width / (gdouble) aspect_width;
+ }
+ else
+ {
+ xratio = yratio = (gdouble) height / (gdouble) aspect_height;
+ }
+
+ if (! dot_for_dot && xresolution != yresolution)
+ {
+ yratio *= xresolution / yresolution;
+ }
+
+ width = RINT (xratio * (gdouble) aspect_width);
+ height = RINT (yratio * (gdouble) aspect_height);
+
+ if (width < 1) width = 1;
+ if (height < 1) height = 1;
+
+ if (return_width) *return_width = width;
+ if (return_height) *return_height = height;
+ if (scaling_up) *scaling_up = (xratio > 1.0) || (yratio > 1.0);
+}
+
+gboolean
+gimp_viewable_get_size (GimpViewable *viewable,
+ gint *width,
+ gint *height)
+{
+ GimpViewableClass *viewable_class;
+ gboolean retval = FALSE;
+ gint w = 0;
+ gint h = 0;
+
+ g_return_val_if_fail (GIMP_IS_VIEWABLE (viewable), FALSE);
+
+ viewable_class = GIMP_VIEWABLE_GET_CLASS (viewable);
+
+ if (viewable_class->get_size)
+ retval = viewable_class->get_size (viewable, &w, &h);
+
+ if (width) *width = w;
+ if (height) *height = h;
+
+ return retval;
+}
+
+/**
+ * gimp_viewable_get_preview_size:
+ * @viewable: the object for which to calculate the preview size.
+ * @size: requested size for preview.
+ * @popup: %TRUE if the preview is intended for a popup window.
+ * @dot_for_dot: If #TRUE, ignore any differences in X and Y resolution.
+ * @width: return location for the the calculated width.
+ * @height: return location for the calculated height.
+ *
+ * Retrieve the size of a viewable's preview. By default, this
+ * simply returns the value of the @size argument for both the @width
+ * and @height, but this can be overridden in objects derived from
+ * #GimpViewable. If either the width or height exceeds
+ * #GIMP_VIEWABLE_MAX_PREVIEW_SIZE, they are silently truncated.
+ **/
+void
+gimp_viewable_get_preview_size (GimpViewable *viewable,
+ gint size,
+ gboolean popup,
+ gboolean dot_for_dot,
+ gint *width,
+ gint *height)
+{
+ gint w, h;
+
+ g_return_if_fail (GIMP_IS_VIEWABLE (viewable));
+ g_return_if_fail (size > 0);
+
+ GIMP_VIEWABLE_GET_CLASS (viewable)->get_preview_size (viewable, size,
+ popup, dot_for_dot,
+ &w, &h);
+
+ w = MIN (w, GIMP_VIEWABLE_MAX_PREVIEW_SIZE);
+ h = MIN (h, GIMP_VIEWABLE_MAX_PREVIEW_SIZE);
+
+ if (width) *width = w;
+ if (height) *height = h;
+
+}
+
+/**
+ * gimp_viewable_get_popup_size:
+ * @viewable: the object for which to calculate the popup size.
+ * @width: the width of the preview from which the popup will be shown.
+ * @height: the height of the preview from which the popup will be shown.
+ * @dot_for_dot: If #TRUE, ignore any differences in X and Y resolution.
+ * @popup_width: return location for the calculated popup width.
+ * @popup_height: return location for the calculated popup height.
+ *
+ * Calculate the size of a viewable's preview, for use in making a
+ * popup. The arguments @width and @height specify the size of the
+ * preview from which the popup will be shown.
+ *
+ * Returns: Whether the viewable wants a popup to be shown. Usually
+ * %TRUE if the passed preview size is smaller than the viewable
+ * size, and %FALSE if the viewable completely fits into the
+ * original preview.
+ **/
+gboolean
+gimp_viewable_get_popup_size (GimpViewable *viewable,
+ gint width,
+ gint height,
+ gboolean dot_for_dot,
+ gint *popup_width,
+ gint *popup_height)
+{
+ gint w, h;
+
+ g_return_val_if_fail (GIMP_IS_VIEWABLE (viewable), FALSE);
+
+ if (GIMP_VIEWABLE_GET_CLASS (viewable)->get_popup_size (viewable,
+ width, height,
+ dot_for_dot,
+ &w, &h))
+ {
+ if (w < 1) w = 1;
+ if (h < 1) h = 1;
+
+ /* limit the popup to 2 * GIMP_VIEWABLE_MAX_POPUP_SIZE
+ * on each axis.
+ */
+ if ((w > (2 * GIMP_VIEWABLE_MAX_POPUP_SIZE)) ||
+ (h > (2 * GIMP_VIEWABLE_MAX_POPUP_SIZE)))
+ {
+ gimp_viewable_calc_preview_size (w, h,
+ 2 * GIMP_VIEWABLE_MAX_POPUP_SIZE,
+ 2 * GIMP_VIEWABLE_MAX_POPUP_SIZE,
+ dot_for_dot, 1.0, 1.0,
+ &w, &h, NULL);
+ }
+
+ /* limit the number of pixels to
+ * GIMP_VIEWABLE_MAX_POPUP_SIZE ^ 2
+ */
+ if ((w * h) > SQR (GIMP_VIEWABLE_MAX_POPUP_SIZE))
+ {
+ gdouble factor;
+
+ factor = sqrt (((gdouble) (w * h) /
+ (gdouble) SQR (GIMP_VIEWABLE_MAX_POPUP_SIZE)));
+
+ w = RINT ((gdouble) w / factor);
+ h = RINT ((gdouble) h / factor);
+ }
+
+ if (w < 1) w = 1;
+ if (h < 1) h = 1;
+
+ if (popup_width) *popup_width = w;
+ if (popup_height) *popup_height = h;
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/**
+ * gimp_viewable_get_preview:
+ * @viewable: The viewable object to get a preview for.
+ * @context: The context to render the preview for.
+ * @width: desired width for the preview
+ * @height: desired height for the preview
+ *
+ * Gets a preview for a viewable object, by running through a variety
+ * of methods until it finds one that works. First, if an
+ * implementation exists of a "get_preview" method, it is tried, and
+ * the result is returned if it is not #NULL. Second, the function
+ * checks to see whether there is a cached preview with the correct
+ * dimensions; if so, it is returned. If neither of these works, then
+ * the function looks for an implementation of the "get_new_preview"
+ * method, and executes it, caching the result. If everything fails,
+ * #NULL is returned.
+ *
+ * Returns: A #GimpTempBuf containing the preview image, or #NULL if
+ * none can be found or created.
+ **/
+GimpTempBuf *
+gimp_viewable_get_preview (GimpViewable *viewable,
+ GimpContext *context,
+ gint width,
+ gint height)
+{
+ GimpViewablePrivate *private;
+ GimpViewableClass *viewable_class;
+ GimpTempBuf *temp_buf = NULL;
+
+ g_return_val_if_fail (GIMP_IS_VIEWABLE (viewable), NULL);
+ g_return_val_if_fail (context == NULL || GIMP_IS_CONTEXT (context), NULL);
+ g_return_val_if_fail (width > 0, NULL);
+ g_return_val_if_fail (height > 0, NULL);
+
+ private = GET_PRIVATE (viewable);
+
+ if (G_UNLIKELY (context == NULL))
+ g_warning ("%s: context is NULL", G_STRFUNC);
+
+ viewable_class = GIMP_VIEWABLE_GET_CLASS (viewable);
+
+ if (viewable_class->get_preview)
+ temp_buf = viewable_class->get_preview (viewable, context, width, height);
+
+ if (temp_buf)
+ return temp_buf;
+
+ if (private->preview_temp_buf)
+ {
+ if (gimp_temp_buf_get_width (private->preview_temp_buf) == width &&
+ gimp_temp_buf_get_height (private->preview_temp_buf) == height)
+ {
+ return private->preview_temp_buf;
+ }
+
+ g_clear_pointer (&private->preview_temp_buf, gimp_temp_buf_unref);
+ }
+
+ if (viewable_class->get_new_preview)
+ temp_buf = viewable_class->get_new_preview (viewable, context,
+ width, height);
+
+ private->preview_temp_buf = temp_buf;
+
+ return temp_buf;
+}
+
+/**
+ * gimp_viewable_get_new_preview:
+ * @viewable: The viewable object to get a preview for.
+ * @width: desired width for the preview
+ * @height: desired height for the preview
+ *
+ * Gets a new preview for a viewable object. Similar to
+ * gimp_viewable_get_preview(), except that it tries things in a
+ * different order, first looking for a "get_new_preview" method, and
+ * then if that fails for a "get_preview" method. This function does
+ * not look for a cached preview.
+ *
+ * Returns: A #GimpTempBuf containing the preview image, or #NULL if
+ * none can be found or created.
+ **/
+GimpTempBuf *
+gimp_viewable_get_new_preview (GimpViewable *viewable,
+ GimpContext *context,
+ gint width,
+ gint height)
+{
+ GimpViewableClass *viewable_class;
+ GimpTempBuf *temp_buf = NULL;
+
+ g_return_val_if_fail (GIMP_IS_VIEWABLE (viewable), NULL);
+ g_return_val_if_fail (context == NULL || GIMP_IS_CONTEXT (context), NULL);
+ g_return_val_if_fail (width > 0, NULL);
+ g_return_val_if_fail (height > 0, NULL);
+
+ if (G_UNLIKELY (context == NULL))
+ g_warning ("%s: context is NULL", G_STRFUNC);
+
+ viewable_class = GIMP_VIEWABLE_GET_CLASS (viewable);
+
+ if (viewable_class->get_new_preview)
+ temp_buf = viewable_class->get_new_preview (viewable, context,
+ width, height);
+
+ if (temp_buf)
+ return temp_buf;
+
+ if (viewable_class->get_preview)
+ temp_buf = viewable_class->get_preview (viewable, context,
+ width, height);
+
+ if (temp_buf)
+ return gimp_temp_buf_copy (temp_buf);
+
+ return NULL;
+}
+
+/**
+ * gimp_viewable_get_dummy_preview:
+ * @viewable: viewable object for which to get a dummy preview.
+ * @width: width of the preview.
+ * @height: height of the preview.
+ * @bpp: bytes per pixel for the preview, must be 3 or 4.
+ *
+ * Creates a dummy preview the fits into the specified dimensions,
+ * containing a default "question" symbol. This function is used to
+ * generate a preview in situations where layer previews have been
+ * disabled in the current Gimp configuration.
+ *
+ * Returns: a #GimpTempBuf containing the preview image.
+ **/
+GimpTempBuf *
+gimp_viewable_get_dummy_preview (GimpViewable *viewable,
+ gint width,
+ gint height,
+ const Babl *format)
+{
+ GdkPixbuf *pixbuf;
+ GimpTempBuf *buf;
+
+ g_return_val_if_fail (GIMP_IS_VIEWABLE (viewable), NULL);
+ g_return_val_if_fail (width > 0, NULL);
+ g_return_val_if_fail (height > 0, NULL);
+ g_return_val_if_fail (format != NULL, NULL);
+
+ pixbuf = gimp_viewable_get_dummy_pixbuf (viewable, width, height,
+ babl_format_has_alpha (format));
+
+ buf = gimp_temp_buf_new_from_pixbuf (pixbuf, format);
+
+ g_object_unref (pixbuf);
+
+ return buf;
+}
+
+/**
+ * gimp_viewable_get_pixbuf:
+ * @viewable: The viewable object to get a pixbuf preview for.
+ * @context: The context to render the preview for.
+ * @width: desired width for the preview
+ * @height: desired height for the preview
+ *
+ * Gets a preview for a viewable object, by running through a variety
+ * of methods until it finds one that works. First, if an
+ * implementation exists of a "get_pixbuf" method, it is tried, and
+ * the result is returned if it is not #NULL. Second, the function
+ * checks to see whether there is a cached preview with the correct
+ * dimensions; if so, it is returned. If neither of these works, then
+ * the function looks for an implementation of the "get_new_pixbuf"
+ * method, and executes it, caching the result. If everything fails,
+ * #NULL is returned.
+ *
+ * Returns: A #GdkPixbuf containing the preview pixbuf, or #NULL if none can
+ * be found or created.
+ **/
+GdkPixbuf *
+gimp_viewable_get_pixbuf (GimpViewable *viewable,
+ GimpContext *context,
+ gint width,
+ gint height)
+{
+ GimpViewablePrivate *private;
+ GimpViewableClass *viewable_class;
+ GdkPixbuf *pixbuf = NULL;
+
+ g_return_val_if_fail (GIMP_IS_VIEWABLE (viewable), NULL);
+ g_return_val_if_fail (context == NULL || GIMP_IS_CONTEXT (context), NULL);
+ g_return_val_if_fail (width > 0, NULL);
+ g_return_val_if_fail (height > 0, NULL);
+
+ private = GET_PRIVATE (viewable);
+
+ if (G_UNLIKELY (context == NULL))
+ g_warning ("%s: context is NULL", G_STRFUNC);
+
+ viewable_class = GIMP_VIEWABLE_GET_CLASS (viewable);
+
+ if (viewable_class->get_pixbuf)
+ pixbuf = viewable_class->get_pixbuf (viewable, context, width, height);
+
+ if (pixbuf)
+ return pixbuf;
+
+ if (private->preview_pixbuf)
+ {
+ if (gdk_pixbuf_get_width (private->preview_pixbuf) == width &&
+ gdk_pixbuf_get_height (private->preview_pixbuf) == height)
+ {
+ return private->preview_pixbuf;
+ }
+
+ g_clear_object (&private->preview_pixbuf);
+ }
+
+ if (viewable_class->get_new_pixbuf)
+ pixbuf = viewable_class->get_new_pixbuf (viewable, context, width, height);
+
+ private->preview_pixbuf = pixbuf;
+
+ return pixbuf;
+}
+
+/**
+ * gimp_viewable_get_new_pixbuf:
+ * @viewable: The viewable object to get a new pixbuf preview for.
+ * @context: The context to render the preview for.
+ * @width: desired width for the pixbuf
+ * @height: desired height for the pixbuf
+ *
+ * Gets a new preview for a viewable object. Similar to
+ * gimp_viewable_get_pixbuf(), except that it tries things in a
+ * different order, first looking for a "get_new_pixbuf" method, and
+ * then if that fails for a "get_pixbuf" method. This function does
+ * not look for a cached pixbuf.
+ *
+ * Returns: A #GdkPixbuf containing the preview, or #NULL if none can
+ * be created.
+ **/
+GdkPixbuf *
+gimp_viewable_get_new_pixbuf (GimpViewable *viewable,
+ GimpContext *context,
+ gint width,
+ gint height)
+{
+ GimpViewableClass *viewable_class;
+ GdkPixbuf *pixbuf = NULL;
+
+ g_return_val_if_fail (GIMP_IS_VIEWABLE (viewable), NULL);
+ g_return_val_if_fail (context == NULL || GIMP_IS_CONTEXT (context), NULL);
+ g_return_val_if_fail (width > 0, NULL);
+ g_return_val_if_fail (height > 0, NULL);
+
+ if (G_UNLIKELY (context == NULL))
+ g_warning ("%s: context is NULL", G_STRFUNC);
+
+ viewable_class = GIMP_VIEWABLE_GET_CLASS (viewable);
+
+ if (viewable_class->get_new_pixbuf)
+ pixbuf = viewable_class->get_new_pixbuf (viewable, context, width, height);
+
+ if (pixbuf)
+ return pixbuf;
+
+ if (viewable_class->get_pixbuf)
+ pixbuf = viewable_class->get_pixbuf (viewable, context, width, height);
+
+ if (pixbuf)
+ return gdk_pixbuf_copy (pixbuf);
+
+ return NULL;
+}
+
+/**
+ * gimp_viewable_get_dummy_pixbuf:
+ * @viewable: the viewable object for which to create a dummy representation.
+ * @width: maximum permitted width for the pixbuf.
+ * @height: maximum permitted height for the pixbuf.
+ * @bpp: bytes per pixel for the pixbuf, must equal 3 or 4.
+ *
+ * Creates a pixbuf containing a default "question" symbol, sized to
+ * fit into the specified dimensions. The depth of the pixbuf must be
+ * 3 or 4 because #GdkPixbuf does not support grayscale. This
+ * function is used to generate a preview in situations where
+ * previewing has been disabled in the current Gimp configuration.
+ * [Note: this function is currently unused except internally to
+ * #GimpViewable -- consider making it static?]
+ *
+ * Returns: the created #GdkPixbuf.
+ **/
+GdkPixbuf *
+gimp_viewable_get_dummy_pixbuf (GimpViewable *viewable,
+ gint width,
+ gint height,
+ gboolean with_alpha)
+{
+ GdkPixbuf *icon;
+ GdkPixbuf *pixbuf;
+ GError *error = NULL;
+ gdouble ratio;
+ gint w, h;
+
+ g_return_val_if_fail (GIMP_IS_VIEWABLE (viewable), NULL);
+ g_return_val_if_fail (width > 0, NULL);
+ g_return_val_if_fail (height > 0, NULL);
+
+ icon = gdk_pixbuf_new_from_resource ("/org/gimp/icons/64/gimp-question.png",
+ &error);
+ if (! icon)
+ {
+ g_critical ("Failed to create icon image: %s", error->message);
+ g_clear_error (&error);
+ return NULL;
+ }
+
+ w = gdk_pixbuf_get_width (icon);
+ h = gdk_pixbuf_get_height (icon);
+
+ ratio = (gdouble) MIN (width, height) / (gdouble) MAX (w, h);
+ ratio = MIN (ratio, 1.0);
+
+ w = RINT (ratio * (gdouble) w);
+ h = RINT (ratio * (gdouble) h);
+
+ pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, with_alpha, 8, width, height);
+ gdk_pixbuf_fill (pixbuf, 0xffffffff);
+
+ if (w && h)
+ gdk_pixbuf_composite (icon, pixbuf,
+ (width - w) / 2, (height - h) / 2, w, h,
+ (width - w) / 2, (height - h) / 2, ratio, ratio,
+ GDK_INTERP_BILINEAR, 0xFF);
+
+ g_object_unref (icon);
+
+ return pixbuf;
+}
+
+/**
+ * gimp_viewable_get_description:
+ * @viewable: viewable object for which to retrieve a description.
+ * @tooltip: return location for an optional tooltip string.
+ *
+ * Retrieves a string containing a description of the viewable object,
+ * By default, it simply returns the name of the object, but this can
+ * be overridden by object types that inherit from #GimpViewable.
+ *
+ * Returns: a copy of the description string. This should be freed
+ * when it is no longer needed.
+ **/
+gchar *
+gimp_viewable_get_description (GimpViewable *viewable,
+ gchar **tooltip)
+{
+ g_return_val_if_fail (GIMP_IS_VIEWABLE (viewable), NULL);
+
+ if (tooltip)
+ *tooltip = NULL;
+
+ return GIMP_VIEWABLE_GET_CLASS (viewable)->get_description (viewable,
+ tooltip);
+}
+
+/**
+ * gimp_viewable_is_name_editable:
+ * @viewable: viewable object for which to retrieve a description.
+ *
+ * Returns: whether the viewable's name is editable by the user.
+ **/
+gboolean
+gimp_viewable_is_name_editable (GimpViewable *viewable)
+{
+ g_return_val_if_fail (GIMP_IS_VIEWABLE (viewable), FALSE);
+
+ return GIMP_VIEWABLE_GET_CLASS (viewable)->is_name_editable (viewable);
+}
+
+/**
+ * gimp_viewable_get_icon_name:
+ * @viewable: viewable object for which to retrieve a icon name.
+ *
+ * Gets the current value of the object's icon name, for use in
+ * constructing an iconic representation of the object.
+ *
+ * Returns: a pointer to the string containing the icon name. The
+ * contents must not be altered or freed.
+ **/
+const gchar *
+gimp_viewable_get_icon_name (GimpViewable *viewable)
+{
+ GimpViewablePrivate *private;
+
+ g_return_val_if_fail (GIMP_IS_VIEWABLE (viewable), NULL);
+
+ private = GET_PRIVATE (viewable);
+
+ if (private->icon_name)
+ return (const gchar *) private->icon_name;
+
+ return GIMP_VIEWABLE_GET_CLASS (viewable)->default_icon_name;
+}
+
+/**
+ * gimp_viewable_set_icon_name:
+ * @viewable: viewable object to assign the specified icon name.
+ * @icon_name: string containing an icon name identifier.
+ *
+ * Seta the object's icon name, for use in constructing iconic smbols
+ * of the object. The contents of @icon_name are copied, so you can
+ * free it when you are done with it.
+ **/
+void
+gimp_viewable_set_icon_name (GimpViewable *viewable,
+ const gchar *icon_name)
+{
+ GimpViewablePrivate *private;
+ GimpViewableClass *viewable_class;
+
+ g_return_if_fail (GIMP_IS_VIEWABLE (viewable));
+
+ private = GET_PRIVATE (viewable);
+
+ g_clear_pointer (&private->icon_name, g_free);
+
+ viewable_class = GIMP_VIEWABLE_GET_CLASS (viewable);
+
+ if (icon_name)
+ {
+ if (viewable_class->default_icon_name == NULL ||
+ strcmp (icon_name, viewable_class->default_icon_name))
+ private->icon_name = g_strdup (icon_name);
+ }
+
+ gimp_viewable_invalidate_preview (viewable);
+
+ g_object_notify (G_OBJECT (viewable), "icon-name");
+}
+
+void
+gimp_viewable_preview_freeze (GimpViewable *viewable)
+{
+ GimpViewablePrivate *private;
+
+ g_return_if_fail (GIMP_IS_VIEWABLE (viewable));
+
+ private = GET_PRIVATE (viewable);
+
+ private->freeze_count++;
+
+ if (private->freeze_count == 1)
+ {
+ if (GIMP_VIEWABLE_GET_CLASS (viewable)->preview_freeze)
+ GIMP_VIEWABLE_GET_CLASS (viewable)->preview_freeze (viewable);
+
+ g_object_notify (G_OBJECT (viewable), "frozen");
+ }
+}
+
+void
+gimp_viewable_preview_thaw (GimpViewable *viewable)
+{
+ GimpViewablePrivate *private;
+
+ g_return_if_fail (GIMP_IS_VIEWABLE (viewable));
+
+ private = GET_PRIVATE (viewable);
+
+ g_return_if_fail (private->freeze_count > 0);
+
+ private->freeze_count--;
+
+ if (private->freeze_count == 0)
+ {
+ if (private->size_changed_prending)
+ {
+ private->size_changed_prending = FALSE;
+
+ gimp_viewable_size_changed (viewable);
+ }
+
+ if (private->invalidate_pending)
+ {
+ private->invalidate_pending = FALSE;
+
+ gimp_viewable_invalidate_preview (viewable);
+ }
+
+ g_object_notify (G_OBJECT (viewable), "frozen");
+
+ if (GIMP_VIEWABLE_GET_CLASS (viewable)->preview_thaw)
+ GIMP_VIEWABLE_GET_CLASS (viewable)->preview_thaw (viewable);
+ }
+}
+
+gboolean
+gimp_viewable_preview_is_frozen (GimpViewable *viewable)
+{
+ g_return_val_if_fail (GIMP_IS_VIEWABLE (viewable), FALSE);
+
+ return GET_PRIVATE (viewable)->freeze_count != 0;
+}
+
+GimpViewable *
+gimp_viewable_get_parent (GimpViewable *viewable)
+{
+ g_return_val_if_fail (GIMP_IS_VIEWABLE (viewable), NULL);
+
+ return GET_PRIVATE (viewable)->parent;
+}
+
+void
+gimp_viewable_set_parent (GimpViewable *viewable,
+ GimpViewable *parent)
+{
+ GimpViewablePrivate *private;
+
+ g_return_if_fail (GIMP_IS_VIEWABLE (viewable));
+ g_return_if_fail (parent == NULL || GIMP_IS_VIEWABLE (parent));
+
+ private = GET_PRIVATE (viewable);
+
+ if (parent != private->parent)
+ {
+ private->parent = parent;
+ private->depth = parent ? gimp_viewable_get_depth (parent) + 1 : 0;
+
+ g_signal_emit (viewable, viewable_signals[ANCESTRY_CHANGED], 0);
+ }
+}
+
+gint
+gimp_viewable_get_depth (GimpViewable *viewable)
+{
+ g_return_val_if_fail (GIMP_IS_VIEWABLE (viewable), 0);
+
+ return GET_PRIVATE (viewable)->depth;
+}
+
+GimpContainer *
+gimp_viewable_get_children (GimpViewable *viewable)
+{
+ g_return_val_if_fail (GIMP_IS_VIEWABLE (viewable), NULL);
+
+ return GIMP_VIEWABLE_GET_CLASS (viewable)->get_children (viewable);
+}
+
+gboolean
+gimp_viewable_get_expanded (GimpViewable *viewable)
+{
+ g_return_val_if_fail (GIMP_IS_VIEWABLE (viewable), FALSE);
+
+ if (GIMP_VIEWABLE_GET_CLASS (viewable)->get_expanded)
+ return GIMP_VIEWABLE_GET_CLASS (viewable)->get_expanded (viewable);
+
+ return FALSE;
+}
+
+void
+gimp_viewable_set_expanded (GimpViewable *viewable,
+ gboolean expanded)
+{
+ g_return_if_fail (GIMP_IS_VIEWABLE (viewable));
+
+ if (GIMP_VIEWABLE_GET_CLASS (viewable)->set_expanded)
+ GIMP_VIEWABLE_GET_CLASS (viewable)->set_expanded (viewable, expanded);
+}
+
+gboolean
+gimp_viewable_is_ancestor (GimpViewable *ancestor,
+ GimpViewable *descendant)
+{
+ g_return_val_if_fail (GIMP_IS_VIEWABLE (ancestor), FALSE);
+ g_return_val_if_fail (GIMP_IS_VIEWABLE (descendant), FALSE);
+
+ while (descendant)
+ {
+ GimpViewable *parent = gimp_viewable_get_parent (descendant);
+
+ if (parent == ancestor)
+ return TRUE;
+
+ descendant = parent;
+ }
+
+ return FALSE;
+}
diff --git a/app/core/gimpviewable.h b/app/core/gimpviewable.h
new file mode 100644
index 0000000..249750e
--- /dev/null
+++ b/app/core/gimpviewable.h
@@ -0,0 +1,201 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-1997 Spencer Kimball and Peter Mattis
+ *
+ * gimpviewable.h
+ * Copyright (C) 2001 Michael Natterer <mitch@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_VIEWABLE_H__
+#define __GIMP_VIEWABLE_H__
+
+
+#include "gimpobject.h"
+
+
+#define GIMP_VIEWABLE_MAX_PREVIEW_SIZE 2048
+#define GIMP_VIEWABLE_MAX_POPUP_SIZE 256
+#define GIMP_VIEWABLE_MAX_BUTTON_SIZE 64
+#define GIMP_VIEWABLE_MAX_MENU_SIZE 48
+
+
+#define GIMP_TYPE_VIEWABLE (gimp_viewable_get_type ())
+#define GIMP_VIEWABLE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_VIEWABLE, GimpViewable))
+#define GIMP_VIEWABLE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_VIEWABLE, GimpViewableClass))
+#define GIMP_IS_VIEWABLE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_VIEWABLE))
+#define GIMP_IS_VIEWABLE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_VIEWABLE))
+#define GIMP_VIEWABLE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_VIEWABLE, GimpViewableClass))
+
+
+typedef struct _GimpViewableClass GimpViewableClass;
+
+struct _GimpViewable
+{
+ GimpObject parent_instance;
+};
+
+struct _GimpViewableClass
+{
+ GimpObjectClass parent_class;
+
+ const gchar *default_icon_name;
+ const gchar *name_changed_signal;
+ gboolean name_editable;
+
+ /* signals */
+ void (* invalidate_preview) (GimpViewable *viewable);
+ void (* size_changed) (GimpViewable *viewable);
+ void (* expanded_changed) (GimpViewable *viewable);
+ void (* ancestry_changed) (GimpViewable *viewable);
+
+ /* virtual functions */
+ gboolean (* get_size) (GimpViewable *viewable,
+ gint *width,
+ gint *height);
+ void (* get_preview_size) (GimpViewable *viewable,
+ gint size,
+ gboolean is_popup,
+ gboolean dot_for_dot,
+ gint *width,
+ gint *height);
+ gboolean (* get_popup_size) (GimpViewable *viewable,
+ gint width,
+ gint height,
+ gboolean dot_for_dot,
+ gint *popup_width,
+ gint *popup_height);
+ GimpTempBuf * (* get_preview) (GimpViewable *viewable,
+ GimpContext *context,
+ gint width,
+ gint height);
+ GimpTempBuf * (* get_new_preview) (GimpViewable *viewable,
+ GimpContext *context,
+ gint width,
+ gint height);
+ GdkPixbuf * (* get_pixbuf) (GimpViewable *viewable,
+ GimpContext *context,
+ gint width,
+ gint height);
+ GdkPixbuf * (* get_new_pixbuf) (GimpViewable *viewable,
+ GimpContext *context,
+ gint width,
+ gint height);
+ gchar * (* get_description) (GimpViewable *viewable,
+ gchar **tooltip);
+
+ gboolean (* is_name_editable) (GimpViewable *viewable);
+
+ void (* preview_freeze) (GimpViewable *viewable);
+ void (* preview_thaw) (GimpViewable *viewable);
+
+ GimpContainer * (* get_children) (GimpViewable *viewable);
+
+ void (* set_expanded) (GimpViewable *viewable,
+ gboolean expand);
+ gboolean (* get_expanded) (GimpViewable *viewable);
+};
+
+
+GType gimp_viewable_get_type (void) G_GNUC_CONST;
+
+void gimp_viewable_invalidate_preview (GimpViewable *viewable);
+void gimp_viewable_size_changed (GimpViewable *viewable);
+void gimp_viewable_expanded_changed (GimpViewable *viewable);
+
+void gimp_viewable_calc_preview_size (gint aspect_width,
+ gint aspect_height,
+ gint width,
+ gint height,
+ gboolean dot_for_dot,
+ gdouble xresolution,
+ gdouble yresolution,
+ gint *return_width,
+ gint *return_height,
+ gboolean *scaling_up);
+
+gboolean gimp_viewable_get_size (GimpViewable *viewable,
+ gint *width,
+ gint *height);
+void gimp_viewable_get_preview_size (GimpViewable *viewable,
+ gint size,
+ gboolean popup,
+ gboolean dot_for_dot,
+ gint *width,
+ gint *height);
+gboolean gimp_viewable_get_popup_size (GimpViewable *viewable,
+ gint width,
+ gint height,
+ gboolean dot_for_dot,
+ gint *popup_width,
+ gint *popup_height);
+
+GimpTempBuf * gimp_viewable_get_preview (GimpViewable *viewable,
+ GimpContext *context,
+ gint width,
+ gint height);
+GimpTempBuf * gimp_viewable_get_new_preview (GimpViewable *viewable,
+ GimpContext *context,
+ gint width,
+ gint height);
+
+GimpTempBuf * gimp_viewable_get_dummy_preview (GimpViewable *viewable,
+ gint width,
+ gint height,
+ const Babl *format);
+
+GdkPixbuf * gimp_viewable_get_pixbuf (GimpViewable *viewable,
+ GimpContext *context,
+ gint width,
+ gint height);
+GdkPixbuf * gimp_viewable_get_new_pixbuf (GimpViewable *viewable,
+ GimpContext *context,
+ gint width,
+ gint height);
+
+GdkPixbuf * gimp_viewable_get_dummy_pixbuf (GimpViewable *viewable,
+ gint width,
+ gint height,
+ gboolean with_alpha);
+
+gchar * gimp_viewable_get_description (GimpViewable *viewable,
+ gchar **tooltip);
+
+gboolean gimp_viewable_is_name_editable (GimpViewable *viewable);
+
+const gchar * gimp_viewable_get_icon_name (GimpViewable *viewable);
+void gimp_viewable_set_icon_name (GimpViewable *viewable,
+ const gchar *icon_name);
+
+void gimp_viewable_preview_freeze (GimpViewable *viewable);
+void gimp_viewable_preview_thaw (GimpViewable *viewable);
+gboolean gimp_viewable_preview_is_frozen (GimpViewable *viewable);
+
+GimpViewable * gimp_viewable_get_parent (GimpViewable *viewable);
+void gimp_viewable_set_parent (GimpViewable *viewable,
+ GimpViewable *parent);
+
+gint gimp_viewable_get_depth (GimpViewable *viewable);
+
+GimpContainer * gimp_viewable_get_children (GimpViewable *viewable);
+
+gboolean gimp_viewable_get_expanded (GimpViewable *viewable);
+void gimp_viewable_set_expanded (GimpViewable *viewable,
+ gboolean expanded);
+
+gboolean gimp_viewable_is_ancestor (GimpViewable *ancestor,
+ GimpViewable *descendant);
+
+
+#endif /* __GIMP_VIEWABLE_H__ */
diff --git a/app/core/gimpwaitable.c b/app/core/gimpwaitable.c
new file mode 100644
index 0000000..4607f74
--- /dev/null
+++ b/app/core/gimpwaitable.c
@@ -0,0 +1,118 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpwaitable.c
+ * Copyright (C) 2018 Ell
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "core-types.h"
+
+#include "gimpwaitable.h"
+
+
+G_DEFINE_INTERFACE (GimpWaitable, gimp_waitable, G_TYPE_OBJECT)
+
+
+/* private functions */
+
+
+static void
+gimp_waitable_default_init (GimpWaitableInterface *iface)
+{
+}
+
+
+/* public functions */
+
+
+void
+gimp_waitable_wait (GimpWaitable *waitable)
+{
+ GimpWaitableInterface *iface;
+
+ g_return_if_fail (GIMP_IS_WAITABLE (waitable));
+
+ iface = GIMP_WAITABLE_GET_INTERFACE (waitable);
+
+ if (iface->wait)
+ iface->wait (waitable);
+}
+
+gboolean
+gimp_waitable_try_wait (GimpWaitable *waitable)
+{
+ GimpWaitableInterface *iface;
+
+ g_return_val_if_fail (GIMP_IS_WAITABLE (waitable), FALSE);
+
+ iface = GIMP_WAITABLE_GET_INTERFACE (waitable);
+
+ if (iface->try_wait)
+ {
+ return iface->try_wait (waitable);
+ }
+ else
+ {
+ gimp_waitable_wait (waitable);
+
+ return TRUE;
+ }
+}
+
+gboolean
+gimp_waitable_wait_until (GimpWaitable *waitable,
+ gint64 end_time)
+{
+ GimpWaitableInterface *iface;
+
+ g_return_val_if_fail (GIMP_IS_WAITABLE (waitable), FALSE);
+
+ iface = GIMP_WAITABLE_GET_INTERFACE (waitable);
+
+ if (iface->wait_until)
+ {
+ return iface->wait_until (waitable, end_time);
+ }
+ else
+ {
+ gimp_waitable_wait (waitable);
+
+ return TRUE;
+ }
+}
+
+gboolean
+gimp_waitable_wait_for (GimpWaitable *waitable,
+ gint64 wait_duration)
+{
+ g_return_val_if_fail (GIMP_IS_WAITABLE (waitable), FALSE);
+
+ if (wait_duration <= 0)
+ {
+ return gimp_waitable_try_wait (waitable);
+ }
+ else
+ {
+ return gimp_waitable_wait_until (waitable,
+ g_get_monotonic_time () + wait_duration);
+ }
+}
diff --git a/app/core/gimpwaitable.h b/app/core/gimpwaitable.h
new file mode 100644
index 0000000..b85bece
--- /dev/null
+++ b/app/core/gimpwaitable.h
@@ -0,0 +1,55 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-1997 Spencer Kimball and Peter Mattis
+ *
+ * gimpwaitable.h
+ * Copyright (C) 2018 Ell
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_WAITABLE_H__
+#define __GIMP_WAITABLE_H__
+
+
+#define GIMP_TYPE_WAITABLE (gimp_waitable_get_type ())
+#define GIMP_IS_WAITABLE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_WAITABLE))
+#define GIMP_WAITABLE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_WAITABLE, GimpWaitable))
+#define GIMP_WAITABLE_GET_INTERFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), GIMP_TYPE_WAITABLE, GimpWaitableInterface))
+
+
+typedef struct _GimpWaitableInterface GimpWaitableInterface;
+
+struct _GimpWaitableInterface
+{
+ GTypeInterface base_iface;
+
+ /* virtual functions */
+ void (* wait) (GimpWaitable *waitable);
+ gboolean (* try_wait) (GimpWaitable *waitable);
+ gboolean (* wait_until) (GimpWaitable *waitable,
+ gint64 end_time);
+};
+
+
+GType gimp_waitable_get_type (void) G_GNUC_CONST;
+
+void gimp_waitable_wait (GimpWaitable *waitable);
+gboolean gimp_waitable_try_wait (GimpWaitable *waitable);
+gboolean gimp_waitable_wait_until (GimpWaitable *waitable,
+ gint64 end_time);
+gboolean gimp_waitable_wait_for (GimpWaitable *waitable,
+ gint64 wait_duration);
+
+
+#endif /* __GIMP_WAITABLE_H__ */