From 5c1676dfe6d2f3c837a5e074117b45613fd29a72 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 20:30:19 +0200 Subject: Adding upstream version 2.10.34. Signed-off-by: Daniel Baumann --- app/core/Makefile.am | 550 +++ app/core/Makefile.in | 2438 +++++++++++++ app/core/core-enums.c | 1316 +++++++ app/core/core-enums.h | 701 ++++ app/core/core-types.h | 303 ++ app/core/gimp-atomic.c | 95 + app/core/gimp-atomic.h | 32 + app/core/gimp-batch.c | 203 ++ app/core/gimp-batch.h | 27 + app/core/gimp-cairo.c | 217 ++ app/core/gimp-cairo.h | 51 + app/core/gimp-contexts.c | 161 + app/core/gimp-contexts.h | 36 + app/core/gimp-data-factories.c | 433 +++ app/core/gimp-data-factories.h | 36 + app/core/gimp-edit.c | 771 +++++ app/core/gimp-edit.h | 61 + app/core/gimp-filter-history.c | 160 + app/core/gimp-filter-history.h | 35 + app/core/gimp-gradients.c | 174 + app/core/gimp-gradients.h | 34 + app/core/gimp-gui.c | 585 ++++ app/core/gimp-gui.h | 211 ++ app/core/gimp-internal-data.c | 346 ++ app/core/gimp-internal-data.h | 34 + app/core/gimp-memsize.c | 341 ++ app/core/gimp-memsize.h | 60 + app/core/gimp-modules.c | 227 ++ app/core/gimp-modules.h | 34 + app/core/gimp-palettes.c | 143 + app/core/gimp-palettes.h | 35 + app/core/gimp-parallel.cc | 553 +++ app/core/gimp-parallel.h | 157 + app/core/gimp-parasites.c | 170 + app/core/gimp-parasites.h | 41 + app/core/gimp-spawn.c | 250 ++ app/core/gimp-spawn.h | 34 + app/core/gimp-tags.c | 271 ++ app/core/gimp-tags.h | 25 + app/core/gimp-templates.c | 213 ++ app/core/gimp-templates.h | 28 + app/core/gimp-transform-3d-utils.c | 359 ++ app/core/gimp-transform-3d-utils.h | 95 + app/core/gimp-transform-resize.c | 841 +++++ app/core/gimp-transform-resize.h | 34 + app/core/gimp-transform-utils.c | 1211 +++++++ app/core/gimp-transform-utils.h | 125 + app/core/gimp-units.c | 488 +++ app/core/gimp-units.h | 29 + app/core/gimp-user-install.c | 977 ++++++ app/core/gimp-user-install.h | 39 + app/core/gimp-utils.c | 1098 ++++++ app/core/gimp-utils.h | 116 + app/core/gimp.c | 1224 +++++++ app/core/gimp.h | 248 ++ app/core/gimpasync.c | 752 ++++ app/core/gimpasync.h | 95 + app/core/gimpasyncset.c | 348 ++ app/core/gimpasyncset.h | 61 + app/core/gimpauxitem.c | 147 + app/core/gimpauxitem.h | 56 + app/core/gimpauxitemundo.c | 138 + app/core/gimpauxitemundo.h | 52 + app/core/gimpbacktrace-backend.h | 34 + app/core/gimpbacktrace-linux.c | 725 ++++ app/core/gimpbacktrace-none.c | 126 + app/core/gimpbacktrace-windows.c | 706 ++++ app/core/gimpbacktrace.h | 70 + app/core/gimpbezierdesc.c | 202 ++ app/core/gimpbezierdesc.h | 47 + app/core/gimpboundary.c | 1016 ++++++ app/core/gimpboundary.h | 68 + app/core/gimpbrush-boundary.c | 130 + app/core/gimpbrush-boundary.h | 32 + app/core/gimpbrush-header.h | 49 + app/core/gimpbrush-load.c | 1213 +++++++ app/core/gimpbrush-load.h | 43 + app/core/gimpbrush-mipmap.cc | 514 +++ app/core/gimpbrush-mipmap.h | 38 + app/core/gimpbrush-private.h | 47 + app/core/gimpbrush-save.c | 107 + app/core/gimpbrush-save.h | 28 + app/core/gimpbrush-transform.cc | 1043 ++++++ app/core/gimpbrush-transform.h | 59 + app/core/gimpbrush.c | 942 +++++ app/core/gimpbrush.h | 152 + app/core/gimpbrushcache.c | 299 ++ app/core/gimpbrushcache.h | 83 + app/core/gimpbrushclipboard.c | 298 ++ app/core/gimpbrushclipboard.h | 58 + app/core/gimpbrushgenerated-load.c | 284 ++ app/core/gimpbrushgenerated-load.h | 33 + app/core/gimpbrushgenerated-save.c | 119 + app/core/gimpbrushgenerated-save.h | 30 + app/core/gimpbrushgenerated.c | 875 +++++ app/core/gimpbrushgenerated.h | 88 + app/core/gimpbrushpipe-load.c | 163 + app/core/gimpbrushpipe-load.h | 32 + app/core/gimpbrushpipe-save.c | 59 + app/core/gimpbrushpipe-save.h | 28 + app/core/gimpbrushpipe.c | 420 +++ app/core/gimpbrushpipe.h | 80 + app/core/gimpbuffer.c | 541 +++ app/core/gimpbuffer.h | 90 + app/core/gimpcancelable.c | 72 + app/core/gimpcancelable.h | 47 + app/core/gimpchannel-combine.c | 471 +++ app/core/gimpchannel-combine.h | 56 + app/core/gimpchannel-select.c | 605 ++++ app/core/gimpchannel-select.h | 160 + app/core/gimpchannel.c | 1956 +++++++++++ app/core/gimpchannel.h | 216 ++ app/core/gimpchannelpropundo.c | 108 + app/core/gimpchannelpropundo.h | 52 + app/core/gimpchannelundo.c | 214 ++ app/core/gimpchannelundo.h | 54 + app/core/gimpchunkiterator.c | 555 +++ app/core/gimpchunkiterator.h | 44 + app/core/gimpcontainer-filter.c | 174 + app/core/gimpcontainer-filter.h | 38 + app/core/gimpcontainer.c | 1167 +++++++ app/core/gimpcontainer.h | 150 + app/core/gimpcontext.c | 3828 ++++++++++++++++++++ app/core/gimpcontext.h | 360 ++ app/core/gimpcoords-interpolate.c | 371 ++ app/core/gimpcoords-interpolate.h | 41 + app/core/gimpcoords.c | 248 ++ app/core/gimpcoords.h | 57 + app/core/gimpcurve-load.c | 54 + app/core/gimpcurve-load.h | 30 + app/core/gimpcurve-map.c | 253 ++ app/core/gimpcurve-map.h | 34 + app/core/gimpcurve-save.c | 44 + app/core/gimpcurve-save.h | 28 + app/core/gimpcurve.c | 1289 +++++++ app/core/gimpcurve.h | 124 + app/core/gimpdashpattern.c | 355 ++ app/core/gimpdashpattern.h | 53 + app/core/gimpdata.c | 1245 +++++++ app/core/gimpdata.h | 133 + app/core/gimpdatafactory.c | 945 +++++ app/core/gimpdatafactory.h | 125 + app/core/gimpdataloaderfactory.c | 562 +++ app/core/gimpdataloaderfactory.h | 77 + app/core/gimpdocumentlist.c | 106 + app/core/gimpdocumentlist.h | 54 + app/core/gimpdrawable-bucket-fill.c | 499 +++ app/core/gimpdrawable-bucket-fill.h | 59 + app/core/gimpdrawable-combine.c | 144 + app/core/gimpdrawable-combine.h | 39 + app/core/gimpdrawable-edit.c | 231 ++ app/core/gimpdrawable-edit.h | 29 + app/core/gimpdrawable-equalize.c | 71 + app/core/gimpdrawable-equalize.h | 26 + app/core/gimpdrawable-fill.c | 279 ++ app/core/gimpdrawable-fill.h | 60 + app/core/gimpdrawable-filters.c | 343 ++ app/core/gimpdrawable-filters.h | 46 + app/core/gimpdrawable-floating-selection.c | 512 +++ app/core/gimpdrawable-floating-selection.h | 31 + app/core/gimpdrawable-foreground-extract.c | 150 + app/core/gimpdrawable-foreground-extract.h | 31 + app/core/gimpdrawable-gradient.c | 313 ++ app/core/gimpdrawable-gradient.h | 57 + app/core/gimpdrawable-histogram.c | 258 ++ app/core/gimpdrawable-histogram.h | 32 + app/core/gimpdrawable-levels.c | 77 + app/core/gimpdrawable-levels.h | 26 + app/core/gimpdrawable-offset.c | 83 + app/core/gimpdrawable-offset.h | 30 + app/core/gimpdrawable-operation.c | 128 + app/core/gimpdrawable-operation.h | 43 + app/core/gimpdrawable-preview.c | 492 +++ app/core/gimpdrawable-preview.h | 63 + app/core/gimpdrawable-private.h | 44 + app/core/gimpdrawable-shadow.c | 110 + app/core/gimpdrawable-shadow.h | 32 + app/core/gimpdrawable-stroke.c | 161 + app/core/gimpdrawable-stroke.h | 45 + app/core/gimpdrawable-transform.c | 1070 ++++++ app/core/gimpdrawable-transform.h | 94 + app/core/gimpdrawable.c | 1916 ++++++++++ app/core/gimpdrawable.h | 227 ++ app/core/gimpdrawablefilter.c | 1364 ++++++++ app/core/gimpdrawablefilter.h | 108 + app/core/gimpdrawablemodundo.c | 216 ++ app/core/gimpdrawablemodundo.h | 55 + app/core/gimpdrawablestack.c | 224 ++ app/core/gimpdrawablestack.h | 66 + app/core/gimpdrawableundo.c | 207 ++ app/core/gimpdrawableundo.h | 54 + app/core/gimpdynamics-load.c | 55 + app/core/gimpdynamics-load.h | 31 + app/core/gimpdynamics-save.c | 44 + app/core/gimpdynamics-save.h | 28 + app/core/gimpdynamics.c | 653 ++++ app/core/gimpdynamics.h | 77 + app/core/gimpdynamicsoutput.c | 767 ++++ app/core/gimpdynamicsoutput.h | 68 + app/core/gimperror.c | 36 + app/core/gimperror.h | 33 + app/core/gimpfilloptions.c | 548 +++ app/core/gimpfilloptions.h | 95 + app/core/gimpfilter.c | 315 ++ app/core/gimpfilter.h | 73 + app/core/gimpfilteredcontainer.c | 373 ++ app/core/gimpfilteredcontainer.h | 68 + app/core/gimpfilterstack.c | 350 ++ app/core/gimpfilterstack.h | 55 + app/core/gimpfloatingselectionundo.c | 135 + app/core/gimpfloatingselectionundo.h | 52 + app/core/gimpgradient-load.c | 575 +++ app/core/gimpgradient-load.h | 36 + app/core/gimpgradient-save.c | 221 ++ app/core/gimpgradient-save.h | 32 + app/core/gimpgradient.c | 2297 ++++++++++++ app/core/gimpgradient.h | 296 ++ app/core/gimpgrid.c | 359 ++ app/core/gimpgrid.h | 81 + app/core/gimpgrouplayer.c | 2297 ++++++++++++ app/core/gimpgrouplayer.h | 78 + app/core/gimpgrouplayerundo.c | 253 ++ app/core/gimpgrouplayerundo.h | 57 + app/core/gimpguide.c | 242 ++ app/core/gimpguide.h | 75 + app/core/gimpguideundo.c | 116 + app/core/gimpguideundo.h | 53 + app/core/gimphistogram.c | 1261 +++++++ app/core/gimphistogram.h | 105 + app/core/gimpidtable.c | 227 ++ app/core/gimpidtable.h | 68 + app/core/gimpimage-arrange.c | 388 +++ app/core/gimpimage-arrange.h | 29 + app/core/gimpimage-color-profile.c | 822 +++++ app/core/gimpimage-color-profile.h | 113 + app/core/gimpimage-colormap.c | 362 ++ app/core/gimpimage-colormap.h | 55 + app/core/gimpimage-convert-data.h | 143 + app/core/gimpimage-convert-fsdither.h | 559 +++ app/core/gimpimage-convert-indexed.c | 4567 ++++++++++++++++++++++++ app/core/gimpimage-convert-indexed.h | 41 + app/core/gimpimage-convert-precision.c | 304 ++ app/core/gimpimage-convert-precision.h | 36 + app/core/gimpimage-convert-type.c | 162 + app/core/gimpimage-convert-type.h | 29 + app/core/gimpimage-crop.c | 234 ++ app/core/gimpimage-crop.h | 32 + app/core/gimpimage-duplicate.c | 535 +++ app/core/gimpimage-duplicate.h | 25 + app/core/gimpimage-flip.c | 276 ++ app/core/gimpimage-flip.h | 34 + app/core/gimpimage-grid.c | 67 + app/core/gimpimage-grid.h | 31 + app/core/gimpimage-guides.c | 216 ++ app/core/gimpimage-guides.h | 54 + app/core/gimpimage-item-list.c | 401 +++ app/core/gimpimage-item-list.h | 63 + app/core/gimpimage-merge.c | 745 ++++ app/core/gimpimage-merge.h | 46 + app/core/gimpimage-metadata.c | 184 + app/core/gimpimage-metadata.h | 33 + app/core/gimpimage-new.c | 393 +++ app/core/gimpimage-new.h | 42 + app/core/gimpimage-pick-color.c | 147 + app/core/gimpimage-pick-color.h | 35 + app/core/gimpimage-pick-item.c | 381 ++ app/core/gimpimage-pick-item.h | 57 + app/core/gimpimage-preview.c | 214 ++ app/core/gimpimage-preview.h | 51 + app/core/gimpimage-private.h | 149 + app/core/gimpimage-quick-mask.c | 212 ++ app/core/gimpimage-quick-mask.h | 43 + app/core/gimpimage-resize.c | 327 ++ app/core/gimpimage-resize.h | 53 + app/core/gimpimage-rotate.c | 377 ++ app/core/gimpimage-rotate.h | 28 + app/core/gimpimage-sample-points.c | 213 ++ app/core/gimpimage-sample-points.h | 60 + app/core/gimpimage-scale.c | 260 ++ app/core/gimpimage-scale.h | 36 + app/core/gimpimage-snap.c | 719 ++++ app/core/gimpimage-snap.h | 63 + app/core/gimpimage-symmetry.c | 189 + app/core/gimpimage-symmetry.h | 40 + app/core/gimpimage-transform.c | 338 ++ app/core/gimpimage-transform.h | 34 + app/core/gimpimage-undo-push.c | 1060 ++++++ app/core/gimpimage-undo-push.h | 256 ++ app/core/gimpimage-undo.c | 695 ++++ app/core/gimpimage-undo.h | 57 + app/core/gimpimage.c | 5191 ++++++++++++++++++++++++++++ app/core/gimpimage.h | 463 +++ app/core/gimpimagefile.c | 1078 ++++++ app/core/gimpimagefile.h | 90 + app/core/gimpimageproxy.c | 877 +++++ app/core/gimpimageproxy.h | 65 + app/core/gimpimageundo.c | 535 +++ app/core/gimpimageundo.h | 69 + app/core/gimpitem-exclusive.c | 276 ++ app/core/gimpitem-exclusive.h | 31 + app/core/gimpitem-linked.c | 187 + app/core/gimpitem-linked.h | 48 + app/core/gimpitem-preview.c | 133 + app/core/gimpitem-preview.h | 40 + app/core/gimpitem.c | 2689 ++++++++++++++ app/core/gimpitem.h | 405 +++ app/core/gimpitempropundo.c | 358 ++ app/core/gimpitempropundo.h | 63 + app/core/gimpitemstack.c | 348 ++ app/core/gimpitemstack.h | 66 + app/core/gimpitemtree.c | 714 ++++ app/core/gimpitemtree.h | 89 + app/core/gimpitemundo.c | 139 + app/core/gimpitemundo.h | 52 + app/core/gimplayer-floating-selection.c | 332 ++ app/core/gimplayer-floating-selection.h | 33 + app/core/gimplayer-new.c | 253 ++ app/core/gimplayer-new.h | 51 + app/core/gimplayer.c | 2943 ++++++++++++++++ app/core/gimplayer.h | 243 ++ app/core/gimplayermask.c | 297 ++ app/core/gimplayermask.h | 61 + app/core/gimplayermaskpropundo.c | 122 + app/core/gimplayermaskpropundo.h | 53 + app/core/gimplayermaskundo.c | 195 ++ app/core/gimplayermaskundo.h | 52 + app/core/gimplayerpropundo.c | 157 + app/core/gimplayerpropundo.h | 57 + app/core/gimplayerstack.c | 243 ++ app/core/gimplayerstack.h | 51 + app/core/gimplayerundo.c | 212 ++ app/core/gimplayerundo.h | 54 + app/core/gimplineart.c | 2979 ++++++++++++++++ app/core/gimplineart.h | 73 + app/core/gimplist.c | 691 ++++ app/core/gimplist.h | 70 + app/core/gimpmarshal.c | 1901 ++++++++++ app/core/gimpmarshal.h | 378 ++ app/core/gimpmarshal.list | 74 + app/core/gimpmaskundo.c | 292 ++ app/core/gimpmaskundo.h | 58 + app/core/gimpmybrush-load.c | 153 + app/core/gimpmybrush-load.h | 33 + app/core/gimpmybrush-private.h | 35 + app/core/gimpmybrush.c | 281 ++ app/core/gimpmybrush.h | 66 + app/core/gimpobject.c | 512 +++ app/core/gimpobject.h | 74 + app/core/gimpobjectqueue.c | 198 ++ app/core/gimpobjectqueue.h | 66 + app/core/gimppaintinfo.c | 142 + app/core/gimppaintinfo.h | 69 + app/core/gimppalette-import.c | 566 +++ app/core/gimppalette-import.h | 48 + app/core/gimppalette-load.c | 702 ++++ app/core/gimppalette-load.h | 66 + app/core/gimppalette-save.c | 77 + app/core/gimppalette-save.h | 28 + app/core/gimppalette.c | 721 ++++ app/core/gimppalette.h | 103 + app/core/gimppalettemru.c | 230 ++ app/core/gimppalettemru.h | 62 + app/core/gimpparamspecs-desc.c | 193 ++ app/core/gimpparamspecs-desc.h | 25 + app/core/gimpparamspecs-duplicate.c | 269 ++ app/core/gimpparamspecs-duplicate.h | 28 + app/core/gimpparamspecs.c | 2925 ++++++++++++++++ app/core/gimpparamspecs.h | 904 +++++ app/core/gimpparasitelist.c | 453 +++ app/core/gimpparasitelist.h | 69 + app/core/gimppattern-header.h | 48 + app/core/gimppattern-load.c | 220 ++ app/core/gimppattern-load.h | 35 + app/core/gimppattern-save.c | 87 + app/core/gimppattern-save.h | 28 + app/core/gimppattern.c | 287 ++ app/core/gimppattern.h | 58 + app/core/gimppatternclipboard.c | 213 ++ app/core/gimppatternclipboard.h | 56 + app/core/gimppdbprogress.c | 408 +++ app/core/gimppdbprogress.h | 66 + app/core/gimppickable-auto-shrink.c | 312 ++ app/core/gimppickable-auto-shrink.h | 41 + app/core/gimppickable-contiguous-region.cc | 1123 ++++++ app/core/gimppickable-contiguous-region.h | 43 + app/core/gimppickable.c | 378 ++ app/core/gimppickable.h | 110 + app/core/gimpprogress.c | 266 ++ app/core/gimpprogress.h | 99 + app/core/gimpprojectable.c | 262 ++ app/core/gimpprojectable.h | 90 + app/core/gimpprojection.c | 1132 ++++++ app/core/gimpprojection.h | 83 + app/core/gimpsamplepoint.c | 215 ++ app/core/gimpsamplepoint.h | 68 + app/core/gimpsamplepointundo.c | 121 + app/core/gimpsamplepointundo.h | 54 + app/core/gimpscanconvert.c | 647 ++++ app/core/gimpscanconvert.h | 81 + app/core/gimpselection.c | 863 +++++ app/core/gimpselection.h | 76 + app/core/gimpsettings.c | 195 ++ app/core/gimpsettings.h | 57 + app/core/gimpstrokeoptions.c | 638 ++++ app/core/gimpstrokeoptions.h | 81 + app/core/gimpsubprogress.c | 317 ++ app/core/gimpsubprogress.h | 59 + app/core/gimpsymmetry-mandala.c | 574 +++ app/core/gimpsymmetry-mandala.h | 61 + app/core/gimpsymmetry-mirror.c | 738 ++++ app/core/gimpsymmetry-mirror.h | 62 + app/core/gimpsymmetry-tiling.c | 442 +++ app/core/gimpsymmetry-tiling.h | 58 + app/core/gimpsymmetry.c | 591 ++++ app/core/gimpsymmetry.h | 106 + app/core/gimptag.c | 442 +++ app/core/gimptag.h | 80 + app/core/gimptagcache.c | 646 ++++ app/core/gimptagcache.h | 63 + app/core/gimptagged.c | 260 ++ app/core/gimptagged.h | 72 + app/core/gimptaggedcontainer.c | 483 +++ app/core/gimptaggedcontainer.h | 67 + app/core/gimptempbuf.c | 450 +++ app/core/gimptempbuf.h | 67 + app/core/gimptemplate.c | 595 ++++ app/core/gimptemplate.h | 97 + app/core/gimptilehandlerprojectable.c | 91 + app/core/gimptilehandlerprojectable.h | 64 + app/core/gimptoolgroup.c | 413 +++ app/core/gimptoolgroup.h | 68 + app/core/gimptoolinfo.c | 263 ++ app/core/gimptoolinfo.h | 95 + app/core/gimptoolitem.c | 227 ++ app/core/gimptoolitem.h | 70 + app/core/gimptooloptions.c | 378 ++ app/core/gimptooloptions.h | 73 + app/core/gimptoolpreset-load.c | 71 + app/core/gimptoolpreset-load.h | 31 + app/core/gimptoolpreset-save.c | 44 + app/core/gimptoolpreset-save.h | 28 + app/core/gimptoolpreset.c | 705 ++++ app/core/gimptoolpreset.h | 67 + app/core/gimptreehandler.c | 238 ++ app/core/gimptreehandler.h | 64 + app/core/gimptreeproxy.c | 634 ++++ app/core/gimptreeproxy.h | 66 + app/core/gimptriviallycancelablewaitable.c | 92 + app/core/gimptriviallycancelablewaitable.h | 57 + app/core/gimpuncancelablewaitable.c | 137 + app/core/gimpuncancelablewaitable.h | 54 + app/core/gimpundo.c | 585 ++++ app/core/gimpundo.h | 99 + app/core/gimpundostack.c | 208 ++ app/core/gimpundostack.h | 64 + app/core/gimpunit.c | 305 ++ app/core/gimpunit.h | 61 + app/core/gimpviewable.c | 1430 ++++++++ app/core/gimpviewable.h | 201 ++ app/core/gimpwaitable.c | 118 + app/core/gimpwaitable.h | 55 + 461 files changed, 144552 insertions(+) create mode 100644 app/core/Makefile.am create mode 100644 app/core/Makefile.in create mode 100644 app/core/core-enums.c create mode 100644 app/core/core-enums.h create mode 100644 app/core/core-types.h create mode 100644 app/core/gimp-atomic.c create mode 100644 app/core/gimp-atomic.h create mode 100644 app/core/gimp-batch.c create mode 100644 app/core/gimp-batch.h create mode 100644 app/core/gimp-cairo.c create mode 100644 app/core/gimp-cairo.h create mode 100644 app/core/gimp-contexts.c create mode 100644 app/core/gimp-contexts.h create mode 100644 app/core/gimp-data-factories.c create mode 100644 app/core/gimp-data-factories.h create mode 100644 app/core/gimp-edit.c create mode 100644 app/core/gimp-edit.h create mode 100644 app/core/gimp-filter-history.c create mode 100644 app/core/gimp-filter-history.h create mode 100644 app/core/gimp-gradients.c create mode 100644 app/core/gimp-gradients.h create mode 100644 app/core/gimp-gui.c create mode 100644 app/core/gimp-gui.h create mode 100644 app/core/gimp-internal-data.c create mode 100644 app/core/gimp-internal-data.h create mode 100644 app/core/gimp-memsize.c create mode 100644 app/core/gimp-memsize.h create mode 100644 app/core/gimp-modules.c create mode 100644 app/core/gimp-modules.h create mode 100644 app/core/gimp-palettes.c create mode 100644 app/core/gimp-palettes.h create mode 100644 app/core/gimp-parallel.cc create mode 100644 app/core/gimp-parallel.h create mode 100644 app/core/gimp-parasites.c create mode 100644 app/core/gimp-parasites.h create mode 100644 app/core/gimp-spawn.c create mode 100644 app/core/gimp-spawn.h create mode 100644 app/core/gimp-tags.c create mode 100644 app/core/gimp-tags.h create mode 100644 app/core/gimp-templates.c create mode 100644 app/core/gimp-templates.h create mode 100644 app/core/gimp-transform-3d-utils.c create mode 100644 app/core/gimp-transform-3d-utils.h create mode 100644 app/core/gimp-transform-resize.c create mode 100644 app/core/gimp-transform-resize.h create mode 100644 app/core/gimp-transform-utils.c create mode 100644 app/core/gimp-transform-utils.h create mode 100644 app/core/gimp-units.c create mode 100644 app/core/gimp-units.h create mode 100644 app/core/gimp-user-install.c create mode 100644 app/core/gimp-user-install.h create mode 100644 app/core/gimp-utils.c create mode 100644 app/core/gimp-utils.h create mode 100644 app/core/gimp.c create mode 100644 app/core/gimp.h create mode 100644 app/core/gimpasync.c create mode 100644 app/core/gimpasync.h create mode 100644 app/core/gimpasyncset.c create mode 100644 app/core/gimpasyncset.h create mode 100644 app/core/gimpauxitem.c create mode 100644 app/core/gimpauxitem.h create mode 100644 app/core/gimpauxitemundo.c create mode 100644 app/core/gimpauxitemundo.h create mode 100644 app/core/gimpbacktrace-backend.h create mode 100644 app/core/gimpbacktrace-linux.c create mode 100644 app/core/gimpbacktrace-none.c create mode 100644 app/core/gimpbacktrace-windows.c create mode 100644 app/core/gimpbacktrace.h create mode 100644 app/core/gimpbezierdesc.c create mode 100644 app/core/gimpbezierdesc.h create mode 100644 app/core/gimpboundary.c create mode 100644 app/core/gimpboundary.h create mode 100644 app/core/gimpbrush-boundary.c create mode 100644 app/core/gimpbrush-boundary.h create mode 100644 app/core/gimpbrush-header.h create mode 100644 app/core/gimpbrush-load.c create mode 100644 app/core/gimpbrush-load.h create mode 100644 app/core/gimpbrush-mipmap.cc create mode 100644 app/core/gimpbrush-mipmap.h create mode 100644 app/core/gimpbrush-private.h create mode 100644 app/core/gimpbrush-save.c create mode 100644 app/core/gimpbrush-save.h create mode 100644 app/core/gimpbrush-transform.cc create mode 100644 app/core/gimpbrush-transform.h create mode 100644 app/core/gimpbrush.c create mode 100644 app/core/gimpbrush.h create mode 100644 app/core/gimpbrushcache.c create mode 100644 app/core/gimpbrushcache.h create mode 100644 app/core/gimpbrushclipboard.c create mode 100644 app/core/gimpbrushclipboard.h create mode 100644 app/core/gimpbrushgenerated-load.c create mode 100644 app/core/gimpbrushgenerated-load.h create mode 100644 app/core/gimpbrushgenerated-save.c create mode 100644 app/core/gimpbrushgenerated-save.h create mode 100644 app/core/gimpbrushgenerated.c create mode 100644 app/core/gimpbrushgenerated.h create mode 100644 app/core/gimpbrushpipe-load.c create mode 100644 app/core/gimpbrushpipe-load.h create mode 100644 app/core/gimpbrushpipe-save.c create mode 100644 app/core/gimpbrushpipe-save.h create mode 100644 app/core/gimpbrushpipe.c create mode 100644 app/core/gimpbrushpipe.h create mode 100644 app/core/gimpbuffer.c create mode 100644 app/core/gimpbuffer.h create mode 100644 app/core/gimpcancelable.c create mode 100644 app/core/gimpcancelable.h create mode 100644 app/core/gimpchannel-combine.c create mode 100644 app/core/gimpchannel-combine.h create mode 100644 app/core/gimpchannel-select.c create mode 100644 app/core/gimpchannel-select.h create mode 100644 app/core/gimpchannel.c create mode 100644 app/core/gimpchannel.h create mode 100644 app/core/gimpchannelpropundo.c create mode 100644 app/core/gimpchannelpropundo.h create mode 100644 app/core/gimpchannelundo.c create mode 100644 app/core/gimpchannelundo.h create mode 100644 app/core/gimpchunkiterator.c create mode 100644 app/core/gimpchunkiterator.h create mode 100644 app/core/gimpcontainer-filter.c create mode 100644 app/core/gimpcontainer-filter.h create mode 100644 app/core/gimpcontainer.c create mode 100644 app/core/gimpcontainer.h create mode 100644 app/core/gimpcontext.c create mode 100644 app/core/gimpcontext.h create mode 100644 app/core/gimpcoords-interpolate.c create mode 100644 app/core/gimpcoords-interpolate.h create mode 100644 app/core/gimpcoords.c create mode 100644 app/core/gimpcoords.h create mode 100644 app/core/gimpcurve-load.c create mode 100644 app/core/gimpcurve-load.h create mode 100644 app/core/gimpcurve-map.c create mode 100644 app/core/gimpcurve-map.h create mode 100644 app/core/gimpcurve-save.c create mode 100644 app/core/gimpcurve-save.h create mode 100644 app/core/gimpcurve.c create mode 100644 app/core/gimpcurve.h create mode 100644 app/core/gimpdashpattern.c create mode 100644 app/core/gimpdashpattern.h create mode 100644 app/core/gimpdata.c create mode 100644 app/core/gimpdata.h create mode 100644 app/core/gimpdatafactory.c create mode 100644 app/core/gimpdatafactory.h create mode 100644 app/core/gimpdataloaderfactory.c create mode 100644 app/core/gimpdataloaderfactory.h create mode 100644 app/core/gimpdocumentlist.c create mode 100644 app/core/gimpdocumentlist.h create mode 100644 app/core/gimpdrawable-bucket-fill.c create mode 100644 app/core/gimpdrawable-bucket-fill.h create mode 100644 app/core/gimpdrawable-combine.c create mode 100644 app/core/gimpdrawable-combine.h create mode 100644 app/core/gimpdrawable-edit.c create mode 100644 app/core/gimpdrawable-edit.h create mode 100644 app/core/gimpdrawable-equalize.c create mode 100644 app/core/gimpdrawable-equalize.h create mode 100644 app/core/gimpdrawable-fill.c create mode 100644 app/core/gimpdrawable-fill.h create mode 100644 app/core/gimpdrawable-filters.c create mode 100644 app/core/gimpdrawable-filters.h create mode 100644 app/core/gimpdrawable-floating-selection.c create mode 100644 app/core/gimpdrawable-floating-selection.h create mode 100644 app/core/gimpdrawable-foreground-extract.c create mode 100644 app/core/gimpdrawable-foreground-extract.h create mode 100644 app/core/gimpdrawable-gradient.c create mode 100644 app/core/gimpdrawable-gradient.h create mode 100644 app/core/gimpdrawable-histogram.c create mode 100644 app/core/gimpdrawable-histogram.h create mode 100644 app/core/gimpdrawable-levels.c create mode 100644 app/core/gimpdrawable-levels.h create mode 100644 app/core/gimpdrawable-offset.c create mode 100644 app/core/gimpdrawable-offset.h create mode 100644 app/core/gimpdrawable-operation.c create mode 100644 app/core/gimpdrawable-operation.h create mode 100644 app/core/gimpdrawable-preview.c create mode 100644 app/core/gimpdrawable-preview.h create mode 100644 app/core/gimpdrawable-private.h create mode 100644 app/core/gimpdrawable-shadow.c create mode 100644 app/core/gimpdrawable-shadow.h create mode 100644 app/core/gimpdrawable-stroke.c create mode 100644 app/core/gimpdrawable-stroke.h create mode 100644 app/core/gimpdrawable-transform.c create mode 100644 app/core/gimpdrawable-transform.h create mode 100644 app/core/gimpdrawable.c create mode 100644 app/core/gimpdrawable.h create mode 100644 app/core/gimpdrawablefilter.c create mode 100644 app/core/gimpdrawablefilter.h create mode 100644 app/core/gimpdrawablemodundo.c create mode 100644 app/core/gimpdrawablemodundo.h create mode 100644 app/core/gimpdrawablestack.c create mode 100644 app/core/gimpdrawablestack.h create mode 100644 app/core/gimpdrawableundo.c create mode 100644 app/core/gimpdrawableundo.h create mode 100644 app/core/gimpdynamics-load.c create mode 100644 app/core/gimpdynamics-load.h create mode 100644 app/core/gimpdynamics-save.c create mode 100644 app/core/gimpdynamics-save.h create mode 100644 app/core/gimpdynamics.c create mode 100644 app/core/gimpdynamics.h create mode 100644 app/core/gimpdynamicsoutput.c create mode 100644 app/core/gimpdynamicsoutput.h create mode 100644 app/core/gimperror.c create mode 100644 app/core/gimperror.h create mode 100644 app/core/gimpfilloptions.c create mode 100644 app/core/gimpfilloptions.h create mode 100644 app/core/gimpfilter.c create mode 100644 app/core/gimpfilter.h create mode 100644 app/core/gimpfilteredcontainer.c create mode 100644 app/core/gimpfilteredcontainer.h create mode 100644 app/core/gimpfilterstack.c create mode 100644 app/core/gimpfilterstack.h create mode 100644 app/core/gimpfloatingselectionundo.c create mode 100644 app/core/gimpfloatingselectionundo.h create mode 100644 app/core/gimpgradient-load.c create mode 100644 app/core/gimpgradient-load.h create mode 100644 app/core/gimpgradient-save.c create mode 100644 app/core/gimpgradient-save.h create mode 100644 app/core/gimpgradient.c create mode 100644 app/core/gimpgradient.h create mode 100644 app/core/gimpgrid.c create mode 100644 app/core/gimpgrid.h create mode 100644 app/core/gimpgrouplayer.c create mode 100644 app/core/gimpgrouplayer.h create mode 100644 app/core/gimpgrouplayerundo.c create mode 100644 app/core/gimpgrouplayerundo.h create mode 100644 app/core/gimpguide.c create mode 100644 app/core/gimpguide.h create mode 100644 app/core/gimpguideundo.c create mode 100644 app/core/gimpguideundo.h create mode 100644 app/core/gimphistogram.c create mode 100644 app/core/gimphistogram.h create mode 100644 app/core/gimpidtable.c create mode 100644 app/core/gimpidtable.h create mode 100644 app/core/gimpimage-arrange.c create mode 100644 app/core/gimpimage-arrange.h create mode 100644 app/core/gimpimage-color-profile.c create mode 100644 app/core/gimpimage-color-profile.h create mode 100644 app/core/gimpimage-colormap.c create mode 100644 app/core/gimpimage-colormap.h create mode 100644 app/core/gimpimage-convert-data.h create mode 100644 app/core/gimpimage-convert-fsdither.h create mode 100644 app/core/gimpimage-convert-indexed.c create mode 100644 app/core/gimpimage-convert-indexed.h create mode 100644 app/core/gimpimage-convert-precision.c create mode 100644 app/core/gimpimage-convert-precision.h create mode 100644 app/core/gimpimage-convert-type.c create mode 100644 app/core/gimpimage-convert-type.h create mode 100644 app/core/gimpimage-crop.c create mode 100644 app/core/gimpimage-crop.h create mode 100644 app/core/gimpimage-duplicate.c create mode 100644 app/core/gimpimage-duplicate.h create mode 100644 app/core/gimpimage-flip.c create mode 100644 app/core/gimpimage-flip.h create mode 100644 app/core/gimpimage-grid.c create mode 100644 app/core/gimpimage-grid.h create mode 100644 app/core/gimpimage-guides.c create mode 100644 app/core/gimpimage-guides.h create mode 100644 app/core/gimpimage-item-list.c create mode 100644 app/core/gimpimage-item-list.h create mode 100644 app/core/gimpimage-merge.c create mode 100644 app/core/gimpimage-merge.h create mode 100644 app/core/gimpimage-metadata.c create mode 100644 app/core/gimpimage-metadata.h create mode 100644 app/core/gimpimage-new.c create mode 100644 app/core/gimpimage-new.h create mode 100644 app/core/gimpimage-pick-color.c create mode 100644 app/core/gimpimage-pick-color.h create mode 100644 app/core/gimpimage-pick-item.c create mode 100644 app/core/gimpimage-pick-item.h create mode 100644 app/core/gimpimage-preview.c create mode 100644 app/core/gimpimage-preview.h create mode 100644 app/core/gimpimage-private.h create mode 100644 app/core/gimpimage-quick-mask.c create mode 100644 app/core/gimpimage-quick-mask.h create mode 100644 app/core/gimpimage-resize.c create mode 100644 app/core/gimpimage-resize.h create mode 100644 app/core/gimpimage-rotate.c create mode 100644 app/core/gimpimage-rotate.h create mode 100644 app/core/gimpimage-sample-points.c create mode 100644 app/core/gimpimage-sample-points.h create mode 100644 app/core/gimpimage-scale.c create mode 100644 app/core/gimpimage-scale.h create mode 100644 app/core/gimpimage-snap.c create mode 100644 app/core/gimpimage-snap.h create mode 100644 app/core/gimpimage-symmetry.c create mode 100644 app/core/gimpimage-symmetry.h create mode 100644 app/core/gimpimage-transform.c create mode 100644 app/core/gimpimage-transform.h create mode 100644 app/core/gimpimage-undo-push.c create mode 100644 app/core/gimpimage-undo-push.h create mode 100644 app/core/gimpimage-undo.c create mode 100644 app/core/gimpimage-undo.h create mode 100644 app/core/gimpimage.c create mode 100644 app/core/gimpimage.h create mode 100644 app/core/gimpimagefile.c create mode 100644 app/core/gimpimagefile.h create mode 100644 app/core/gimpimageproxy.c create mode 100644 app/core/gimpimageproxy.h create mode 100644 app/core/gimpimageundo.c create mode 100644 app/core/gimpimageundo.h create mode 100644 app/core/gimpitem-exclusive.c create mode 100644 app/core/gimpitem-exclusive.h create mode 100644 app/core/gimpitem-linked.c create mode 100644 app/core/gimpitem-linked.h create mode 100644 app/core/gimpitem-preview.c create mode 100644 app/core/gimpitem-preview.h create mode 100644 app/core/gimpitem.c create mode 100644 app/core/gimpitem.h create mode 100644 app/core/gimpitempropundo.c create mode 100644 app/core/gimpitempropundo.h create mode 100644 app/core/gimpitemstack.c create mode 100644 app/core/gimpitemstack.h create mode 100644 app/core/gimpitemtree.c create mode 100644 app/core/gimpitemtree.h create mode 100644 app/core/gimpitemundo.c create mode 100644 app/core/gimpitemundo.h create mode 100644 app/core/gimplayer-floating-selection.c create mode 100644 app/core/gimplayer-floating-selection.h create mode 100644 app/core/gimplayer-new.c create mode 100644 app/core/gimplayer-new.h create mode 100644 app/core/gimplayer.c create mode 100644 app/core/gimplayer.h create mode 100644 app/core/gimplayermask.c create mode 100644 app/core/gimplayermask.h create mode 100644 app/core/gimplayermaskpropundo.c create mode 100644 app/core/gimplayermaskpropundo.h create mode 100644 app/core/gimplayermaskundo.c create mode 100644 app/core/gimplayermaskundo.h create mode 100644 app/core/gimplayerpropundo.c create mode 100644 app/core/gimplayerpropundo.h create mode 100644 app/core/gimplayerstack.c create mode 100644 app/core/gimplayerstack.h create mode 100644 app/core/gimplayerundo.c create mode 100644 app/core/gimplayerundo.h create mode 100644 app/core/gimplineart.c create mode 100644 app/core/gimplineart.h create mode 100644 app/core/gimplist.c create mode 100644 app/core/gimplist.h create mode 100644 app/core/gimpmarshal.c create mode 100644 app/core/gimpmarshal.h create mode 100644 app/core/gimpmarshal.list create mode 100644 app/core/gimpmaskundo.c create mode 100644 app/core/gimpmaskundo.h create mode 100644 app/core/gimpmybrush-load.c create mode 100644 app/core/gimpmybrush-load.h create mode 100644 app/core/gimpmybrush-private.h create mode 100644 app/core/gimpmybrush.c create mode 100644 app/core/gimpmybrush.h create mode 100644 app/core/gimpobject.c create mode 100644 app/core/gimpobject.h create mode 100644 app/core/gimpobjectqueue.c create mode 100644 app/core/gimpobjectqueue.h create mode 100644 app/core/gimppaintinfo.c create mode 100644 app/core/gimppaintinfo.h create mode 100644 app/core/gimppalette-import.c create mode 100644 app/core/gimppalette-import.h create mode 100644 app/core/gimppalette-load.c create mode 100644 app/core/gimppalette-load.h create mode 100644 app/core/gimppalette-save.c create mode 100644 app/core/gimppalette-save.h create mode 100644 app/core/gimppalette.c create mode 100644 app/core/gimppalette.h create mode 100644 app/core/gimppalettemru.c create mode 100644 app/core/gimppalettemru.h create mode 100644 app/core/gimpparamspecs-desc.c create mode 100644 app/core/gimpparamspecs-desc.h create mode 100644 app/core/gimpparamspecs-duplicate.c create mode 100644 app/core/gimpparamspecs-duplicate.h create mode 100644 app/core/gimpparamspecs.c create mode 100644 app/core/gimpparamspecs.h create mode 100644 app/core/gimpparasitelist.c create mode 100644 app/core/gimpparasitelist.h create mode 100644 app/core/gimppattern-header.h create mode 100644 app/core/gimppattern-load.c create mode 100644 app/core/gimppattern-load.h create mode 100644 app/core/gimppattern-save.c create mode 100644 app/core/gimppattern-save.h create mode 100644 app/core/gimppattern.c create mode 100644 app/core/gimppattern.h create mode 100644 app/core/gimppatternclipboard.c create mode 100644 app/core/gimppatternclipboard.h create mode 100644 app/core/gimppdbprogress.c create mode 100644 app/core/gimppdbprogress.h create mode 100644 app/core/gimppickable-auto-shrink.c create mode 100644 app/core/gimppickable-auto-shrink.h create mode 100644 app/core/gimppickable-contiguous-region.cc create mode 100644 app/core/gimppickable-contiguous-region.h create mode 100644 app/core/gimppickable.c create mode 100644 app/core/gimppickable.h create mode 100644 app/core/gimpprogress.c create mode 100644 app/core/gimpprogress.h create mode 100644 app/core/gimpprojectable.c create mode 100644 app/core/gimpprojectable.h create mode 100644 app/core/gimpprojection.c create mode 100644 app/core/gimpprojection.h create mode 100644 app/core/gimpsamplepoint.c create mode 100644 app/core/gimpsamplepoint.h create mode 100644 app/core/gimpsamplepointundo.c create mode 100644 app/core/gimpsamplepointundo.h create mode 100644 app/core/gimpscanconvert.c create mode 100644 app/core/gimpscanconvert.h create mode 100644 app/core/gimpselection.c create mode 100644 app/core/gimpselection.h create mode 100644 app/core/gimpsettings.c create mode 100644 app/core/gimpsettings.h create mode 100644 app/core/gimpstrokeoptions.c create mode 100644 app/core/gimpstrokeoptions.h create mode 100644 app/core/gimpsubprogress.c create mode 100644 app/core/gimpsubprogress.h create mode 100644 app/core/gimpsymmetry-mandala.c create mode 100644 app/core/gimpsymmetry-mandala.h create mode 100644 app/core/gimpsymmetry-mirror.c create mode 100644 app/core/gimpsymmetry-mirror.h create mode 100644 app/core/gimpsymmetry-tiling.c create mode 100644 app/core/gimpsymmetry-tiling.h create mode 100644 app/core/gimpsymmetry.c create mode 100644 app/core/gimpsymmetry.h create mode 100644 app/core/gimptag.c create mode 100644 app/core/gimptag.h create mode 100644 app/core/gimptagcache.c create mode 100644 app/core/gimptagcache.h create mode 100644 app/core/gimptagged.c create mode 100644 app/core/gimptagged.h create mode 100644 app/core/gimptaggedcontainer.c create mode 100644 app/core/gimptaggedcontainer.h create mode 100644 app/core/gimptempbuf.c create mode 100644 app/core/gimptempbuf.h create mode 100644 app/core/gimptemplate.c create mode 100644 app/core/gimptemplate.h create mode 100644 app/core/gimptilehandlerprojectable.c create mode 100644 app/core/gimptilehandlerprojectable.h create mode 100644 app/core/gimptoolgroup.c create mode 100644 app/core/gimptoolgroup.h create mode 100644 app/core/gimptoolinfo.c create mode 100644 app/core/gimptoolinfo.h create mode 100644 app/core/gimptoolitem.c create mode 100644 app/core/gimptoolitem.h create mode 100644 app/core/gimptooloptions.c create mode 100644 app/core/gimptooloptions.h create mode 100644 app/core/gimptoolpreset-load.c create mode 100644 app/core/gimptoolpreset-load.h create mode 100644 app/core/gimptoolpreset-save.c create mode 100644 app/core/gimptoolpreset-save.h create mode 100644 app/core/gimptoolpreset.c create mode 100644 app/core/gimptoolpreset.h create mode 100644 app/core/gimptreehandler.c create mode 100644 app/core/gimptreehandler.h create mode 100644 app/core/gimptreeproxy.c create mode 100644 app/core/gimptreeproxy.h create mode 100644 app/core/gimptriviallycancelablewaitable.c create mode 100644 app/core/gimptriviallycancelablewaitable.h create mode 100644 app/core/gimpuncancelablewaitable.c create mode 100644 app/core/gimpuncancelablewaitable.h create mode 100644 app/core/gimpundo.c create mode 100644 app/core/gimpundo.h create mode 100644 app/core/gimpundostack.c create mode 100644 app/core/gimpundostack.h create mode 100644 app/core/gimpunit.c create mode 100644 app/core/gimpunit.h create mode 100644 app/core/gimpviewable.c create mode 100644 app/core/gimpviewable.h create mode 100644 app/core/gimpwaitable.c create mode 100644 app/core/gimpwaitable.h (limited to 'app/core') 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 \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 \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 +#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", "<>"), 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 . + */ + +#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="<>" >*/ + + 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 . + */ + +#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 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 . + */ +#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 . + */ + +#include "config.h" + +#include +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include +#include + +#include +#include + +#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 . + */ + +#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 + * + * Some code here is based on code from librsvg that was originally + * written by Raph Levien 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 . + */ + +#include "config.h" + +#include +#include +#include + +#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 + * + * Some code here is based on code from librsvg that was originally + * written by Raph Levien 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 . + */ + +#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 . + */ + +#include "config.h" + +#include +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include +#include +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include + +#include +#include + +#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 . + */ + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#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 . + */ + +#include "config.h" + +#include +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include + +#include +#include +#include + +#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 . + */ + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#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 . + */ + + +#include "config.h" + +#include +#include + +#ifdef HAVE_UNISTD_H +#include +#endif + +#ifdef G_OS_WIN32 +#include +#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 . + */ + +#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 + +template +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 +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 +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 +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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#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, ¤t); + + 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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#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 . + */ + +#include "config.h" + +#include + +#ifdef HAVE_VFORK +#include +#include +#include +#include +#endif + +#ifdef HAVE_FCNTL_H +#include +#endif + +#ifdef G_OS_WIN32 +#include +#include +#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 . + */ + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#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, "\n"); + g_string_append (tags_installer.buf, "\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\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 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, " \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, " %s\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 . + */ + +#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 . + */ + +#include "config.h" + +#include + +#include +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include + +#include +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include + +#include + +#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 . + */ + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/* This file contains functions to load & save the file containing the + * user-defined size units, when the application starts/finished. + */ + +#include "config.h" + +#include + +#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 . + */ + +#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 . + */ + +/* 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 +#include +#include + +#include + +#ifdef HAVE_UNISTD_H +#include +#endif + +#ifdef PLATFORM_OSX +#include +#endif + +#include +#include + +#ifdef G_OS_WIN32 +#include +#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 \ + "\"/buffers/buffers-paste-as-new\"" "|" \ + "\"/edit/edit-paste-as-new\"" "|" \ + "\"/file/file-export\"" "|" \ + "\"/file/file-export-to\"" "|" \ + "\"/layers/layers-text-tool\"" "|" \ + "\"/plug-in/plug-in-gauss\"" "|" \ + "\"/tools/tools-value-[1-4]-.*\"" "|" \ + "\"/vectors/vectors-path-tool\"" "|" \ + "\"/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, "\"/buffers/buffers-paste-as-new\"") == 0) + { + g_string_append (new_value, "\"/buffers/buffers-paste-as-new-image\""); + } + else if (g_strcmp0 (match, "\"/edit/edit-paste-as-new\"") == 0) + { + g_string_append (new_value, "\"/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, "\"/file/file-export\"") == 0) + { + g_string_append (new_value, "\"/file/file-export-as\""); + } + else if (g_strcmp0 (match, "\"/file/file-export-to\"") == 0) + { + g_string_append (new_value, "\"/file/file-export\""); + } + else if (g_strcmp0 (match, "\"/layers/layers-text-tool\"") == 0) + { + g_string_append (new_value, "\"/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, "\"/plug-in/plug-in-gauss\"") == 0) + { + g_string_append (new_value, "\"/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, "\"/tools/tools-value-1-")) + { + g_string_append (new_value, "\"/tools/tools-opacity-"); + g_string_append (new_value, match + 31); + } + else if (g_str_has_prefix (match, "\"/tools/tools-value-2-")) + { + g_string_append (new_value, "\"/tools/tools-size-"); + g_string_append (new_value, match + 31); + } + else if (g_str_has_prefix (match, "\"/tools/tools-value-3-")) + { + g_string_append (new_value, "\"/tools/tools-aspect-"); + g_string_append (new_value, match + 31); + } + else if (g_str_has_prefix (match, "\"/tools/tools-value-4-")) + { + g_string_append (new_value, "\"/tools/tools-angle-"); + g_string_append (new_value, match + 31); + } + else if (g_strcmp0 (match, "\"/vectors/vectors-path-tool\"") == 0) + { + g_string_append (new_value, "\"/vectors/vectors-edit\""); + } + else if (g_strcmp0 (match, "\"/tools/tools-blend\"") == 0) + { + g_string_append (new_value, "\"/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 . + */ + +#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 . + */ + +#include "config.h" + +#include +#include +#include +#include + +#ifdef HAVE__NL_MEASUREMENT_MEASUREMENT +#include +#endif + +#ifdef HAVE_UNISTD_H +#include +#endif + +#include + +#ifdef G_OS_WIN32 +#define _WIN32_WINNT 0x0500 +#include +#include +#endif + +#if defined(G_OS_UNIX) && defined(HAVE_EXECINFO_H) +/* For get_backtrace() */ +#include +#include +#include +#endif + +#include +#include +#include +#include + +#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 < etc. this is obvious, for ￿ 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 . + */ + +#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 . + */ + +#include "config.h" + +#include /* strlen */ + +#include +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include +#include + +#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 . + */ + +#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 . + */ + +#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 . + */ + + +#define _GNU_SOURCE + + +#include "config.h" + +#include + +#include "gimpbacktrace-backend.h" + + +#ifdef GIMP_BACKTRACE_BACKEND_LINUX + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_LIBBACKTRACE +#include +#endif + +#ifdef HAVE_LIBUNWIND +#define UNW_LOCAL_ONLY +#include +#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 . + */ + + +#include "config.h" + +#include + +#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 . + */ + + +#include "config.h" + +#include + +#include "gimpbacktrace-backend.h" + + +#ifdef GIMP_BACKTRACE_BACKEND_WINDOWS + + +#include +#include +#include +#include + +#include + +#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 . + */ + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#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 . + */ + +#include "config.h" + +#include +#include + +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include +#include +#include + +#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 . + */ + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include +#include + +#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 */ +/* 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 . + */ + +#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 . + */ + +#include "config.h" + +#include + +#include +#include + +#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 +struct MipmapTraits; + +template <> +struct MipmapTraits +{ + 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 +{ + 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 +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::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::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::mix (src[c], src[src_stride + c]); + + src += 2 * src_stride; + dest += dest_stride; + } + + src0 += N; + dest0 += N; + } + }); + + return destination; + } +}; + +template +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 ()); + + case 3: + return func (MipmapAlgorithms ()); + } + } + else if (type == babl_type ("float")) + { + switch (n_components) + { + case 1: + return func (MipmapAlgorithms ()); + + case 3: + return func (MipmapAlgorithms ()); + } + } + + 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 . + */ + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include + +#include +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include +#include +#include + +#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 . + */ + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include +#include + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#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 . + */ + +#include "config.h" + +#include + +#include + +#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, ¶mstring, 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 . + */ + +#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 . + */ + +#include "config.h" + +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include +#include + +#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 (¶ms); + gimp_pixpipe_params_parse (paramstring, ¶ms); + + 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 (¶ms); + + 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 . + */ + +#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 . + */ + +#include "config.h" + +#include +#include +#include + +#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 . + */ + +#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 . + */ + + +#include "config.h" + +#include +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include + +#include +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include + +#include +#include +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include + +#include +#include +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include +#include + +#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 . + */ + +#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 . + */ + + +#include "config.h" + +#include + +#include +#include +#include + +#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 . + */ + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#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 . + */ + +#include "config.h" + +#include +#include +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include + +#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 . + */ + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#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 . + */ + +#include "config.h" + +#include +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include + +#include +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include +#include /* memcmp */ + +#include +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include + +#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 . + */ + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include +#include + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include +#include + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#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 . + */ + +#include "config.h" + +#include +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include +#define GEGL_ITERATOR2_API +#include +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include +#include +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include +#include +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include +#include +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include +#include +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include +#include +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include +#include +#include + +#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 . + */ + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include +#include + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#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 . + */ + +#include "config.h" + +#include +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include + +#include +#include +#include + +#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 . + */ + +#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 + * Sven Neumann + * 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 . + */ + +#include "config.h" + +#include +#include + +#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 + * Sven Neumann + * 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 . + */ + +#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 . + */ + +#include "config.h" + +#include + +#include +#include +#include + +#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 . + */ + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include +#include +#include + +#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 . + */ + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include +#include + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#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 . + */ + +#include "config.h" + +#include +#include + +#include +#include +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include +#include +#include + +#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, + ©_x, + ©_y, + ©_width, + ©_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 . + */ + +#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 . + */ + +/* 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 +#include +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include +#include + +#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 . + */ + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#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 . + */ + +#include "config.h" + +#include +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include +#include +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include +#include + +#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 . + */ + +#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 + * 2011 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 . + */ + +#include "config.h" + +#include + +#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 + * 2011 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 . + */ + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#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 . + */ + +#include "config.h" + +#include +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include +#include +#include + +#include +#include +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include + +#include +#include +#include + +#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 . + */ + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include +#include +#include + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include +#include + +#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, + ©_bounds.x, + ©_bounds.y, + ©_bounds.width, + ©_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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#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 . + */ + +#include "config.h" + +#include +#include + +#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 . + */ + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#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 . + */ + +#include "config.h" + +#include +#include + +#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 . + */ + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include +#include +#include + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#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 . + */ + +#include "config.h" + +#include +#include + +#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 . + */ + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include +#include +#include + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#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 . + */ + +#include "config.h" + +#include + +#include +#include +#include + +#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 . + */ + +#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 . + */ + +/* 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 + +/* '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 . + */ + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/* + * 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 +#include +#include + +#include +#include +#include + +#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< %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<>1) ) >> R_SHIFT) +#define GSDF(g) ((g) >= ((HIST_G_ELEMS-1) << G_SHIFT) ? HIST_G_ELEMS-1 : \ + ((g) + ((1<>1) ) >> G_SHIFT) +#define BSDF(b) ((b) >= ((HIST_B_ELEMS-1) << B_SHIFT) ? HIST_B_ELEMS-1 : \ + ((b) + ((1<>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. + * + */ +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<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 . + */ + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include +#include + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#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 . + */ + +#include "config.h" + +#include +#include +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include +#include + +#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: " 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: " 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 . + */ + +#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 . + */ + +#include "config.h" + +#include +#include + +#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 . + */ + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#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 . + */ + +#include "config.h" + +#include +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include +#include + +#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 #GimpItems 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 . + */ + +#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 . + */ + +#include "config.h" + +#include +#include +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include +#include +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include +#include +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include +#include +#include + +#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 . + */ + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include +#include +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include +#include + +#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 . + */ + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#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 . + */ + +#include "config.h" + +#include +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include +#include + +#include +#include +#include +#include + +#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 (©)) + gimp_image_undo_push_image_parasite (image, + C_("undo-type", "Attach Parasite to Image"), + ©); + + /* 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, ©); + + if (push_undo && gimp_parasite_has_flag (©, GIMP_PARASITE_ATTACH_PARENT)) + { + gimp_parasite_shift_parent (©); + gimp_parasite_attach (image->gimp, ©); + } + + 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 . + */ + +#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 + * 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 . + */ + +#include "config.h" + +#include + +#include +#include + +#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 + * 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 . + */ + +#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 . + */ + +#include "config.h" + +#include +#include +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include + +#include +#include +#include + +#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 . + */ + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#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 . + */ + +#include "config.h" + +#include +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include + +#include +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include +#include + +#include +#include + +#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 (©)) + { + /* 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, ©); + } + else if (gimp_parasite_is_persistent (©) && + ! gimp_parasite_compare (©, + gimp_item_parasite_find + (item, gimp_parasite_name (©)))) + { + gimp_image_undo_push_cantundo (private->image, + C_("undo-type", "Attach Parasite to Item")); + } + } + + gimp_parasite_list_add (private->parasites, ©); + + if (gimp_parasite_has_flag (©, GIMP_PARASITE_ATTACH_PARENT)) + { + gimp_parasite_shift_parent (©); + gimp_image_parasite_attach (private->image, ©, TRUE); + } + else if (gimp_parasite_has_flag (©, GIMP_PARASITE_ATTACH_GRANDPARENT)) + { + gimp_parasite_shift_parent (©); + gimp_parasite_shift_parent (©); + gimp_parasite_attach (private->image->gimp, ©); + } + + if (gimp_item_is_attached (item) && + gimp_parasite_is_undoable (©)) + { + 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 . + */ + +#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 . + */ + +#include "config.h" + +#include +#include + +#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 . + */ + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include +#include +#include + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include +#include + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#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 . + */ + +#include "config.h" + +#include +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include +#include +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include +#include + +#include +#include +#include + +#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), + ©_x, ©_y, + ©_width, ©_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 . + */ + +#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 . + */ + +#include "config.h" + +#include +#include + +#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 " 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 . + */ + +#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 . + */ + +#include "config.h" + +#include +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include +#include + +#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 . + */ + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include /* strcmp */ + +#include + +#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 "#" */ + 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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#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 + +#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 + +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 . + */ + +#include "config.h" + +#include +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include +#include + +#include + +#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 . + */ + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include +#include +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include + +#include +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include +#include +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include + +#include +#include +#include + +#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.*);", 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 . + */ + +#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 . + */ + +#include "config.h" + +#include +#include +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include + +#include +#include +#include + +#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 . + */ + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include +#include + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#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 . + */ + +#include "config.h" + +#include + +#include + +#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 . + */ + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include +#include +#include + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#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 . + */ + +#include "config.h" + +#include +#include + +#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 . + */ + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include +#include + +#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, ¶site_name)) + break; + + token = G_TOKEN_INT; + + if (g_scanner_peek_next_token (scanner) != token) + goto cleanup; + + if (! gimp_scanner_parse_int (scanner, ¶site_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, ¶site_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, + ¶site_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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include +#include +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include + +#include +#include + +#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 . + */ + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include +#include + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#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 . + */ + +#include "config.h" + +#include + +#include +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include + +#include +#include +#include + +#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 . + */ + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/* 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 + +#include +#include +#include + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include +#include + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#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 . + */ + +#include "config.h" + +#include +#include + +#include +#include +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include + +#include +#include + +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include +#include +#include + +#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 . + */ + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include +#include + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include + +#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 . + */ + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include +#include +#include + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include +#include +#include + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#include +#include + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include +#include + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __GIMP_TAG_H__ +#define __GIMP_TAG_H__ + + +#include + + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#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, "\n"); + g_string_append (buf, "\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 \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, " %s\n", tag_string); + g_free (tag_string); + } + } + + g_string_append (buf, " \n"); + } + + g_string_append (buf, "\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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#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 . + */ + +#include "config.h" + +#include + +#include +#include +#include +#include +#include + +#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 . + */ + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/* This file contains the definition of the image template objects. + */ + +#include "config.h" + +#include +#include +#include + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#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 . + */ + +#include "config.h" + +#include +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include + +#include +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include + +#include +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include + +#include +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include +#include + +#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 . + */ + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#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 . + */ + +#include "config.h" + +#include +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include + +#include +#include + +#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 . + */ + +#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 . + */ + +#include "config.h" + +#include +#include + +#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 . + */ + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/* 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 + +#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 . + */ + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +#include +#include +#include + +#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 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#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 . + */ + + +#include "config.h" + +#include +#include + +#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 . + */ + +#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__ */ -- cgit v1.2.3