1
1
mirror of https://gitlab.gnome.org/GNOME/gimp.git synced 2025-10-06 05:22:40 +02:00

Compare commits

...

162 Commits

Author SHA1 Message Date
Jehan
14fed539c4 plug-ins: better GUI feedback when previewing zoom/offset keyframes.
For efficiency, when editing a frame offset or zoom at a given position,
I do not apply the keyframe immediately (which may update neighbour
frames by interpolation) since there may likely be many recomputations.
I still want the GUI to act as though this is just like any keyframe,
with the camera icon, reset buttons, etc.
2017-10-07 05:00:17 +02:00
Jehan
778625133d plug-ins: add zoom keyframing for the animation plug-in.
It's still very edgy, but that's a start.
2017-10-07 05:00:17 +02:00
Jehan
82db9d3baa plug-ins: recursively search layer groups for animation suites. 2017-10-07 05:00:17 +02:00
Jehan
b01f53b54c plug-ins: reorder the render queue when changing playback position. 2017-10-07 05:00:17 +02:00
Jehan
b0f34c93d6 plug-ins: order animation rendering queue from the current position. 2017-10-07 05:00:17 +02:00
Jehan
ee4be4703a plug-ins: add a spinner to indicate animation frames still rendering.
It doesn't give much details on how many frames, or which ones. But at
least there is some activity indicator which may give hints about why a
playback is not as expected.
2017-10-07 05:00:16 +02:00
Jehan
50ab0e1bde plug-ins: cels were not properly updating when adding/deleting. 2017-10-07 05:00:16 +02:00
Jehan
dcafccfd0e plug-ins: refresh the progress display after animation export. 2017-10-07 05:00:16 +02:00
Jehan
26d2a4dd7e plug-ins: allow interrupting animation export.
Adding a return value to the "loading" signal so that an export can be
interrupted in the middle (rather than waiting for the end of a long
export if you discover you made a mistake). When an export is in
progress, the export button becomes an export-cancel button.
Also add a parameterized progression message.
Finally make the full window non sensitive, but for the export-cancel
button.
2017-10-07 05:00:16 +02:00
Jehan
82a41c648a plug-ins: storyboard reload must take into account core image updates. 2017-10-07 05:00:16 +02:00
Jehan
ff757b0e76 plug-ins: animation_renderer_get_buffer() expects a frame position. 2017-10-07 05:00:16 +02:00
Jehan
225c64bedb plug-ins: allow forcing the cache creation when hitting "Reload".
Most of the time, we want to rely on cached data, but the "Reload"
button is explicitly made to create new frames even when no change has
been made (seemingly). This is a workaround to GIMP plugin limitations,
as long as we have no signal from GIMP core when a layer has been
changed.
2017-10-07 05:00:16 +02:00
Jehan
3f1af1a294 plug-ins: prevent unwanted signals upon destroying storyboard widgets.
For some reason, when a spin button had the focus, it was setting its
value to 0 upon being destroyed. We definitely don't want that. Rather
than blocking every spin button, I just make every child insensitive,
which does the deal.
2017-10-07 05:00:15 +02:00
Jehan
36d14167e0 plug-ins: simplify the keyframe view code a bit.
Getting rid of a now useless function.
2017-10-07 05:00:15 +02:00
Jehan
dbbd861913 plug-ins: layer names are probably not proper default comments.
The layer names still stay visible as mouse-hover tooltips on the
thumbnails.
2017-10-07 05:00:15 +02:00
Jehan
e1a0eaea9c plug-ins: update the animatic when moving panels.
Otherwise the render can't happen.
2017-10-07 05:00:15 +02:00
Jehan
9af5152811 plug-ins: animatic not properly re-rendered at panel duration changes. 2017-10-07 05:00:15 +02:00
Jehan
b0cbfdf56f plug-ins: dragging the display area only works for cel animations...
... for the time being.
2017-10-07 05:00:15 +02:00
Jehan
1e54537c8c plug-ins: update filtering when updating the layer view selection. 2017-10-07 05:00:15 +02:00
Jehan
23c6d5ea21 plug-ins: check for object existence before disconnecting handler. 2017-10-07 05:00:15 +02:00
Jehan
8b05db33e2 plug-ins: better check for queue existence in the idle.
When finalizing the animation renderer, the idle may still live slightly
longer after the queue has been freed. I check first for the priv
member, because for some reason, I had segfaults sometimes when shutting
down the plugin immediately after start, though I don't see how this
could not be present at the time the idle runs; yet it was so.
2017-10-07 05:00:14 +02:00
Jehan
d490db1141 plug-ins: uncheck filter when unfiltered layer is selected. 2017-10-07 05:00:14 +02:00
Jehan
e5bda45efa plug-ins: update filter behavior on layer groups in animation play.
Keep the groups and all its contents if its name passes the filter; keep
a group if it has any children which passes its filter (keep only these
children).
2017-10-07 05:00:14 +02:00
Jehan
483ffdb063 plug-ins: add a "Filter by level title" checkbox for cel animations.
Only view layers starting with the level title. Organized animators, who
name their layers accordingly, will find this less messy.
2017-10-07 05:00:14 +02:00
Jehan
21a32a7b99 plug-ins: fix camera keyframe creation.
I needed to test for the list item, not the data, which may be NULL even
when the item is there.
2017-10-07 05:00:14 +02:00
Jehan
03d6b2499e plug-ins: add a delete button on camera keyframing. 2017-10-07 05:00:14 +02:00
Jehan
50fc5ac8aa plug-ins: apply the camera preview on releasing mouse after dnd. 2017-10-07 05:00:14 +02:00
Jehan
e650bf80d5 plug-ins: allow dragging the display area for camera.
It's much too slow, but that's a first step.
2017-10-07 05:00:13 +02:00
Jehan
8d02bc5fcb plug-ins: add a camera preview concept to update only the current frame.
When moving the camera, it is useless to start big processing over all
impacted frames (because of interpolation of camera moves) immediately.
Just wait for when the user confirms the keyframe. Right now, that only
happens when switching to another frame.
2017-10-07 05:00:13 +02:00
Jehan
6381b376c1 plug-ins: always show the camera information.
Also display the camera data for the current position. Making position
and camera unrelated is just too confusing.
2017-10-07 05:00:13 +02:00
Jehan
646ca41bcd plug-ins: recreate the camera when cleaned-up.
animation_cel_animation_reset_defaults() free most memory. The camera
has to be recreated as well.
2017-10-07 05:00:13 +02:00
Jehan
61c9cddf53 plug-ins: prevent too much computation on constant camera update.
If we are updating the camera in the GUI with spin buttons or mouse
scroll, it will likely create a lot of events, hence useless
re-computations. Add a timeout constantly postponed when updating in
order to prevent this.
2017-10-07 05:00:13 +02:00
Jehan
1087dccf96 plug-ins: fix update when changing camera position.
Also do not limit camera position to the animation (nor the image) size.
Finally I don't show the unit menu. Actually I'd like to be able to show
the percentage unit, but not all the physical ones. I will have to have
a look at this later.
2017-10-07 05:00:13 +02:00
Jehan
e6f44694dc plug-ins: save animation's camera work in the parasite.
The camera still sucks. There should be drag'n drop capabilities, and
right now there are various bugs, like when adding new frames. That's
work-in-progress!
2017-10-07 05:00:13 +02:00
Jehan
6f9078d6b4 plug-ins: save the animation display size in the parasite. 2017-10-07 05:00:12 +02:00
Jehan
589569f948 plug-ins: add the concept of display size.
The size of the animation may be different from the one of the image.
This way, we can create bigger images than the display, for instance for
panning, zooming, or other effects.
2017-10-07 05:00:12 +02:00
Jehan
a795f35ff2 plug-ins: save onion-skin preference in the parasite. 2017-10-07 05:00:12 +02:00
Jehan
eb7804d9bc plug-ins: properly reorder panels on drag'n drop.
Do not reload the whole animation. Also properly reorder comments as
well.
2017-10-07 05:00:12 +02:00
Jehan
600110b2bc plug-ins: onion skins can now be configured.
Only up to 5 onion skins are allowed right now (that's already a lot to
not have a messy output). Frame position with identical contents are
overlooked (if we shoot on 2s or 3s, obviously displaying an onion skin
of identical content is meaningless).
This data is not yet saved in the parasite, but this will come.
2017-10-07 05:00:12 +02:00
Jehan
6219a3fe17 plug-ins: very basic onion-skinning.
Add onion-skinning when viewing in GIMP. I don't use onion-skinning in
the preview (plug-in display area) because that is mostly used for
painting. When actually previewing the video, you will want the "normal"
animation.
This is right now hard-coded in the sync feature (when double-clicking
the frame) but in the hand, this will have to be customizable. Also the
limitation is that if you wanted to use opacity feature in the finale
rendering, as well as various kind of effects.
Also set the last layer of the current position as the active layer.
There are definitely better logics than this (in particular if some cels
are selected), but that's a first step towards better.
2017-10-07 05:00:12 +02:00
Jehan
31b715f37c plug-ins: display the destination when drag'n dropping panels. 2017-10-07 05:00:12 +02:00
Jehan
89af4d59ab plug-ins: remove useless include. 2017-10-07 05:00:11 +02:00
Jehan
24b75f4a9e plug-ins: add drag'n drop in storyboard.
Reorder panel by drag'n dropping them around. This also edits the order
in the actual image. This first version reloads the storyboard, which is
still kind of fast even with many dozens of layers.
Yet we will want to have a proper reordering of the GUI without full
reload at some point.
2017-10-07 05:00:11 +02:00
Jehan
f2bc2f5e6f plug-ins: save the animation playback proxy ratio. 2017-10-07 05:00:11 +02:00
Jehan
c536d16959 plug-ins: proxy ratio is a data for the AnimationPlayback.
This should not be stored in the Animation.
Also there are now 2 separate animation_get_size() and
animation_playback_get_size(), the former being obviously the full size
for the animation whereas the later is the preview size after proxy
applied.
2017-10-07 05:00:11 +02:00
Jehan
2bed4e942c plug-ins: animation re-rendering broken on proxy ratio changed. 2017-10-07 05:00:11 +02:00
Jehan
a3daecc313 plug-ins: check camera existence before cleaning it up. 2017-10-07 05:00:11 +02:00
Jehan
1a4262d1a5 plug-ins: animation must be finalized after the renderer. 2017-10-07 05:00:11 +02:00
Jehan
a67d9019d1 plug-ins: single-click on track title selects all, and double-click...
... to edit the title.
In order to not trigger a select-all action on double click as well, I
delay it though I'm really not sure it's a good experience to do so.
Maybe I will review this later.
2017-10-07 05:00:10 +02:00
Jehan
c375ab2a13 plug-ins: fix animation loops.
Position of successive suite of frames were wrong. Not sure when I
introduced this bug.
2017-10-07 05:00:10 +02:00
Jehan
3b91ef052f plug-ins: fix fit-to-display on first render.
It was broken since GUI expose event was now happening faster than
actual image rendering (thanks to multi-threading), hence using the
wrong dimension information to fit the image to display.
2017-10-07 05:00:10 +02:00
Jehan
2f4fbfa380 plug-ins: style a bit the title labels for animation levels. 2017-10-07 05:00:10 +02:00
Jehan
3d4a41ed29 plug-ins: add a new widget to handle track titles.
This is salvaged from old code of mine for an editable label widget.
2017-10-07 05:00:10 +02:00
Jehan
2ea27098dc plug-ins: no need for destroy functions for animation render queues.
Must have been a remnant from a copy-paste or a previous version.
Anyway these queue contents are int (disguised as gpointer!) so
obviously there is nothing to free.
2017-10-07 05:00:10 +02:00
Jehan
6fa670f77c plug-ins: install animation-play specific data and set plugin icon. 2017-10-07 05:00:09 +02:00
Aryeom Han
23d80e6567 plug-ins: new icon for animation-play plugin. 2017-10-07 05:00:09 +02:00
Jehan
b941092e53 plug-ins: speed-up updating composition of many frames at once.
Even though actual rendering is now done in background, the GUI was
still lagging out. This was because of the recursive renaming on cels
which was still needed but we were recomputing cel names just too many
times.
2017-10-07 05:00:09 +02:00
Jehan
b70fb642e4 plug-ins: export also sequence of images.
I recognize only png, jpeg and tiff formats for now.
Also allow to create directories through the file chooser dialog.
2017-10-07 05:00:09 +02:00
Jehan
b614e8e8e6 plug-ins: add a file chooser to choose animation file name.
Also move the code to a separate file and add some filter.
Right now it still only export video files, but I should soon be able to
export image sequences as well.
2017-10-07 05:00:09 +02:00
Jehan
2559054563 plug-ins: add an export button.
This first version is more a proof of concept. You can't yet choose the
format (video, image sequence, file format…) nor even the file path.
2017-10-07 05:00:09 +02:00
Jehan
d27b28a185 plug-ins: action labels not properly filled. 2017-10-07 05:00:08 +02:00
Jehan
09d1d07b9d plug-ins: only run the idle source on the renderer when needed.
Running it continuously was eating the processor. Just run it
temporarily when we are still waiting for renders to be done.
2017-10-07 05:00:08 +02:00
Jehan
7ad5037c14 plug-ins: render the frame buffer in a separate thread.
This way, the GUI does not have to hang forever each time the frames'
composition changes. It still does for changes on many frames at once. I
will have to investigate the issue later.
All GTK+ code still has to stay in the same thread. This is why I emit
the "cache-updated" signal in the main thread as regularly verified by a
separate queue.
Otherwise the plugin would crash with:
> animation-play: Fatal IO error 11 (Resource temporarily unavailable) on X server :0.0.
> [xcb] Unknown request in queue while dequeuing
> [xcb] Most likely this is a multi-threaded client and XInitThreads has not been called
> [xcb] Aborting, sorry about that.
> animation-play: xcb_io.c:165: dequeue_pending_request: Assertion `!xcb_xlib_unknown_req_in_deq' failed.
2017-10-07 05:00:08 +02:00
Jehan
d19bc7016a plug-ins: properly initialize a pointer to NULL.
This fixes a possible segmentation fault.
2017-10-07 05:00:08 +02:00
Jehan
993aefb50e plug-ins: move all the caching to a separate renderer.
This is a first step towards a threaded rendering which won't block the
GUI.
2017-10-07 05:00:08 +02:00
Jehan
84b473106f plug-ins: playback state not reloaded properly in animatics.
Commit 4fb1466 added the feature, working in cel animation, not in
animatics. Fix it.
2017-10-07 05:00:08 +02:00
Jehan
726f5a7729 plug-ins: better align some function declarations' parameters. 2017-10-07 05:00:07 +02:00
Jehan
8310eb0d4d plug-ins: GIMP_STOCK_* have now become GIMP_ICON_*. 2017-10-07 05:00:07 +02:00
Jehan
816f19513f plug-ins: fix "Comments" column position in animation plugin...
... after adding/removing tracks.
2017-10-07 05:00:07 +02:00
Jehan
a65bb3884b plug-ins: add red color tags to the displayed layers of current frame. 2017-10-07 05:00:07 +02:00
Jehan
8c04ada9a0 plug-ins: double-click on animation's frame position updates the...
... image view in GIMP.
2017-10-07 05:00:07 +02:00
Jehan
c295a9ca48 plug-ins: save the current animation playback state.
Current position, and start/stop loop frames are relevant to the
work-in-progress in current animation project.
This is a first version. I'm still wondering about the current relation
between Animation and AnimationPlayback and therefore code architecture.
2017-10-07 05:00:07 +02:00
Jehan
1be912fa54 plug-ins: add a start of implementation for camera keyframing.
It is barely usable at this state since it will recompute too much at
any update. It also miss a lot of the UI to set or delete a keyframe,
and navigate through the animation positions.
It works only for a full frame for the time being (there should also be
camera works possible on independant levels) and only provides offset
(i.e. tilting and panning). Zoom or rotation features will be necessary,
as well as effects (basically animating GEGL operations).
Finally data persistence will need to be implemented in the
serialization of the animation.
Yet that's a start for further development in progress.
2017-10-07 05:00:07 +02:00
Jehan
35e3e961d5 plug-ins: private declaration reordering. 2017-10-07 05:00:06 +02:00
Jehan
67dd03c7e9 plug-ins: prevent resize of animation playback widget when progress...
... bar text length changes.
2017-10-07 05:00:06 +02:00
Jehan
926292b66d plug-ins: space is a possible separator between frame subnumbers. 2017-10-07 05:00:06 +02:00
Jehan
d237ed96a6 plug-ins: more subtle zoom in and out in animation playback.
Use 5% zooming steps rather than 10%.
2017-10-07 05:00:06 +02:00
Jehan
230ffc8456 plug-ins: add a "Fit to display" feature in animation playback.
And fit the animation to display by default at animation set.
Revival of an older feature I wiped out in one of my rewritings.
2017-10-07 05:00:06 +02:00
Jehan
96e03bdb1f plug-ins: disconnect animation_storyboard_stopped() on storyboard...
... finalize().
2017-10-07 05:00:06 +02:00
Jehan
5f48f8ed60 plug-ins: no need to try and render the current frame at animation set. 2017-10-07 05:00:05 +02:00
Jehan
1dbcbc9bc3 plug-ins: animation_animatic_get_frame() can return NULL on empty cache.
We should only g_object_ref() if the cache has contents.
2017-10-07 05:00:05 +02:00
Jehan
ebbc934f3f plug-ins: reorganize caching to have a visible GUI immediately.
Caching can take time and would delay the exposition of the GUI,
having one wonder whether the plugin really started or not. Now the
animation structure (saved in parasite as XML) is immediately parsed,
and the frame caching is only started once the GUI is exposed.
It is still not perfect because caching makes the GUI hardly responsive,
but this is a first step.
Also took the opportunity to some cleaning, renaming (i.e
s/load_xml/deserialize/) and code reorganization here and there.
2017-10-07 05:00:05 +02:00
Jehan
b1b6ccfc2b plug-ins: add a contextual menu on cels to add/delete/duplicate them. 2017-10-07 05:00:05 +02:00
Jehan
673fed12fe plug-ins: fix default cel animation construction.
A very silly bug.
2017-10-07 05:00:05 +02:00
Jehan
9df60bc5ae plug-ins: support animation cycles with several layers in a cell.
If the layers all have the same numbering scheme, then successive
frames in the suite will follow this common scheme (which could result
in some frames having less layers than the next ones in the cycle).
Otherwise this is like merging 2 sequential suite without any link
in their respective numbering.
2017-10-07 05:00:05 +02:00
Jehan
4638d812df plug-ins: automatically create animation cycles from layer names. 2017-10-07 05:00:04 +02:00
Jehan
0d3b17d678 plug-ins: return value of gimp_image_get_layers() must be freed...
... with g_free().
2017-10-07 05:00:04 +02:00
Jehan
05a3da5d10 plug-ins: improved labelling for cells.
Adding underlined ellipsis when several layers are selected. Also adding
pango markups to differentiate from normal layer names and truncated or
special labels.
2017-10-07 05:00:04 +02:00
Jehan
a28927c85c plug-ins: allow to select several layers per animation cell.
This is a difference from actual celluloid animation. Yet traditional
animation would not split the color and the drawing in 2 celluloids
(they would usually paint on the other side). Well we could just have
color as separate levels, but this would definitely complicate things
and make the x-sheet messy.
2017-10-07 05:00:04 +02:00
Jehan
ecfe2e49e2 plug-ins: some more code factorization in X-Sheet animation layout. 2017-10-07 05:00:04 +02:00
Jehan
dffa184d0f plug-ins: cleanup before re-loading default animation on parsing failure. 2017-10-07 05:00:04 +02:00
Jehan
b25ca9b9b5 plug-ins: using g_printerr() instead of g_warning() for...
... non-programmatical errors.
For instance, when layers are removed, these are somehow "expected"
errors in the plug-in runtime (since we have no hook to act when it
actually happens). So I just log on stderr for user information.
Let's use g_warning() only for potential programmatic errors (like
failing to parse XML which we built ourselves).
2017-10-07 05:00:04 +02:00
Jehan
a5590895fc plug-ins: handling deleted layers.
WIP: maybe I should just gray it or something? Allowing to hit "Undo"?
2017-10-07 05:00:03 +02:00
Jehan
cabc8a58cf plug-ins: fix refreshing cel animations. 2017-10-07 05:00:03 +02:00
Jehan
1e2aa92e47 plug-ins: better progress bar text. 2017-10-07 05:00:03 +02:00
Jehan
114d704e44 plug-ins: improve progress bar's mouse interaction.
The preview on hover was a terrible idea because you always move the
pointer over and then the whole UI goes crazy. Just preview on click,
but allow for keeping the mouse button pressed so that you can still
easily scan through the frames in a single movement.
One has to release the mouse over the progress bar to validate the
position change (otherwise, it reverts to the previous position).
2017-10-07 05:00:03 +02:00
Jehan
81c421ea87 plug-ins: get rid of unblock_ui().
It doesn't even do anymore what the name says and calls only a single
function on the data parameter.
Let's directly call this function with g_signal_connect_swapped().
2017-10-07 05:00:03 +02:00
Jehan
d3edfacffb plug-ins: get rid of animation_dialog_refresh().
The whole window resizing business made sense at the very start when
big images created a huge dialog. Now the animation is inside a scrolled
window and therefore the dialog won't go over the screen area.
Also only refresh the layer list when specifically clicking the refresh
button only.
2017-10-07 05:00:03 +02:00
Jehan
ab4e577aac plug-ins: do not recreate the whole X-Sheet on add/delete track.
There is still a problem on deletion because of the "loaded" signal
where I refresh the whole dialog, which is absolutely wrong. Also this
is still too slow just to remove or add a simple track.
Finally there is definitely more code factorization to be done.
2017-10-07 05:00:02 +02:00
Jehan
d598791f19 plug-ins: factor the X-Sheet construction code. 2017-10-07 05:00:02 +02:00
Jehan
6396c312e4 plug-ins: broken X-sheet when adding new frames to cel animation. 2017-10-07 05:00:02 +02:00
Jehan
e7c0aac59d plug-ins: add time in second in the animation dialog's progress bar. 2017-10-07 05:00:02 +02:00
Jehan
db1b0a76c3 plug-ins: add a visual separator every second in the X-Sheet.
A little helper for animators.
2017-10-07 05:00:02 +02:00
Jehan
e13eb5a432 plug-ins: get a quick frame preview when entering the progress.
Yet revert back to the current fixed position as soon as exiting the
progress bar, unless one actually clicked on another position.
2017-10-07 05:00:02 +02:00
Jehan
ceb582c6ed plug-ins: in x-sheet and storyboard, do not jump to current panel...
... and position while playing. This is too fast and just disturbing.
We must jump to the frame position (X-Sheet) or the panel (storyboard)
only when the playback stops or when one jumps without playing.
2017-10-07 05:00:02 +02:00
Jehan
39fe5b3081 plug-ins: scroll to storyboard panel corresponding to playback position.
The code used in AnimationXSheet moved to `utils` so that I could reuse
it for any scrolled window. Thus I made the storyboard a native scrolled
window.
2017-10-07 05:00:01 +02:00
Jehan
3eb896007f plug-ins: click the storyboard panel to jump to the right position.
Instead of using a stupid arrow as button, use the whole panel image.
2017-10-07 05:00:01 +02:00
Jehan
07462546e3 plug-ins: minor improvements on storyboard GUI. 2017-10-07 05:00:01 +02:00
Jehan
c1467113f3 plug-ins: move settings in a tab.
The top bar should only show usual options, like zoom, refresh and
detach. Framerate or animation type are settings which are most often
set once and for all, or at least rarely changed.
2017-10-07 05:00:01 +02:00
Jehan
5fbd2fea07 plug-ins: allow zooming in/out with ctrl-scroll up/down. 2017-10-07 05:00:01 +02:00
Jehan
1664e97a88 plug-ins: rename s/animationanimatic.[ch]/animation-animatic.[ch]/.
Keep consistent file names.
2017-10-07 05:00:01 +02:00
Jehan
9dd4179f25 plug-ins: add track deletion button.
The UI is quite ugly with all these buttons. I will revise it later.
2017-10-07 05:00:00 +02:00
Jehan
5d3e3cae84 plug-ins: add and move tracks for cel animations. 2017-10-07 05:00:00 +02:00
Jehan
52b764db69 plug-ins: don't rebuild whole xsheet when changing animation duration. 2017-10-07 05:00:00 +02:00
Jehan
ba9d2d88ba plug-ins: fix a few bugs when changing animation types. 2017-10-07 05:00:00 +02:00
Jehan
e04da2b7ad plug-ins: add a spin button to set duration for cel animations.
Current GUI is not very pretty, but I just do something which "works"
for now. I also have some issues with the spin button: clicking an arrow
sometimes moves of 2 or 3 frames. Using a debugger, there seems to be
some race condition with GtkSpinButton code which reproduces the event.
I will have to get a closer look later.
2017-10-07 05:00:00 +02:00
Jehan
b39b898cd6 plug-ins: better behavior on animation duration change.
When the animation lengthens, if the stop position was the last frame,
keep it that way. Also fix animation position changing when it is not
available anymore in the new range.
2017-10-07 05:00:00 +02:00
Jehan
7833558f58 plug-ins: split the playback code out of the Animation class. 2017-10-07 05:00:00 +02:00
Jehan
131a5fe1b4 plug-ins: get rid of animation's start position property.
Adding this concept so low level was an error. Internally everything
should be counted from 0, be it frames, panels, layers…
I will later add the concept back, but only for display and image
export numbering.
2017-10-07 05:00:00 +02:00
Jehan
b17da21b56 plug-ins: scroll the X-Sheet when jumping to a frame position. 2017-10-07 04:59:59 +02:00
Jehan
fc9ea67e26 plug-ins: jump to a frame position by clicking on xsheet frame numbers. 2017-10-07 04:59:59 +02:00
Jehan
a003111553 plug-ins: make so the layer list takes all the vertical space...
... in the animation plugin.
2017-10-07 04:59:59 +02:00
Jehan
9bd475308c plug-ins: name animation cels appropriately. 2017-10-07 04:59:59 +02:00
Jehan
2ba6950e6d plug-ins: fix cel selection in animation. 2017-10-07 04:59:59 +02:00
Jehan
95a140a3a6 plug-ins: select layer for each track/frame couple.
For now, only a single layer can be selected on a given frame.
2017-10-07 04:59:59 +02:00
Jehan
b969fde412 plug-ins: allow updating comments for cel animations. 2017-10-07 04:59:58 +02:00
Jehan
d415344a5a plug-ins: do not reverse track order at each serialization. 2017-10-07 04:59:58 +02:00
Jehan
5417753d8c plug-ins: allow updating animation track titles. 2017-10-07 04:59:58 +02:00
Jehan
b37df6ebe3 plug-ins: frame selection for cel animation. 2017-10-07 04:59:58 +02:00
Jehan
d2120a74dd plug-ins: some basic X-Sheet layout. 2017-10-07 04:59:58 +02:00
Jehan
0bd00d9b8d plug-ins: add a view of the image layers for cel animation. 2017-10-07 04:59:58 +02:00
Jehan
ac3a7d1910 plug-ins: serialization for cel animations. 2017-10-07 04:59:57 +02:00
Jehan
ad5e37714b plug-ins: add new animation type "cel animation".
This is for more featureful animation with several layers/tracks of
images composed into single frames.
Right now, there is no UI. Only the core code is implemented.
There is no XML serialization for this type of animation yet.
2017-10-07 04:59:57 +02:00
Jehan
7a92c3a888 plug-ins: improve proxy support in animation playback.
- Rename label to just "Proxy".
- Toggling the expander should not enable/disable proxy.
- All proxy and preview size code moved to Animation class.
- Add animation_set_proxy(). animation_load*() do not have the proxy
ratio as parameter anymore.
2017-10-07 04:59:57 +02:00
Jehan
3bd674ee39 plug-ins: cache panels directly as GEGL buffers.
Do not use an internal GIMP image anymore and do all the dirty work
directly in GEGL graphs.
Also pre-compute updated frames as soon as possible (instead of the last
second when it is needed at playback) as a cache system.
2017-10-07 04:59:57 +02:00
Jehan
bf881644df plug-ins: do not destroy widgets twice. 2017-10-07 04:59:57 +02:00
Jehan
050cf79264 plug-ins: fix animation panel's thumbnails.
When the panel is based on a layer with size different to the image,
the thumbnail ended up wrong. Duplicate temporary the image to resize
layers before generating thumbnails.
2017-10-07 04:59:57 +02:00
Jehan
0458f5ad69 plug-ins: implement "combine" (blend mode "normal"/gegl:over) button.
We have now implemented all features previously available.
2017-10-07 04:59:56 +02:00
Jehan
4563a6c229 plug-ins: tab key in comment fields to switch to the next comment field. 2017-10-07 04:59:56 +02:00
Jehan
3d3673d220 plug-ins: display selected panel and allow to jump to panel. 2017-10-07 04:59:56 +02:00
Jehan
1644b1c3a0 plug-ins: save and load animatic info as XML.
Right now, only the framerate, panel comments and durations are loaded.
This is still work-in-progress.
2017-10-07 04:59:56 +02:00
Jehan
11568f0582 plug-ins: minor code cleanup of animation-play. 2017-10-07 04:59:56 +02:00
Jehan
03b3d46a03 plug-ins: reset framerate warning when it comes back to acceptable rate. 2017-10-07 04:59:56 +02:00
Jehan
39a6e2e179 plug-ins: the legacy animation becomes "animatic" animation.
We show all layers as panels of a storyboard view, where one can choose
each panel's duration, and whether it combines with the previous one.
Layer name tagging will still be processed, for backward compatibility,
but everything will be doable through a much nicer UI now.
Changes of logics:
- There aren't 2 modes now. By default, all panels just replace the
previous one. Note that the button to combine exists but does not work
yet.
- Panels can now be commented. Current version has the UI, but neither
comments nor any other settings are saved at this point.
- Time is not given in ms as for tagging. Each panel duration is in
frames, with a global framerate. Keeping irregular frame duration is
not how animation works.
2017-10-07 04:59:55 +02:00
Jehan
cff95a477f plug-ins: do not use direct letters as shortcuts.
Though it is very practical, it takes precedence when working in text
widgets. Maybe I'll improve later so that it can work except when in a
text entry or similar. Until then, use <control> action shortcuts.
2017-10-07 04:59:55 +02:00
Jehan
cceeedabb9 plug-ins: complete code reorganization of animation-play.
We temporary lost the tags feature which will be back soon with a
complete UI, as well as the framerate check.
2017-10-07 04:59:55 +02:00
Jehan
95703ef951 plug-ins: make sure the drawing area is realized before drawing to it. 2017-10-07 04:59:55 +02:00
Jehan
c008eaf4fc plug-ins: stop animation playback before quitting. 2017-10-07 04:59:55 +02:00
Jehan
c15f828aa6 plug-ins: warn of low framerate…
useful when playing big HD animations.
2017-10-07 04:59:55 +02:00
Jehan
a33b0cc0fe plug-ins: animation-play reorganized into several files. 2017-10-07 04:59:55 +02:00
Jehan
1dc3171d58 plug-ins: movie animation-play in its own directory.
This will allow to complexify its features without headaches on huge
files.
2017-10-07 04:59:55 +02:00
Jehan
974ba1c4f4 plug-ins: reorder all functions in animation-play...
and make sure they all have a declaration.
2017-10-07 04:59:54 +02:00
Jehan
0beb322993 plug-ins: major cleanup of animation-play.
- Got rid of all the global variables, but the regexp;
- much nicer UI, with all play tools at the bottom, animation settings
at top left, and view settings at top right.
- got rid of the duration_factor, but allow now fps entry, and shortcut
to easily in|decrement fps.
- fine proxy quality instead of just a rough checkbox.
- Better grouped actions, and menus only filled with some actions which
are not available as UI, with visible accelerator shown alongside.
2017-10-07 04:59:54 +02:00
Jehan
245538dc57 plug-ins: display an alpha background when no animation frame to play instead of quitting. 2017-10-07 04:59:54 +02:00
Jehan
af392e2ecb plug-ins: support for layer groups in animation-play.
Support for groups good in combine/replace mode, not perfect yet in tags mode.
It won't deal with group's compositing mode other than "normal".
2017-10-07 04:59:54 +02:00
Jehan
a3c58e5f0d plug-ins: improved UI for frame length support.
When refreshing the UI, if we were at the first/last frame, stay at first/last
frame, even when a different value.
Also update the start/end frame's spin buttons width depending on max frame value.
2017-10-07 04:59:54 +02:00
Jehan
a23605e53b plug-ins: don't show the animation playback quality checkbox when useless. 2017-10-07 04:59:54 +02:00
Jehan
0a069fe46d plug-ins: add start and end frame concept to animation playback to be able to play only a piece of animation.
Additionally the progress bar and the rest of the lower tool are now
separated in a paned box to easily hide a side or another.
2017-10-07 04:59:53 +02:00
Jehan
c68f85b2b2 plug-ins: move to a frame in animation playback by clicking on the progress bar. 2017-10-07 04:59:53 +02:00
Jehan
afbc399e50 plug-ins: must faster implementation for tagged disposal mode of animation playback.
In particular, each frame keeps track of which other frames are actually
the same drawable, and even when the finale frame is different, but has
a common base, I "fork" from the base, instead of re-generating it from
the start.
Finally at render time, I don't redraw again the same frame twice on a row.
2017-10-07 04:59:53 +02:00
Jehan
ee28201289 plug-ins: better support of big images: quality checkbox, loading notice, cleaner exit, frames locking.
The "preview quality" checkbox allows to preview the animation as at
most half the original image's dimension. Big animations (more than the
screen's dimension) with many frames will be checked by default.
This allows to significantly reduce loading time for huge images.

While loading, the playing progress bar will be used as a loading
progress bar, and care has been taken so that the UI is not frozen when
this happens. This allows a much better user experience.

Also the created frame image must be cleaned on exit, because even
though the process will finish, GEGL won't know about it (bug 690512).

Finally I lock the UI and prevent several actions on frames in the same
time, like rendering, or reinitialization.
2017-10-07 04:59:53 +02:00
Jehan
5d33347c83 plug-ins: new disposal mode "tags" for animation playback, allowing to generate frames from layer tags. 2017-10-07 04:59:53 +02:00
41 changed files with 16075 additions and 1778 deletions

View File

@@ -2536,6 +2536,7 @@ build/windows/Makefile
build/windows/gimp.rc
build/windows/gimp-plug-ins.rc
plug-ins/Makefile
plug-ins/animation-play/Makefile
plug-ins/file-bmp/Makefile
plug-ins/file-exr/Makefile
plug-ins/file-faxg3/Makefile

View File

@@ -31,6 +31,7 @@ endif
SUBDIRS = \
$(script_fu) \
$(pygimp) \
animation-play \
file-bmp \
$(file_darktable) \
$(file_exr) \

2
plug-ins/animation-play/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
/Makefile.in
/Makefile

View File

@@ -0,0 +1,82 @@
AUTOMAKE_OPTIONS = subdir-objects
if OS_WIN32
mwindows = -mwindows
else
libm = -lm
endif
libgimp = $(top_builddir)/libgimp/libgimp-$(GIMP_API_VERSION).la
libgimpbase = $(top_builddir)/libgimpbase/libgimpbase-$(GIMP_API_VERSION).la
libgimpcolor = $(top_builddir)/libgimpcolor/libgimpcolor-$(GIMP_API_VERSION).la
libgimpconfig = $(top_builddir)/libgimpconfig/libgimpconfig-$(GIMP_API_VERSION).la
libgimpmath = $(top_builddir)/libgimpmath/libgimpmath-$(GIMP_API_VERSION).la $(libm)
libgimpmodule = $(top_builddir)/libgimpmodule/libgimpmodule-$(GIMP_API_VERSION).la
libgimpui = $(top_builddir)/libgimp/libgimpui-$(GIMP_API_VERSION).la
libgimpwidgets = $(top_builddir)/libgimpwidgets/libgimpwidgets-$(GIMP_API_VERSION).la
AM_LDFLAGS = $(mwindows)
libexecdir = $(gimpplugindir)/plug-ins
libexec_PROGRAMS = animation-play
AM_CPPFLAGS = \
-I$(top_srcdir) \
$(GTK_CFLAGS) \
$(GEGL_CFLAGS) \
-I$(includedir)
LDADD = \
$(libgimpui) \
$(libgimpwidgets) \
$(libgimpmodule) \
$(libgimp) \
$(libgimpmath) \
$(libgimpconfig) \
$(libgimpcolor) \
$(libgimpbase) \
$(GTK_LIBS) \
$(GEGL_LIBS) \
$(RT_LIBS) \
$(INTLLIBS) \
$(animation_play_RC)
animation_play_SOURCES = \
core/animation.h \
core/animation.c \
core/animation-animatic.h \
core/animation-animatic.c \
core/animation-camera.h \
core/animation-camera.c \
core/animation-celanimation.h \
core/animation-celanimation.c \
core/animation-playback.h \
core/animation-playback.c \
core/animation-renderer.h \
core/animation-renderer.c \
widgets/animation-dialog.h \
widgets/animation-dialog.c \
widgets/animation-dialog-export.h \
widgets/animation-dialog-export.c \
widgets/animation-editable-label.h \
widgets/animation-editable-label.c \
widgets/animation-editable-label-string.h \
widgets/animation-editable-label-string.c \
widgets/animation-keyframe-view.h \
widgets/animation-keyframe-view.c \
widgets/animation-layer-view.h \
widgets/animation-layer-view.c \
widgets/animation-menus.h \
widgets/animation-menus.c \
widgets/animation-storyboard.h \
widgets/animation-storyboard.c \
widgets/animation-xsheet.h \
widgets/animation-xsheet.c \
animation-utils.h \
animation-utils.c \
animation-play.c
iconsdir = $(gimpdatadir)/plug-ins/animation-play/icons/
icons_DATA = icons/gimp-motion.png

View File

@@ -0,0 +1,136 @@
/*
* Animation Playback plug-in version 0.99.1
*
* (c) Adam D. Moss : 1997-2000 : adam@gimp.org : adam@foxbox.org
* (c) Mircea Purdea : 2009 : someone_else@exhalus.net
* (c) Jehan : 2012-2016 : jehan at girinstud.io
*
* GIMP - The GNU Image Manipulation Program
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <libgimp/gimp.h>
#undef GDK_DISABLE_DEPRECATED
#include <libgimp/gimpui.h>
#include "libgimp/stdplugins-intl.h"
#include "widgets/animation-dialog.h"
#include "animation-utils.h"
static void query (void);
static void run (const gchar *name,
gint nparams,
const GimpParam *param,
gint *nreturn_vals,
GimpParam **return_vals);
const GimpPlugInInfo PLUG_IN_INFO =
{
NULL, /* init_proc */
NULL, /* quit_proc */
query, /* query_proc */
run, /* run_proc */
};
MAIN ()
static void
query (void)
{
static const GimpParamDef args[] =
{
{ GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" },
{ GIMP_PDB_IMAGE, "image", "Input image" },
{ GIMP_PDB_DRAWABLE, "drawable", "Input drawable (unused)" }
};
gimp_install_procedure (PLUG_IN_PROC,
N_("Preview an animation"),
"",
"Adam D. Moss <adam@gimp.org>",
"Adam D. Moss <adam@gimp.org>",
"1997, 1998...",
N_("Animation _Playback..."),
"RGB*, INDEXED*, GRAY*",
GIMP_PLUGIN,
G_N_ELEMENTS (args), 0,
args, NULL);
gimp_plugin_menu_register (PLUG_IN_PROC, "<Image>/Filters/Animation");
gimp_plugin_icon_register (PLUG_IN_PROC, GIMP_ICON_TYPE_ICON_NAME,
(const guint8 *) "media-playback-start");
}
static void
run (const gchar *name,
gint n_params,
const GimpParam *param,
gint *nreturn_vals,
GimpParam **return_vals)
{
static GimpParam values[1];
GimpPDBStatusType status;
GimpRunMode run_mode;
GeglConfig *config;
INIT_I18N ();
gegl_init (NULL, NULL);
config = gegl_config ();
/* For preview, we want fast (0.0) over high quality (1.0). */
g_object_set (config, "quality", 0.0, NULL);
run_mode = param[0].data.d_int32;
if (run_mode == GIMP_RUN_NONINTERACTIVE ||
n_params != 3)
{
/* This plugin is meaningless right now other than interactive. */
status = GIMP_PDB_CALLING_ERROR;
}
else
{
GtkWidget *dialog;
gint32 image_id;
gimp_ui_init (PLUG_IN_BINARY, TRUE);
image_id = param[1].data.d_image;
dialog = animation_dialog_new (image_id);
gimp_help_connect (GTK_WIDGET (dialog),
gimp_standard_help_func,
PLUG_IN_PROC, NULL);
gtk_widget_show_now (GTK_WIDGET (dialog));
gtk_main ();
gimp_displays_flush ();
status = GIMP_PDB_SUCCESS;
}
values[0].type = GIMP_PDB_STATUS;
values[0].data.d_status = status;
*nreturn_vals = 1;
*return_vals = values;
gegl_exit ();
gimp_quit ();
}

View File

@@ -0,0 +1,344 @@
/*
* Animation Playback plug-in version 0.99.1
*
* (c) Jehan : 2012-2015 : jehan at girinstud.io
*
* GIMP - The GNU Image Manipulation Program
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <string.h>
#include <libgimp/gimp.h>
#undef GDK_DISABLE_DEPRECATED
#include <libgimp/gimpui.h>
#include "libgimp/stdplugins-intl.h"
#include "animation-utils.h"
/* total_alpha_preview:
* Fill the @drawing_data with an alpha (grey chess) pattern.
* This uses a static array, copied over each line (with some shift to
* reproduce the pattern), using `memcpy()`.
* The reason why we keep the pattern in the statically allocated memory,
* instead of simply looping through @drawing_data and recreating the
* pattern is simply because `memcpy()` implementations are supposed to
* be more efficient than loops over an array. */
void
total_alpha_preview (guchar *drawing_data,
guint drawing_width,
guint drawing_height)
{
static guint alpha_line_width = 0;
static guchar *alpha_line = NULL;
gint i;
g_assert (drawing_width > 0);
/* If width change, we update the "alpha" line. */
if (alpha_line_width < drawing_width + 8)
{
alpha_line_width = drawing_width + 8;
g_free (alpha_line);
/* A full line + 8 pixels (1 square). */
alpha_line = g_malloc (alpha_line_width * 3);
for (i = 0; i < alpha_line_width; i++)
{
/* 8 pixels dark grey, 8 pixels light grey, and so on. */
if (i & 8)
{
alpha_line[i * 3 + 0] =
alpha_line[i * 3 + 1] =
alpha_line[i * 3 + 2] = 102;
}
else
{
alpha_line[i * 3 + 0] =
alpha_line[i * 3 + 1] =
alpha_line[i * 3 + 2] = 154;
}
}
}
for (i = 0; i < drawing_height; i++)
{
if (i & 8)
{
memcpy (&drawing_data[i * 3 * drawing_width],
alpha_line,
3 * drawing_width);
}
else
{
/* Every 8 vertical pixels, we shift the horizontal line by 8 pixels. */
memcpy (&drawing_data[i * 3 * drawing_width],
alpha_line + 24,
3 * drawing_width);
}
}
}
/**
* normal_blend:
* @width: width of the returned #GeglBuffer.
* @height: height of the returned #GeglBuffer.
* @backdrop_buffer: optional backdrop image (may be %NULL).
* @backdrop_scale_ratio: scale ratio (`]0.0, 1.0]`) for @backdrop_buffer.
* @backdrop_offset_x: original X offset of the backdrop (not processed
* with @backdrop_scale_ratio yet).
* @backdrop_offset_y: original Y offset of the backdrop (not processed
* with @backdrop_scale_ratio yet).
* @source_buffer: source image (cannot be %NULL).
* @source_scale_ratio: scale ratio (`]0.0, 1.0]`) for @source_buffer.
* @source_offset_x: original X offset of the source (not processed with
* @source_scale_ratio yet).
* @source_offset_y: original Y offset of the source (not processed with
* @source_scale_ratio yet).
*
* Creates a new #GeglBuffer of size @widthx@height with @source_buffer
* scaled with @source_scale_ratio r, and translated by offsets:
* (@source_offset_x * @source_scale_ratio,
* @source_offset_y * @source_scale_ratio).
*
* If @backdrop_buffer is not %NULL, it is resized with
* @backdrop_scale_ratio, and offsetted by:
* (@backdrop_offset_x * @backdrop_scale_ratio,
* @backdrop_offset_y * @backdrop_scale_ratio)
*
* Finally @source_buffer is composited over @backdrop_buffer in normal
* blend mode.
*
* Returns: the newly allocated #GeglBuffer containing the result of
* said scaling, translation and blending.
*/
GeglBuffer *
normal_blend (gint width,
gint height,
GeglBuffer *backdrop_buffer,
gdouble backdrop_scale_ratio,
gint backdrop_offset_x,
gint backdrop_offset_y,
GeglBuffer *source_buffer,
gdouble source_scale_ratio,
gint source_offset_x,
gint source_offset_y)
{
GeglBuffer *buffer;
GeglNode *graph;
GeglNode *source, *src_scale, *src_translate;
GeglNode *backdrop, *bd_scale, *bd_translate;
GeglNode *blend, *target;
gdouble offx;
gdouble offy;
g_return_val_if_fail (source_scale_ratio >= 0.0 &&
source_buffer &&
(! backdrop_buffer ||
backdrop_scale_ratio >= 0.0),
NULL);
/* Panel image. */
buffer = gegl_buffer_new (GEGL_RECTANGLE (0, 0, width, height),
gegl_buffer_get_format (source_buffer));
graph = gegl_node_new ();
/* Source */
source = gegl_node_new_child (graph,
"operation", "gegl:buffer-source",
"buffer", source_buffer,
NULL);
src_scale = gegl_node_new_child (graph,
"operation", "gegl:scale-ratio",
"sampler", GEGL_SAMPLER_NEAREST,
"x", source_scale_ratio,
"y", source_scale_ratio,
NULL);
offx = source_offset_x * source_scale_ratio;
offy = source_offset_y * source_scale_ratio;
src_translate = gegl_node_new_child (graph,
"operation", "gegl:translate",
"x", offx,
"y", offy,
NULL);
/* Target */
target = gegl_node_new_child (graph,
"operation", "gegl:write-buffer",
"buffer", buffer,
NULL);
if (backdrop_buffer)
{
/* Backdrop */
backdrop = gegl_node_new_child (graph,
"operation", "gegl:buffer-source",
"buffer", backdrop_buffer,
NULL);
bd_scale = gegl_node_new_child (graph,
"operation", "gegl:scale-ratio",
"sampler", GEGL_SAMPLER_NEAREST,
"x", backdrop_scale_ratio,
"y", backdrop_scale_ratio,
NULL);
offx = backdrop_offset_x * backdrop_scale_ratio;
offy = backdrop_offset_y * backdrop_scale_ratio;
bd_translate = gegl_node_new_child (graph,
"operation", "gegl:translate",
"x", offx,
"y", offy,
NULL);
gegl_node_link_many (source, src_scale, src_translate, NULL);
gegl_node_link_many (backdrop, bd_scale, bd_translate, NULL);
/* Blending */
blend = gegl_node_new_child (graph,
"operation", "gegl:over",
NULL);
gegl_node_link_many (bd_translate, blend, target, NULL);
gegl_node_connect_to (src_translate, "output",
blend, "aux");
}
else
{
gegl_node_link_many (source, src_scale, src_translate, target, NULL);
}
gegl_node_process (target);
g_object_unref (graph);
return buffer;
}
void
show_scrolled_child (GtkScrolledWindow *window,
GtkWidget *child)
{
GtkWidget *contents = gtk_bin_get_child (GTK_BIN (window));
GtkAdjustment *hadj;
GtkAdjustment *vadj;
GtkAllocation window_allocation;
GtkAllocation child_allocation;
gint x;
gint y;
gint x_xsheet;
gint y_xsheet;
hadj = gtk_scrolled_window_get_vadjustment (window);
vadj = gtk_scrolled_window_get_vadjustment (window);
/* Handling both contents with native scroll abilities, and
contents added with a viewport. */
if (GTK_IS_VIEWPORT (contents))
contents = gtk_bin_get_child (GTK_BIN (contents));
gtk_widget_translate_coordinates (child, GTK_WIDGET (window),
0, 0, &x_xsheet, &y_xsheet);
gtk_widget_translate_coordinates (child, contents,
0, 0, &x, &y);
gtk_widget_get_allocation (child, &child_allocation);
gtk_widget_get_allocation (GTK_WIDGET (window),
&window_allocation);
/* Scroll only if the widget is not already visible. */
if (x_xsheet < 0 || x_xsheet + child_allocation.width > window_allocation.width)
{
gtk_adjustment_set_value (hadj, x);
}
if (y_xsheet < 0 || y_xsheet + child_allocation.height > window_allocation.height)
{
gtk_adjustment_set_value (vadj, y);
}
}
void
hide_item (gint item,
gboolean recursive,
gboolean reset_tag)
{
gimp_item_set_visible (item, FALSE);
if (reset_tag)
gimp_item_set_color_tag (item, GIMP_COLOR_TAG_NONE);
if (recursive && gimp_item_is_group (item))
{
gint32 *children;
gint32 n_children;
gint i;
children = gimp_item_get_children (item, &n_children);
for (i = 0; i < n_children; i++)
{
hide_item (children[i], TRUE, reset_tag);
}
}
}
void
show_layer (gint item,
gint32 color_tag,
gdouble opacity)
{
gint32 parent;
gimp_item_set_visible (item, TRUE);
if (color_tag >= 0)
gimp_item_set_color_tag (item, color_tag);
gimp_layer_set_opacity (item, opacity * 100.0);
/* Show the parent as well, but do not update its color tag. */
parent = gimp_item_get_parent (item);
if (parent > 0)
{
show_layer (parent, -1, 1.0);
}
}
gint
compare_int_from (gconstpointer f1,
gconstpointer f2,
gpointer data)
{
gint first_frame = GPOINTER_TO_INT (data);
gint frame1 = GPOINTER_TO_INT (f1);
gint frame2 = GPOINTER_TO_INT (f2);
gint invert;
if (frame1 == frame2)
{
return 0;
}
else
{
invert = ((frame1 >= first_frame && frame2 >= first_frame) ||
(frame1 < first_frame && frame2 < first_frame)) ? 1 : -1;
if (frame1 < frame2)
return invert * -1;
else
return invert;
}
}

View File

@@ -0,0 +1,72 @@
/* GIMP - The GNU Image Manipulation Program
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
*
* animation-utils.c
* Copyright (C) 2015 Jehan <jehan@gimp.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __ANIMATION_UTILS_H__
#define __ANIMATION_UTILS_H__
#include <gtk/gtk.h>
#define PLUG_IN_PROC "plug-in-animationplay"
#define PLUG_IN_BINARY "animation-play"
#define PLUG_IN_ROLE "gimp-animation-playback"
#define MAX_FRAMERATE 300.0
#define DEFAULT_FRAMERATE 24.0
typedef enum
{
ANIMATION_DND_TYPE_NONE = 0,
ANIMATION_DND_TYPE_PANEL = 1,
GIMP_DND_TYPE_LAST = ANIMATION_DND_TYPE_PANEL
} AnimationDndType;
void total_alpha_preview (guchar *drawing_data,
guint drawing_width,
guint drawing_height);
GeglBuffer * normal_blend (gint width,
gint height,
GeglBuffer *backdrop,
gdouble backdrop_scale_ratio,
gint backdrop_offset_x,
gint backdrop_offset_y,
GeglBuffer *source,
gdouble source_scale_ratio,
gint source_offset_x,
gint source_offset_y);
void show_scrolled_child (GtkScrolledWindow *window,
GtkWidget *child);
void hide_item (gint item,
gboolean recursive,
gboolean reset_color_tag);
void show_layer (gint item,
gint32 color_tag,
gdouble opacity);
gint compare_int_from (gconstpointer f1,
gconstpointer f2,
gpointer data);
#endif /* __ANIMATION_UTILS_H__ */

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,74 @@
/* GIMP - The GNU Image Manipulation Program
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
*
* animation.h
* Copyright (C) 2016 Jehan <jehan@gimp.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __ANIMATION_ANIMATIC_H__
#define __ANIMATION_ANIMATIC_H__
#include "animation.h"
#define ANIMATION_TYPE_ANIMATIC (animation_animatic_get_type ())
#define ANIMATION_ANIMATIC(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), ANIMATION_TYPE_ANIMATIC, AnimationAnimatic))
#define ANIMATION_ANIMATIC_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), ANIMATION_TYPE_ANIMATIC, AnimationAnimaticClass))
#define ANIMATION_IS_ANIMATIC(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), ANIMATION_TYPE_ANIMATIC))
#define ANIMATION_IS_ANIMATIC_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), ANIMATION_TYPE_ANIMATIC))
#define ANIMATION_ANIMATIC_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), ANIMATION_TYPE_ANIMATIC, AnimationAnimaticClass))
typedef struct _AnimationAnimatic AnimationAnimatic;
typedef struct _AnimationAnimaticClass AnimationAnimaticClass;
struct _AnimationAnimatic
{
Animation parent_instance;
};
struct _AnimationAnimaticClass
{
AnimationClass parent_class;
};
GType animation_animatic_get_type (void);
void animation_animatic_set_panel_duration (AnimationAnimatic *animatic,
gint panel,
gint duration);
gint animation_animatic_get_panel_duration (AnimationAnimatic *animatic,
gint panel);
void animation_animatic_set_comment (AnimationAnimatic *animatic,
gint panel,
const gchar *comment);
const gchar * animation_animatic_get_comment (AnimationAnimatic *animatic,
gint panel);
void animation_animatic_set_combine (AnimationAnimatic *animatic,
gint panel,
gboolean combine);
const gboolean animation_animatic_get_combine (AnimationAnimatic *animatic,
gint panel);
gint animation_animatic_get_panel (AnimationAnimatic *animation,
gint position);
gint animation_animatic_get_position (AnimationAnimatic *animation,
gint panel);
void animation_animatic_move_panel (AnimationAnimatic *animatic,
gint panel,
gint new_panel);
#endif /* __ANIMATION_ANIMATIC_H__ */

View File

@@ -0,0 +1,735 @@
/* GIMP - The GNU Image Manipulation Program
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
*
* animation-camera.c
* Copyright (C) 2016-2017 Jehan <jehan@gimp.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <libgimp/gimp.h>
#include "libgimp/stdplugins-intl.h"
#include "animation.h"
#include "animation-camera.h"
enum
{
PROP_0,
PROP_ANIMATION,
};
enum
{
CAMERA_CHANGED,
KEYFRAME_SET,
KEYFRAME_DELETED,
LAST_SIGNAL
};
typedef struct
{
gint x;
gint y;
}
Offset;
struct _AnimationCameraPrivate
{
Animation *animation;
/* Panning and tilting. */
GList *offsets;
GList *zoom;
/* Preview */
gint preview_position;
Offset *preview_offset;
gdouble preview_scale;
gboolean block_signals;
};
static void animation_camera_finalize (GObject *object);
static void animation_camera_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec);
static void animation_camera_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec);
static void animation_camera_emit_camera_changed (AnimationCamera *camera,
gint position);
static void animation_camera_get_real (AnimationCamera *camera,
gint position,
gint *x_offset,
gint *y_offset,
gdouble *scale);
G_DEFINE_TYPE (AnimationCamera, animation_camera, G_TYPE_OBJECT)
#define parent_class animation_camera_parent_class
static guint signals[LAST_SIGNAL] = { 0 };
static void
animation_camera_class_init (AnimationCameraClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->finalize = animation_camera_finalize;
object_class->get_property = animation_camera_get_property;
object_class->set_property = animation_camera_set_property;
/**
* AnimationCamera::camera-changed:
* @camera: the #AnimationCamera.
* @position:
* @duration:
*
* The ::camera-changed signal will be emitted when camera offsets,
* zoom, or other characteristics were updated between
* [@position; @position + @duration[.
*/
signals[CAMERA_CHANGED] =
g_signal_new ("camera-changed",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (AnimationCameraClass, camera_changed),
NULL, NULL,
NULL,
G_TYPE_NONE,
2,
G_TYPE_INT,
G_TYPE_INT);
/**
* AnimationCamera::keyframe-set:
* @camera: the #AnimationCamera.
* @position:
*
* The ::keyframe-set signal will be emitted when a keyframe is
* created or modified at @position.
*/
signals[KEYFRAME_SET] =
g_signal_new ("keyframe-set",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (AnimationCameraClass, keyframe_set),
NULL, NULL,
NULL,
G_TYPE_NONE,
1,
G_TYPE_INT);
/**
* AnimationCamera::keyframe-deleted:
* @camera: the #AnimationCamera.
* @position:
*
* The ::keyframe-set signal will be emitted when a keyframe is
* deleted at @position.
*/
signals[KEYFRAME_DELETED] =
g_signal_new ("keyframe-deleted",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (AnimationCameraClass, keyframe_deleted),
NULL, NULL,
NULL,
G_TYPE_NONE,
1,
G_TYPE_INT);
g_object_class_install_property (object_class, PROP_ANIMATION,
g_param_spec_object ("animation",
NULL, NULL,
ANIMATION_TYPE_ANIMATION,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY));
g_type_class_add_private (klass, sizeof (AnimationCameraPrivate));
}
static void
animation_camera_init (AnimationCamera *view)
{
view->priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
ANIMATION_TYPE_CAMERA,
AnimationCameraPrivate);
view->priv->preview_position = -1;
}
/************ Public Functions ****************/
/**
* animation_camera_new:
*
* Creates a new camera.
*/
AnimationCamera *
animation_camera_new (Animation *animation)
{
AnimationCamera *camera;
camera = g_object_new (ANIMATION_TYPE_CAMERA,
"animation", animation,
NULL);
return camera;
}
gboolean
animation_camera_has_offset_keyframe (AnimationCamera *camera,
gint position)
{
g_return_val_if_fail (position >= 0 &&
position < animation_get_duration (camera->priv->animation),
FALSE);
return g_list_nth_data (camera->priv->offsets, position) != NULL;
}
gboolean
animation_camera_has_zoom_keyframe (AnimationCamera *camera,
gint position)
{
g_return_val_if_fail (position >= 0 &&
position < animation_get_duration (camera->priv->animation),
FALSE);
return g_list_nth_data (camera->priv->zoom, position) != NULL;
}
void
animation_camera_set_offsets (AnimationCamera *camera,
gint position,
gint x,
gint y)
{
GList *iter;
Offset *offset;
g_return_if_fail (position >= 0 &&
position < animation_get_duration (camera->priv->animation));
iter = g_list_nth (camera->priv->offsets, position);
if (! iter)
{
gint length = g_list_length (camera->priv->offsets);
gint i;
for (i = length; i < position; i++)
{
camera->priv->offsets = g_list_append (camera->priv->offsets, NULL);
}
offset = g_new (Offset, 1);
camera->priv->offsets = g_list_append (camera->priv->offsets, offset);
}
else
{
if (! iter->data)
{
iter->data = g_new (Offset, 1);
}
offset = iter->data;
}
offset->x = x;
offset->y = y;
if (! camera->priv->block_signals)
{
g_signal_emit (camera, signals[KEYFRAME_SET], 0, position);
animation_camera_emit_camera_changed (camera, position);
}
}
void
animation_camera_zoom (AnimationCamera *camera,
gint position,
gdouble scale)
{
GList *iter;
gdouble *zoom;
g_return_if_fail (position >= 0 &&
position < animation_get_duration (camera->priv->animation));
iter = g_list_nth (camera->priv->zoom, position);
if (! iter)
{
gint length = g_list_length (camera->priv->zoom);
gint i;
for (i = length; i < position; i++)
{
camera->priv->zoom = g_list_append (camera->priv->zoom, NULL);
}
zoom = g_new (gdouble, 1);
camera->priv->zoom = g_list_append (camera->priv->zoom, zoom);
}
else
{
if (! iter->data)
{
iter->data = g_new (gdouble, 1);
}
zoom = iter->data;
}
*zoom = scale;
if (! camera->priv->block_signals)
{
g_signal_emit (camera, signals[KEYFRAME_SET], 0, position);
animation_camera_emit_camera_changed (camera, position);
}
}
void
animation_camera_delete_offset_keyframe (AnimationCamera *camera,
gint position)
{
GList *iter;
g_return_if_fail (position >= 0 &&
position < animation_get_duration (camera->priv->animation));
iter = g_list_nth (camera->priv->offsets, position);
if (iter && iter->data)
{
g_free (iter->data);
iter->data = NULL;
g_signal_emit (camera, signals[KEYFRAME_DELETED], 0, position);
animation_camera_emit_camera_changed (camera, position);
}
if (camera->priv->preview_position == position)
{
animation_camera_reset_preview (camera);
}
}
void
animation_camera_delete_zoom_keyframe (AnimationCamera *camera,
gint position)
{
GList *iter;
g_return_if_fail (position >= 0 &&
position < animation_get_duration (camera->priv->animation));
iter = g_list_nth (camera->priv->zoom, position);
if (iter && iter->data)
{
g_free (iter->data);
iter->data = NULL;
g_signal_emit (camera, signals[KEYFRAME_DELETED], 0, position);
animation_camera_emit_camera_changed (camera, position);
}
if (camera->priv->preview_position == position)
{
animation_camera_reset_preview (camera);
}
}
void
animation_camera_preview_keyframe (AnimationCamera *camera,
gint position,
gint x,
gint y,
gdouble scale)
{
g_return_if_fail (position >= 0 &&
position < animation_get_duration (camera->priv->animation));
if (! camera->priv->preview_offset)
camera->priv->preview_offset = g_new (Offset, 1);
camera->priv->preview_offset->x = x;
camera->priv->preview_offset->y = y;
camera->priv->preview_scale = scale;
camera->priv->preview_position = position;
g_signal_emit (camera, signals[KEYFRAME_SET], 0, position);
g_signal_emit (camera, signals[CAMERA_CHANGED], 0,
position, 1);
}
void
animation_camera_apply_preview (AnimationCamera *camera)
{
if (camera->priv->preview_position != -1)
{
gint preview_offset_x;
gint preview_offset_y;
gdouble preview_scale;
gint real_offset_x;
gint real_offset_y;
gdouble real_scale;
gint position;
animation_camera_get (camera, camera->priv->preview_position,
&preview_offset_x, &preview_offset_y,
&preview_scale);
animation_camera_get_real (camera, camera->priv->preview_position,
&real_offset_x, &real_offset_y,
&real_scale);
if (camera->priv->preview_offset)
g_free (camera->priv->preview_offset);
camera->priv->preview_offset = NULL;
position = camera->priv->preview_position;
camera->priv->preview_position = -1;
/* Do not run the changed signal twice and recompute twice the
* same frame. Just a little internal trick. */
camera->priv->block_signals = TRUE;
if (preview_offset_x != real_offset_x ||
preview_offset_y != real_offset_y)
{
animation_camera_set_offsets (camera, position,
preview_offset_x,
preview_offset_y);
}
if (preview_scale != real_scale)
{
animation_camera_zoom (camera, position, preview_scale);
}
camera->priv->block_signals = FALSE;
g_signal_emit (camera, signals[KEYFRAME_SET], 0, position);
animation_camera_emit_camera_changed (camera, position);
}
}
void
animation_camera_reset_preview (AnimationCamera *camera)
{
gboolean changed = FALSE;
gint position_changed = -1;
if (camera->priv->preview_position != -1)
{
gint preview_offset_x;
gint preview_offset_y;
gdouble preview_scale;
gint real_offset_x;
gint real_offset_y;
gdouble real_scale;
animation_camera_get (camera, camera->priv->preview_position,
&preview_offset_x, &preview_offset_y,
&preview_scale);
animation_camera_get_real (camera, camera->priv->preview_position,
&real_offset_x, &real_offset_y,
&real_scale);
changed = (preview_offset_x != real_offset_x ||
preview_offset_y != real_offset_y ||
preview_scale != real_scale);
position_changed = camera->priv->preview_position;
if (camera->priv->preview_offset)
g_free (camera->priv->preview_offset);
camera->priv->preview_offset = NULL;
}
camera->priv->preview_position = -1;
if (changed)
{
g_signal_emit (camera, signals[KEYFRAME_DELETED], 0,
position_changed);
g_signal_emit (camera, signals[CAMERA_CHANGED], 0,
position_changed, 1);
}
}
void
animation_camera_get (AnimationCamera *camera,
gint position,
gint *x_offset,
gint *y_offset,
gdouble *scale)
{
if (camera->priv->preview_position == position)
{
*x_offset = camera->priv->preview_offset->x;
*y_offset = camera->priv->preview_offset->y;
*scale = camera->priv->preview_scale;
}
else
{
animation_camera_get_real (camera, position,
x_offset, y_offset,
scale);
}
}
/************ Private Functions ****************/
static void
animation_camera_finalize (GObject *object)
{
g_list_free_full (ANIMATION_CAMERA (object)->priv->offsets, g_free);
g_list_free_full (ANIMATION_CAMERA (object)->priv->zoom, g_free);
G_OBJECT_CLASS (parent_class)->finalize (object);
}
static void
animation_camera_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
AnimationCamera *camera = ANIMATION_CAMERA (object);
switch (property_id)
{
case PROP_ANIMATION:
camera->priv->animation = g_value_get_object (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
animation_camera_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
AnimationCamera *camera = ANIMATION_CAMERA (object);
switch (property_id)
{
case PROP_ANIMATION:
g_value_set_object (value, camera->priv->animation);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
animation_camera_emit_camera_changed (AnimationCamera *camera,
gint position)
{
GList *iter;
gint prev_keyframe;
gint next_keyframe;
gint i;
if (position > 0)
{
i = position - 1;
iter = g_list_nth (camera->priv->offsets, i);
for (; iter && ! iter->data; iter = iter->prev, i--)
;
iter = g_list_nth (camera->priv->zoom, i);
prev_keyframe = i + 1;
for (; iter && ! iter->data; iter = iter->prev, i--)
;
prev_keyframe = MIN (i + 1, prev_keyframe);
}
else
{
prev_keyframe = 0;
}
if (position < animation_get_duration (camera->priv->animation) - 1)
{
i = position + 1;
iter = g_list_nth (camera->priv->offsets, i);
for (; iter && ! iter->data; iter = iter->next, i++)
;
if (iter && iter->data)
{
next_keyframe = i - 1;
iter = g_list_nth (camera->priv->zoom, i);
for (; iter && ! iter->data; iter = iter->next, i++)
;
next_keyframe = MAX (i - 1, next_keyframe);
}
else
{
next_keyframe = animation_get_duration (camera->priv->animation) - 1;
}
}
else
{
next_keyframe = animation_get_duration (camera->priv->animation) - 1;
}
g_signal_emit (camera, signals[CAMERA_CHANGED], 0,
prev_keyframe, next_keyframe - prev_keyframe + 1);
}
static void
animation_camera_get_real (AnimationCamera *camera,
gint position,
gint *x_offset,
gint *y_offset,
gdouble *scale)
{
Offset *position_keyframe;
gdouble *zoom_keyframe;
g_return_if_fail (position >= 0 &&
position < animation_get_duration (camera->priv->animation));
position_keyframe = g_list_nth_data (camera->priv->offsets, position);
if (position_keyframe)
{
/* There is a keyframe to this exact position. Use its values. */
*x_offset = position_keyframe->x;
*y_offset = position_keyframe->y;
}
else
{
GList *iter;
Offset *prev_keyframe = NULL;
Offset *next_keyframe = NULL;
gint prev_keyframe_pos;
gint next_keyframe_pos;
gint i;
/* This position is not a keyframe. */
if (position > 0)
{
i = MIN (position - 1, g_list_length (camera->priv->offsets) - 1);
iter = g_list_nth (camera->priv->offsets, i);
for (; iter && ! iter->data; iter = iter->prev, i--)
;
if (iter && iter->data)
{
prev_keyframe_pos = i;
prev_keyframe = iter->data;
}
}
if (position < animation_get_duration (camera->priv->animation) - 1)
{
i = position + 1;
iter = g_list_nth (camera->priv->offsets, i);
for (; iter && ! iter->data; iter = iter->next, i++)
;
if (iter && iter->data)
{
next_keyframe_pos = i;
next_keyframe = iter->data;
}
}
if (prev_keyframe == NULL && next_keyframe == NULL)
{
*x_offset = *y_offset = 0;
}
else if (prev_keyframe == NULL)
{
*x_offset = next_keyframe->x;
*y_offset = next_keyframe->y;
}
else if (next_keyframe == NULL)
{
*x_offset = prev_keyframe->x;
*y_offset = prev_keyframe->y;
}
else
{
/* XXX No curve editing or anything like this yet.
* All keyframing is linear in this first version.
*/
*x_offset = prev_keyframe->x + (position - prev_keyframe_pos) *
(next_keyframe->x - prev_keyframe->x) /
(next_keyframe_pos - prev_keyframe_pos);
*y_offset = prev_keyframe->y + (position - prev_keyframe_pos) *
(next_keyframe->y - prev_keyframe->y) /
(next_keyframe_pos - prev_keyframe_pos);
}
}
zoom_keyframe = g_list_nth_data (camera->priv->zoom, position);
if (zoom_keyframe)
{
/* There is a keyframe to this exact position. Use its values. */
*scale = *zoom_keyframe;
}
else
{
GList *iter;
gdouble *prev_keyframe = NULL;
gdouble *next_keyframe = NULL;
gint prev_keyframe_pos;
gint next_keyframe_pos;
gint i;
/* This position is not a keyframe. */
if (position > 0)
{
i = MIN (position - 1, g_list_length (camera->priv->zoom) - 1);
iter = g_list_nth (camera->priv->zoom, i);
for (; iter && ! iter->data; iter = iter->prev, i--)
;
if (iter && iter->data)
{
prev_keyframe_pos = i;
prev_keyframe = iter->data;
}
}
if (position < animation_get_duration (camera->priv->animation) - 1)
{
i = position + 1;
iter = g_list_nth (camera->priv->zoom, i);
for (; iter && ! iter->data; iter = iter->next, i++)
;
if (iter && iter->data)
{
next_keyframe_pos = i;
next_keyframe = iter->data;
}
}
if (prev_keyframe == NULL && next_keyframe == NULL)
{
*scale = 1.0;
}
else if (prev_keyframe == NULL)
{
*scale = *next_keyframe;
}
else if (next_keyframe == NULL)
{
*scale = *prev_keyframe;
}
else
{
/* XXX No curve editing or anything like this yet.
* All keyframing is linear in this first version.
*/
*scale = *prev_keyframe + (position - prev_keyframe_pos) *
(*next_keyframe - *prev_keyframe) /
(next_keyframe_pos - prev_keyframe_pos);
}
}
}

View File

@@ -0,0 +1,90 @@
/* GIMP - The GNU Image Manipulation Program
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
*
* animation-camera.h
* Copyright (C) 2016-2017 Jehan <jehan@gimp.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __ANIMATION_CAMERA_H__
#define __ANIMATION_CAMERA_H__
#define ANIMATION_TYPE_CAMERA (animation_camera_get_type ())
#define ANIMATION_CAMERA(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), ANIMATION_TYPE_CAMERA, AnimationCamera))
#define ANIMATION_CAMERA_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), ANIMATION_TYPE_CAMERA, AnimationCameraClass))
#define ANIMATION_IS_CAMERA(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), ANIMATION_TYPE_CAMERA))
#define ANIMATION_IS_CAMERA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), ANIMATION_TYPE_CAMERA))
#define ANIMATION_CAMERA_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), ANIMATION_TYPE_CAMERA, AnimationCameraClass))
typedef struct _AnimationCamera AnimationCamera;
typedef struct _AnimationCameraClass AnimationCameraClass;
typedef struct _AnimationCameraPrivate AnimationCameraPrivate;
struct _AnimationCamera
{
GObject parent_instance;
AnimationCameraPrivate *priv;
};
struct _AnimationCameraClass
{
GObjectClass parent_class;
/* Signals */
void (*camera_changed) (AnimationCamera *camera,
gint position,
gint duration);
void (*keyframe_set) (AnimationCamera *camera,
gint position);
void (*keyframe_deleted) (AnimationCamera *camera,
gint position);
};
GType animation_camera_get_type (void) G_GNUC_CONST;
AnimationCamera * animation_camera_new (Animation *animation);
gboolean animation_camera_has_offset_keyframe (AnimationCamera *camera,
gint position);
gboolean animation_camera_has_zoom_keyframe (AnimationCamera *camera,
gint position);
void animation_camera_set_offsets (AnimationCamera *camera,
gint position,
gint x,
gint y);
void animation_camera_zoom (AnimationCamera *camera,
gint position,
gdouble scale);
void animation_camera_delete_offset_keyframe (AnimationCamera *camera,
gint position);
void animation_camera_delete_zoom_keyframe (AnimationCamera *camera,
gint position);
void animation_camera_preview_keyframe (AnimationCamera *camera,
gint position,
gint x,
gint y,
gdouble scale);
void animation_camera_apply_preview (AnimationCamera *camera);
void animation_camera_reset_preview (AnimationCamera *camera);
void animation_camera_get (AnimationCamera *camera,
gint position,
gint *x_offset,
gint *y_offset,
gdouble *scale);
#endif /* __ANIMATION_CAMERA_H__ */

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,99 @@
/* GIMP - The GNU Image Manipulation Program
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
*
* animation.h
* Copyright (C) 2016 Jehan <jehan@gimp.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __ANIMATION_CEL_ANIMATION_H__
#define __ANIMATION_CEL_ANIMATION_H__
#include "animation.h"
#define ANIMATION_TYPE_CEL_ANIMATION (animation_cel_animation_get_type ())
#define ANIMATION_CEL_ANIMATION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), ANIMATION_TYPE_CEL_ANIMATION, AnimationCelAnimation))
#define ANIMATION_CEL_ANIMATION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), ANIMATION_TYPE_CEL_ANIMATION, AnimationCelAnimationClass))
#define ANIMATION_IS_CEL_ANIMATION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), ANIMATION_TYPE_CEL_ANIMATION))
#define ANIMATION_IS_CEL_ANIMATION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), ANIMATION_TYPE_CEL_ANIMATION))
#define ANIMATION_CEL_ANIMATION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), ANIMATION_TYPE_CEL_ANIMATION, AnimationCelAnimationClass))
typedef struct _AnimationCelAnimation AnimationCelAnimation;
typedef struct _AnimationCelAnimationClass AnimationCelAnimationClass;
typedef struct _AnimationCelAnimationPrivate AnimationCelAnimationPrivate;
struct _AnimationCelAnimation
{
Animation parent_instance;
AnimationCelAnimationPrivate *priv;
};
struct _AnimationCelAnimationClass
{
AnimationClass parent_class;
};
GType animation_cel_animation_get_type (void);
void animation_cel_animation_set_layers (AnimationCelAnimation *animation,
gint level,
gint position,
const GList *layers);
const GList * animation_cel_animation_get_layers (AnimationCelAnimation *animation,
gint level,
gint position);
void animation_cel_animation_set_comment (AnimationCelAnimation *animation,
gint position,
const gchar *comment);
const gchar * animation_cel_animation_get_comment (AnimationCelAnimation *animation,
gint position);
void animation_cel_animation_set_duration (AnimationCelAnimation *animation,
gint duration);
void animation_cel_animation_set_onion_skins (AnimationCelAnimation *animation,
gint skins);
gint animation_cel_animation_get_onion_skins (AnimationCelAnimation *animation);
GObject * animation_cel_animation_get_main_camera (AnimationCelAnimation *animation);
gint animation_cel_animation_get_levels (AnimationCelAnimation *animation);
gint animation_cel_animation_level_up (AnimationCelAnimation *animation,
gint level);
gint animation_cel_animation_level_down (AnimationCelAnimation *animation,
gint level);
gboolean animation_cel_animation_level_delete (AnimationCelAnimation *animation,
gint level);
gboolean animation_cel_animation_level_add (AnimationCelAnimation *animation,
gint level);
void animation_cel_animation_set_track_title (AnimationCelAnimation *animation,
gint level,
const gchar *title);
const gchar * animation_cel_animation_get_track_title (AnimationCelAnimation *animation,
gint level);
gboolean animation_cel_animation_cel_delete (AnimationCelAnimation *animation,
gint level,
gint position);
gboolean animation_cel_animation_cel_add (AnimationCelAnimation *animation,
gint level,
gint position,
gboolean dup_previous);
#endif /* __ANIMATION_CEL_ANIMATION_H__ */

View File

@@ -0,0 +1,933 @@
/* GIMP - The GNU Image Manipulation Program
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
*
* animation-playback.c
* Copyright (C) 2016 Jehan <jehan@gimp.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <string.h>
#include <libgimp/gimp.h>
#include "libgimp/stdplugins-intl.h"
#include "animation.h"
#include "animation-playback.h"
#include "animation-renderer.h"
enum
{
START,
STOP,
RANGE,
POSITION,
LOW_FRAMERATE,
PROXY_CHANGED,
RENDERING,
LAST_SIGNAL
};
enum
{
PROP_0,
PROP_ANIMATION
};
struct _AnimationPlaybackPrivate
{
Animation *animation;
GObject *renderer;
/* State of the currently loaded playback. */
gint position;
/* Playback can be a subset of frames. */
gint start;
gint stop;
gboolean stop_at_end;
gdouble proxy_ratio;
guint timer;
gint64 start_time;
gint64 frames_played;
};
typedef struct
{
AnimationPlayback *playback;
gint level;
} ParseStatus;
static void animation_playback_finalize (GObject *object);
static void animation_playback_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec);
static void animation_playback_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec);
static void on_duration_changed (Animation *animation,
gint duration,
AnimationPlayback *playback);
static void on_cache_updated (AnimationRenderer *renderer,
gint position,
AnimationPlayback *playback);
static void on_rendering (AnimationRenderer *renderer,
gboolean rendering,
AnimationPlayback *playback);
/* Timer callback for playback. */
static gboolean animation_playback_advance_frame_callback (AnimationPlayback *playback);
/* XML parsing */
static void animation_playback_start_element (GMarkupParseContext *context,
const gchar *element_name,
const gchar **attribute_names,
const gchar **attribute_values,
gpointer user_data,
GError **error);
static void animation_playback_end_element (GMarkupParseContext *context,
const gchar *element_name,
gpointer user_data,
GError **error);
G_DEFINE_TYPE (AnimationPlayback, animation_playback, G_TYPE_OBJECT)
#define parent_class animation_playback_parent_class
static guint animation_playback_signals[LAST_SIGNAL] = { 0 };
static void
animation_playback_class_init (AnimationPlaybackClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
/**
* AnimationPlayback::start:
* @playback: the #AnimationPlayback.
*
* The @playback starts to play.
*/
animation_playback_signals[START] =
g_signal_new ("start",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (AnimationPlaybackClass, start),
NULL, NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE,
0);
/**
* AnimationPlayback::stop:
* @playback: the #AnimationPlayback.
*
* The @playback stops playing.
*/
animation_playback_signals[STOP] =
g_signal_new ("stop",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (AnimationPlaybackClass, stop),
NULL, NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE,
0);
/**
* AnimationPlayback::range:
* @playback: the #AnimationPlayback.
* @start: the playback start frame.
* @stop: the playback last frame.
*
* The ::range signal is emitted when the playback range is
* updated.
*/
animation_playback_signals[RANGE] =
g_signal_new ("range",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (AnimationPlaybackClass, range),
NULL, NULL,
NULL,
G_TYPE_NONE,
2,
G_TYPE_INT,
G_TYPE_INT);
/**
* AnimationPlayback::position:
* @playback: the #AnimationPlayback.
* @position: current position to be displayed.
* @buffer: the #GeglBuffer for the frame at @position.
* @must_draw_null: meaning of a %NULL @buffer.
* %TRUE means we have to draw an empty frame.
* %FALSE means the new frame is same as the current frame.
*
* This signal indicates that playback position has changed so that
* the GUI can display it and process other updates.
*/
animation_playback_signals[POSITION] =
g_signal_new ("position",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (AnimationPlaybackClass, position),
NULL, NULL,
NULL,
G_TYPE_NONE,
3,
G_TYPE_INT,
GEGL_TYPE_BUFFER,
G_TYPE_BOOLEAN);
/**
* AnimationPlayback::low-framerate:
* @playback: the #AnimationPlayback.
* @actual_fps: the current playback framerate in fps.
*
* The ::low-framerate signal is emitted when the playback framerate
* is lower than expected. It is also emitted once when the framerate
* comes back to acceptable rate.
*/
animation_playback_signals[LOW_FRAMERATE] =
g_signal_new ("low-framerate",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (AnimationPlaybackClass, low_framerate),
NULL, NULL,
g_cclosure_marshal_VOID__DOUBLE,
G_TYPE_NONE,
1,
G_TYPE_DOUBLE);
/**
* AnimationPlayback::proxy:
* @playback: the #AnimationPlayback.
* @ratio: the current proxy ratio [0-1.0].
*
* The ::proxy signal is emitted to announce a change of proxy size.
*/
animation_playback_signals[PROXY_CHANGED] =
g_signal_new ("proxy-changed",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (AnimationPlaybackClass, proxy_changed),
NULL, NULL,
g_cclosure_marshal_VOID__DOUBLE,
G_TYPE_NONE,
1,
G_TYPE_DOUBLE);
/**
* AnimationPlayback::rendering:
* @playback: the #AnimationPlayback.
* @has_queue: whether there is more to render.
*
* The ::rendering signal will be emitted when the renderer has queued
* frames, and a last time with @has_queue as #TRUE when all is
* rendered. It mostly passes along AnimationRenderer::rendering
* signal, since only @playback has access to the renderer object.
*/
animation_playback_signals[RENDERING] =
g_signal_new ("rendering",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (AnimationRendererClass, rendering),
NULL, NULL,
NULL,
G_TYPE_NONE,
1,
G_TYPE_BOOLEAN);
object_class->finalize = animation_playback_finalize;
object_class->set_property = animation_playback_set_property;
object_class->get_property = animation_playback_get_property;
/**
* AnimationPlayback:animation:
*
* The associated #Animation.
*/
g_object_class_install_property (object_class, PROP_ANIMATION,
g_param_spec_object ("animation",
NULL, NULL,
ANIMATION_TYPE_ANIMATION,
G_PARAM_READWRITE));
g_type_class_add_private (klass, sizeof (AnimationPlaybackPrivate));
}
static void
animation_playback_init (AnimationPlayback *playback)
{
playback->priv = G_TYPE_INSTANCE_GET_PRIVATE (playback,
ANIMATION_TYPE_PLAYBACK,
AnimationPlaybackPrivate);
playback->priv->proxy_ratio = 1.0;
}
/************ Public Functions ****************/
AnimationPlayback *
animation_playback_new (void)
{
AnimationPlayback *playback;
playback = g_object_new (ANIMATION_TYPE_PLAYBACK,
NULL);
return playback;
}
gchar *
animation_playback_serialize (AnimationPlayback *playback)
{
gchar *xml;
gchar proxy[6];
/* Make sure to have a locale-independent string. Also no need to have
* useless precision. This should give 3 digits after the decimal point.
* More than enough.
*/
g_ascii_dtostr ((gchar*) &proxy, 6, playback->priv->proxy_ratio);
xml = g_strdup_printf ("<playback position=\"%d\" "
"start=\"%d\" stop=\"%d\" proxy=\"%s\"/>",
playback->priv->position,
playback->priv->start,
playback->priv->stop,
proxy);
return xml;
}
void
animation_playback_set_animation (AnimationPlayback *playback,
Animation *animation,
const gchar *xml)
{
g_object_set (playback,
"animation", animation,
NULL);
if (xml)
{
/* Reset to last known playback status. */
const GMarkupParser markup_parser =
{
animation_playback_start_element,
animation_playback_end_element,
NULL, /* text */
NULL, /* passthrough */
NULL /* error */
};
GMarkupParseContext *context;
ParseStatus status = { 0, };
GError *error = NULL;
status.playback = playback;
status.level = 0;
context = g_markup_parse_context_new (&markup_parser,
0, &status, NULL);
g_markup_parse_context_parse (context, xml, strlen (xml), &error);
if (error == NULL)
g_markup_parse_context_end_parse (context, &error);
g_markup_parse_context_free (context);
if (error)
g_warning ("Error parsing XML: %s", error->message);
g_clear_error (&error);
}
}
Animation *
animation_playback_get_animation (AnimationPlayback *playback)
{
return playback->priv->animation;
}
gint
animation_playback_get_position (AnimationPlayback *playback)
{
return playback->priv->position;
}
GeglBuffer *
animation_playback_get_buffer (AnimationPlayback *playback,
gint position)
{
AnimationRenderer *renderer;
renderer = ANIMATION_RENDERER (playback->priv->renderer);
return animation_renderer_get_buffer (renderer, position);
}
gboolean
animation_playback_is_playing (AnimationPlayback *playback)
{
return (playback->priv->timer != 0);
}
void
animation_playback_play (AnimationPlayback *playback)
{
gint duration;
duration = (gint) (1000.0 /
animation_get_framerate (playback->priv->animation));
if (playback->priv->timer)
{
/* It means we are already playing, so we should not need to play
* again.
* Still be liberal and simply remove the timer before creating a
* new one. */
g_source_remove (playback->priv->timer);
g_signal_emit (playback, animation_playback_signals[STOP], 0);
}
playback->priv->start_time = g_get_monotonic_time ();
playback->priv->frames_played = 1;
playback->priv->timer = g_timeout_add ((guint) duration,
(GSourceFunc) animation_playback_advance_frame_callback,
playback);
g_signal_emit (playback, animation_playback_signals[START], 0);
}
void
animation_playback_stop (AnimationPlayback *playback)
{
if (playback->priv->timer)
{
/* Stop playing by removing any playback timer. */
g_source_remove (playback->priv->timer);
playback->priv->timer = 0;
g_signal_emit (playback, animation_playback_signals[STOP], 0);
}
}
void
animation_playback_next (AnimationPlayback *playback)
{
AnimationRenderer *renderer;
GeglBuffer *buffer = NULL;
gint previous_pos = playback->priv->position;
gboolean identical;
if (! playback->priv->animation)
return;
playback->priv->position = playback->priv->start +
((playback->priv->position - playback->priv->start + 1) %
(playback->priv->stop - playback->priv->start + 1));
renderer = ANIMATION_RENDERER (playback->priv->renderer);
identical = animation_renderer_identical (renderer,
previous_pos,
playback->priv->position);
if (! identical)
{
buffer = animation_renderer_get_buffer (renderer,
playback->priv->position);
}
g_signal_emit (playback, animation_playback_signals[POSITION], 0,
playback->priv->position, buffer, ! identical);
if (buffer != NULL)
{
g_object_unref (buffer);
}
}
void
animation_playback_prev (AnimationPlayback *playback)
{
AnimationRenderer *renderer;
GeglBuffer *buffer = NULL;
gint prev_pos = playback->priv->position;
gboolean identical;
if (! playback->priv->animation)
return;
if (playback->priv->position == playback->priv->start)
{
playback->priv->position = animation_playback_get_stop (playback);
}
else
{
--playback->priv->position;
}
renderer = ANIMATION_RENDERER (playback->priv->renderer);
identical = animation_renderer_identical (renderer,
prev_pos,
playback->priv->position);
if (! identical)
{
buffer = animation_renderer_get_buffer (renderer, playback->priv->position);
}
g_signal_emit (playback, animation_playback_signals[POSITION], 0,
playback->priv->position, buffer, ! identical);
if (buffer)
g_object_unref (buffer);
}
void
animation_playback_jump (AnimationPlayback *playback,
gint index)
{
AnimationRenderer *renderer;
GeglBuffer *buffer = NULL;
gint prev_pos = playback->priv->position;
gboolean identical;
if (! playback->priv->animation)
return;
if (index < playback->priv->start ||
index > playback->priv->stop)
return;
else
playback->priv->position = index;
renderer = ANIMATION_RENDERER (playback->priv->renderer);
identical = animation_renderer_identical (renderer,
prev_pos,
playback->priv->position);
if (! identical)
{
buffer = animation_renderer_get_buffer (renderer, playback->priv->position);
}
g_signal_emit (playback, animation_playback_signals[POSITION], 0,
playback->priv->position, buffer, ! identical);
if (buffer)
g_object_unref (buffer);
}
void
animation_playback_set_start (AnimationPlayback *playback,
gint index)
{
gint duration;
if (! playback->priv->animation)
return;
duration = animation_get_duration (playback->priv->animation);
if (index < 0 ||
index >= duration)
{
playback->priv->start = 0;
}
else
{
playback->priv->start = index;
}
if (playback->priv->stop < playback->priv->start)
{
playback->priv->stop = duration - 1;
playback->priv->stop_at_end = TRUE;
}
g_signal_emit (playback, animation_playback_signals[RANGE], 0,
playback->priv->start, playback->priv->stop);
if (playback->priv->position < playback->priv->start ||
playback->priv->position > playback->priv->stop)
{
animation_playback_jump (playback, playback->priv->start);
}
}
gint
animation_playback_get_start (AnimationPlayback *playback)
{
return playback->priv->start;
}
void
animation_playback_set_stop (AnimationPlayback *playback,
gint index)
{
gint duration;
if (! playback->priv->animation)
return;
duration = animation_get_duration (playback->priv->animation);
if (index < 0 ||
index >= duration)
{
playback->priv->stop = duration - 1;
playback->priv->stop_at_end = TRUE;
}
else
{
playback->priv->stop = index;
if (index == duration - 1)
playback->priv->stop_at_end = TRUE;
}
if (playback->priv->stop < playback->priv->start)
{
playback->priv->start = 0;
}
g_signal_emit (playback, animation_playback_signals[RANGE], 0,
playback->priv->start, playback->priv->stop);
if (playback->priv->position < playback->priv->start ||
playback->priv->position > playback->priv->stop)
{
animation_playback_jump (playback, playback->priv->start);
}
}
gint
animation_playback_get_stop (AnimationPlayback *playback)
{
return playback->priv->stop;
}
void
animation_playback_get_size (AnimationPlayback *playback,
gint *width,
gint *height)
{
animation_get_size (playback->priv->animation,
width, height);
/* Apply proxy ratio. */
*width *= playback->priv->proxy_ratio;
*height *= playback->priv->proxy_ratio;
}
void
animation_playback_set_proxy (AnimationPlayback *playback,
gdouble ratio)
{
g_return_if_fail (ratio > 0.0 && ratio <= 1.0);
if (playback->priv->proxy_ratio != ratio)
{
playback->priv->proxy_ratio = ratio;
g_signal_emit (playback,
animation_playback_signals[PROXY_CHANGED],
0, ratio);
}
}
gdouble
animation_playback_get_proxy (AnimationPlayback *playback)
{
return playback->priv->proxy_ratio;
}
/************ Private Functions ****************/
static void
animation_playback_finalize (GObject *object)
{
AnimationPlayback *playback = ANIMATION_PLAYBACK (object);
g_object_unref (playback->priv->renderer);
if (playback->priv->animation)
g_object_unref (playback->priv->animation);
G_OBJECT_CLASS (parent_class)->finalize (object);
}
static void
animation_playback_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
AnimationPlayback *playback = ANIMATION_PLAYBACK (object);
switch (property_id)
{
case PROP_ANIMATION:
{
Animation *animation;
if (playback->priv->renderer)
g_object_unref (playback->priv->renderer);
if (playback->priv->animation)
g_object_unref (playback->priv->animation);
animation = g_value_dup_object (value);
playback->priv->animation = animation;
playback->priv->renderer = NULL;
if (! animation)
break;
/* Default playback is the full range of frames. */
playback->priv->position = 0;
playback->priv->start = 0;
playback->priv->stop = animation_get_duration (animation) - 1;
playback->priv->stop_at_end = TRUE;
g_signal_connect (animation, "duration-changed",
G_CALLBACK (on_duration_changed), playback);
playback->priv->renderer = animation_renderer_new (object);
g_signal_connect (playback->priv->renderer, "cache-updated",
G_CALLBACK (on_cache_updated), playback);
g_signal_connect (playback->priv->renderer, "rendering",
G_CALLBACK (on_rendering), playback);
}
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
animation_playback_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
AnimationPlayback *playback = ANIMATION_PLAYBACK (object);
switch (property_id)
{
case PROP_ANIMATION:
g_value_set_object (value, playback->priv->animation);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
on_duration_changed (Animation *animation,
gint duration,
AnimationPlayback *playback)
{
if (! playback->priv->animation)
return;
if (playback->priv->stop >= duration ||
playback->priv->stop_at_end)
{
playback->priv->stop = duration - 1;
playback->priv->stop_at_end = TRUE;
}
if (playback->priv->start >= duration ||
playback->priv->start > playback->priv->stop)
{
playback->priv->start = 0;
}
if (playback->priv->position < playback->priv->start ||
playback->priv->position > playback->priv->stop)
{
playback->priv->position = playback->priv->start;
}
g_signal_emit (playback, animation_playback_signals[RANGE], 0,
playback->priv->start, playback->priv->stop);
}
static void
on_cache_updated (AnimationRenderer *renderer,
gint position,
AnimationPlayback *playback)
{
if (animation_playback_get_position (playback) == position)
{
AnimationRenderer *renderer;
GeglBuffer *buffer;
renderer = ANIMATION_RENDERER (playback->priv->renderer);
buffer = animation_renderer_get_buffer (renderer, position);
g_signal_emit (playback, animation_playback_signals[POSITION], 0,
position, buffer, TRUE);
if (buffer)
{
g_object_unref (buffer);
}
}
}
static void
on_rendering (AnimationRenderer *renderer,
gboolean rendering,
AnimationPlayback *playback)
{
/* Just transform the renderer's "rendering" signal into a playback's
* one to pass the information along to the GUI.
*/
g_signal_emit (playback, animation_playback_signals[RENDERING], 0,
rendering);
}
static gboolean
animation_playback_advance_frame_callback (AnimationPlayback *playback)
{
gdouble framerate;
gint64 duration;
gint64 duration_since_start;
static gboolean prev_low_framerate = FALSE;
framerate = animation_get_framerate (playback->priv->animation);
animation_playback_next (playback);
duration = (gint) (1000.0 / framerate);
duration_since_start = (g_get_monotonic_time () - playback->priv->start_time) / 1000;
duration = duration - (duration_since_start - playback->priv->frames_played * duration);
if (duration < 1)
{
if (prev_low_framerate)
{
/* Let's only warn the user for several subsequent slow frames. */
gdouble real_framerate;
real_framerate = (gdouble) playback->priv->frames_played * 1000.0 / duration_since_start;
if (real_framerate < framerate)
g_signal_emit (playback, animation_playback_signals[LOW_FRAMERATE], 0,
real_framerate);
}
duration = 1;
prev_low_framerate = TRUE;
}
else
{
if (prev_low_framerate)
{
/* Let's reset framerate warning. */
g_signal_emit (playback, animation_playback_signals[LOW_FRAMERATE], 0,
framerate);
}
prev_low_framerate = FALSE;
}
playback->priv->frames_played++;
playback->priv->timer = g_timeout_add ((guint) duration,
(GSourceFunc) animation_playback_advance_frame_callback,
(AnimationPlayback *) playback);
return G_SOURCE_REMOVE;
}
static void
animation_playback_start_element (GMarkupParseContext *context,
const gchar *element_name,
const gchar **attribute_names,
const gchar **attribute_values,
gpointer user_data,
GError **error)
{
const gchar **names = attribute_names;
const gchar **values = attribute_values;
ParseStatus *status = (ParseStatus *) user_data;
AnimationPlayback *playback = status->playback;
if (status->level == 1 && g_strcmp0 (element_name, "playback") == 0)
{
gint duration;
duration = animation_get_duration (playback->priv->animation);
while (*names && *values)
{
if (strcmp (*names, "position") == 0 && **values)
{
gint position = g_ascii_strtoll (*values, NULL, 10);
if (position >= duration)
{
g_set_error (error, 0, 0,
_("Playback position %d out of range [0, %d]."),
position, duration - 1);
}
else
{
playback->priv->position = position;
}
}
else if (strcmp (*names, "start") == 0 && **values)
{
gint start = g_ascii_strtoll (*values, NULL, 10);
if (start >= duration)
{
g_set_error (error, 0, 0,
_("Playback start %d out of range [0, %d]."),
start, duration - 1);
}
else
{
playback->priv->start = start;
}
}
else if (strcmp (*names, "stop") == 0 && **values)
{
gint stop = g_ascii_strtoll (*values, NULL, 10);
if (stop >= duration)
{
g_set_error (error, 0, 0,
_("Playback stop %d out of range [0, %d]."),
stop, duration - 1);
}
else
{
playback->priv->stop = stop;
playback->priv->stop_at_end = (stop == duration - 1);
}
}
else if (strcmp (*names, "proxy") == 0 && **values)
{
gdouble ratio = g_ascii_strtod (*values, NULL);
animation_playback_set_proxy (playback, ratio);
}
names++;
values++;
}
if (playback->priv->stop < playback->priv->start)
{
playback->priv->stop = duration - 1;
playback->priv->stop_at_end = TRUE;
}
if (playback->priv->position < playback->priv->start ||
playback->priv->position > playback->priv->stop)
{
playback->priv->position = playback->priv->start;
}
}
status->level++;
}
static void
animation_playback_end_element (GMarkupParseContext *context,
const gchar *element_name,
gpointer user_data,
GError **error)
{
((ParseStatus *) user_data)->level--;
}

View File

@@ -0,0 +1,101 @@
/* GIMP - The GNU Image Manipulation Program
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
*
* animation_playback.h
* Copyright (C) 2016 Jehan <jehan@gimp.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __ANIMATION_PLAYBACK_H__
#define __ANIMATION_PLAYBACK_H__
#define ANIMATION_TYPE_PLAYBACK (animation_playback_get_type ())
#define ANIMATION_PLAYBACK(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), ANIMATION_TYPE_PLAYBACK, AnimationPlayback))
#define ANIMATION_PLAYBACK_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), ANIMATION_TYPE_PLAYBACK, AnimationPlaybackClass))
#define ANIMATION_IS_PLAYBACK(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), ANIMATION_TYPE_PLAYBACK))
#define ANIMATION_IS_PLAYBACK_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), ANIMATION_TYPE_PLAYBACK))
#define ANIMATION_PLAYBACK_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), ANIMATION_TYPE_PLAYBACK, AnimationPlaybackClass))
typedef struct _AnimationPlayback AnimationPlayback;
typedef struct _AnimationPlaybackClass AnimationPlaybackClass;
typedef struct _AnimationPlaybackPrivate AnimationPlaybackPrivate;
struct _AnimationPlayback
{
GObject parent_instance;
AnimationPlaybackPrivate *priv;
};
struct _AnimationPlaybackClass
{
GObjectClass parent_class;
/* Signals */
void (*start) (AnimationPlayback *playback);
void (*stop) (AnimationPlayback *playback);
void (*range) (AnimationPlayback *playback,
gint start,
gint stop);
void (*position) (AnimationPlayback *playback,
gint position,
GeglBuffer *buffer,
gboolean must_draw_null);
void (*low_framerate) (AnimationPlayback *playback,
gdouble actual_fps);
void (*proxy_changed) (AnimationPlayback *animation,
gdouble ratio);
void (*rendering) (AnimationPlayback *renderer,
gboolean has_render_queue);
};
GType animation_playback_get_type (void);
AnimationPlayback * animation_playback_new (void);
gchar * animation_playback_serialize (AnimationPlayback *playback);
void animation_playback_set_animation (AnimationPlayback *playback,
Animation *animation,
const gchar *xml);
Animation * animation_playback_get_animation (AnimationPlayback *playback);
gint animation_playback_get_position (AnimationPlayback *playback);
GeglBuffer * animation_playback_get_buffer (AnimationPlayback *playback,
gint position);
gboolean animation_playback_is_playing (AnimationPlayback *playback);
void animation_playback_play (AnimationPlayback *playback);
void animation_playback_stop (AnimationPlayback *playback);
void animation_playback_next (AnimationPlayback *playback);
void animation_playback_prev (AnimationPlayback *playback);
void animation_playback_jump (AnimationPlayback *playback,
gint index);
void animation_playback_set_start (AnimationPlayback *playback,
gint index);
gint animation_playback_get_start (AnimationPlayback *playback);
void animation_playback_set_stop (AnimationPlayback *playback,
gint index);
gint animation_playback_get_stop (AnimationPlayback *playback);
void animation_playback_get_size (AnimationPlayback *playback,
gint *width,
gint *height);
void animation_playback_set_proxy (AnimationPlayback *playback,
gdouble ratio);
gdouble animation_playback_get_proxy (AnimationPlayback *playback);
#endif /* __ANIMATION_PLAYBACK_H__ */

View File

@@ -0,0 +1,717 @@
/* GIMP - The GNU Image Manipulation Program
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
*
* animation-renderer.c
* Copyright (C) 2017 Jehan <jehan@gimp.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <libgimp/gimp.h>
#include <libgimp/stdplugins-intl.h>
#include "animation-utils.h"
#include "animation.h"
#include "animation-playback.h"
#include "animation-renderer.h"
enum
{
CACHE_UPDATED,
RENDERING,
LAST_SIGNAL
};
enum
{
PROP_0,
PROP_PLAYBACK
};
struct _AnimationRendererPrivate
{
AnimationPlayback *playback;
GAsyncQueue *queue;
GAsyncQueue *ack_queue;
guint idle_id;
/* Frames are cached as GEGL buffers. */
GMutex lock;
GeglBuffer **cache;
gchar **hashes;
GHashTable *cache_table;
gint cache_size;
GThread *queue_thread;
};
static void animation_renderer_finalize (GObject *object);
static void animation_renderer_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec);
static void animation_renderer_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec);
static gpointer animation_renderer_process_queue (AnimationRenderer *renderer);
static gboolean animation_renderer_idle_update (AnimationRenderer *renderer);
static void on_proxy_changed (AnimationPlayback *animation,
gdouble ratio,
AnimationRenderer *renderer);
static void on_playback_position (AnimationPlayback *animation,
gint frame_number,
GeglBuffer *buffer,
gboolean must_draw_null,
AnimationRenderer *renderer);
static void on_size_changed (Animation *animation,
gint width,
gint height,
AnimationRenderer *renderer);
static void on_frames_changed (Animation *animation,
gint position,
gint length,
AnimationRenderer *renderer);
static void on_duration_changed (Animation *animation,
gint duration,
AnimationRenderer *renderer);
static void on_animation_loaded (Animation *animation,
AnimationRenderer *renderer);
G_DEFINE_TYPE (AnimationRenderer, animation_renderer, G_TYPE_OBJECT)
#define parent_class animation_renderer_parent_class
static guint signals[LAST_SIGNAL] = { 0 };
static void
animation_renderer_class_init (AnimationRendererClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
/**
* AnimationRenderer::cache-updated:
* @renderer: the #AnimationRenderer.
* @position: the frame position whose cache was updated.
*
* The ::cache-updated signal will be emitted when the contents
* of frame at @position changes.
*/
signals[CACHE_UPDATED] =
g_signal_new ("cache-updated",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (AnimationRendererClass, cache_updated),
NULL, NULL,
NULL,
G_TYPE_NONE,
1,
G_TYPE_INT);
/**
* AnimationRenderer::rendering:
* @renderer: the #AnimationRenderer.
* @has_queue: whether there is more to render.
*
* The ::rendering signal will be emitted when the renderer has queued
* frames, and a last time with @has_queue as #TRUE when all is
* rendered.
*/
signals[RENDERING] =
g_signal_new ("rendering",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (AnimationRendererClass, rendering),
NULL, NULL,
NULL,
G_TYPE_NONE,
1,
G_TYPE_BOOLEAN);
object_class->finalize = animation_renderer_finalize;
object_class->set_property = animation_renderer_set_property;
object_class->get_property = animation_renderer_get_property;
/**
* AnimationRenderer:animation:
*
* The associated #Animation.
*/
g_object_class_install_property (object_class, PROP_PLAYBACK,
g_param_spec_object ("playback",
NULL, NULL,
ANIMATION_TYPE_PLAYBACK,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY));
g_type_class_add_private (klass, sizeof (AnimationRendererPrivate));
}
static void
animation_renderer_init (AnimationRenderer *renderer)
{
renderer->priv = G_TYPE_INSTANCE_GET_PRIVATE (renderer,
ANIMATION_TYPE_RENDERER,
AnimationRendererPrivate);
g_mutex_init (&(renderer->priv->lock));
renderer->priv->cache_table = g_hash_table_new_full (g_str_hash, g_str_equal,
(GDestroyNotify) g_free,
(GDestroyNotify) g_weak_ref_clear);
renderer->priv->queue = g_async_queue_new ();
renderer->priv->ack_queue = g_async_queue_new ();
}
static void
animation_renderer_finalize (GObject *object)
{
AnimationRenderer *renderer = ANIMATION_RENDERER (object);
Animation *animation;
gint i;
animation = animation_playback_get_animation (renderer->priv->playback);
/* Stop the thread. */
g_async_queue_push_front (renderer->priv->queue,
GINT_TO_POINTER (- 1));
g_thread_join (renderer->priv->queue_thread);
g_thread_unref (renderer->priv->queue_thread);
/* Clean remaining data. */
if (renderer->priv->idle_id)
g_source_remove (renderer->priv->idle_id);
renderer->priv->idle_id = 0;
g_mutex_lock (&renderer->priv->lock);
for (i = 0; i < animation_get_duration (animation); i++)
{
if (renderer->priv->cache[i])
g_object_unref (renderer->priv->cache[i]);
if (renderer->priv->hashes[i])
g_free (renderer->priv->hashes[i]);
}
g_free (renderer->priv->cache);
g_free (renderer->priv->hashes);
g_async_queue_unref (renderer->priv->queue);
renderer->priv->queue = NULL;
g_async_queue_unref (renderer->priv->ack_queue);
renderer->priv->ack_queue = NULL;
g_hash_table_destroy (renderer->priv->cache_table);
g_mutex_unlock (&renderer->priv->lock);
g_mutex_clear (&renderer->priv->lock);
G_OBJECT_CLASS (parent_class)->finalize (object);
}
static void
animation_renderer_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
AnimationRenderer *renderer = ANIMATION_RENDERER (object);
switch (property_id)
{
case PROP_PLAYBACK:
renderer->priv->playback = g_value_get_object (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
animation_renderer_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
AnimationRenderer *renderer = ANIMATION_RENDERER (object);
switch (property_id)
{
case PROP_PLAYBACK:
g_value_set_object (value, renderer->priv->playback);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static gpointer
animation_renderer_process_queue (AnimationRenderer *renderer)
{
while (TRUE)
{
Animation *animation;
GeglBuffer *buffer = NULL;
GWeakRef *ref = NULL;
gchar *hash = NULL;
gchar *hash_cp = NULL;
gint frame;
frame = GPOINTER_TO_INT (g_async_queue_pop (renderer->priv->queue)) - 1;
/* End flag. */
if (frame < 0)
g_thread_exit (NULL);
/* It is possible to have position bigger than the animation duration if the
* request was sent before a duration change. When this happens, just ignore
* the request silently and go to the next one. */
g_mutex_lock (&renderer->priv->lock);
if (frame >= renderer->priv->cache_size)
{
g_mutex_unlock (&renderer->priv->lock);
continue;
}
g_mutex_unlock (&renderer->priv->lock);
animation = animation_playback_get_animation (renderer->priv->playback);
hash = ANIMATION_GET_CLASS (animation)->get_frame_hash (animation,
frame);
if (hash)
{
ref = g_hash_table_lookup (renderer->priv->cache_table, hash);
hash_cp = g_strdup (hash);
if (ref)
{
/* Acquire and add a new reference to the buffer. */
buffer = g_weak_ref_get (ref);
}
else
{
ref = g_new (GWeakRef, 1);
g_weak_ref_init (ref, NULL);
g_mutex_lock (&renderer->priv->lock);
g_hash_table_insert (renderer->priv->cache_table,
hash, ref);
g_mutex_unlock (&renderer->priv->lock);
}
if (! buffer)
{
gdouble proxy_ratio;
proxy_ratio = animation_playback_get_proxy (renderer->priv->playback);
buffer = ANIMATION_GET_CLASS (animation)->create_frame (animation,
G_OBJECT (renderer),
frame,
proxy_ratio);
g_weak_ref_set (ref, buffer);
}
}
g_mutex_lock (&renderer->priv->lock);
if (renderer->priv->cache[frame])
g_object_unref (renderer->priv->cache[frame]);
if (renderer->priv->hashes[frame])
g_free (renderer->priv->hashes[frame]);
renderer->priv->cache[frame] = buffer;
renderer->priv->hashes[frame] = hash_cp;
g_mutex_unlock (&renderer->priv->lock);
/* Tell the main thread which buffers were updated, so that the
* GUI reflects the change. */
g_async_queue_remove (renderer->priv->ack_queue,
GINT_TO_POINTER (frame + 1));
g_async_queue_push (renderer->priv->ack_queue,
GINT_TO_POINTER (frame + 1));
/* Relinquish CPU regularly. */
g_thread_yield ();
}
return NULL;
}
static gboolean
animation_renderer_idle_update (AnimationRenderer *renderer)
{
gpointer p;
gboolean retval = G_SOURCE_CONTINUE;;
while ((p = g_async_queue_try_pop (renderer->priv->ack_queue)))
{
gint frame = GPOINTER_TO_INT (p) - 1;
g_signal_emit (renderer, signals[CACHE_UPDATED], 0, frame);
}
/* Make sure the UI gets updated regularly. */
while (g_main_context_pending (NULL))
g_main_context_iteration (NULL, FALSE);
/* If nothing is being rendered (negative queue length, meaning the
* renderer is waiting), nor is there anything in the ACK queue, just
* stop the idle source. */
if (renderer->priv && renderer->priv->queue &&
g_async_queue_length (renderer->priv->queue) < 0 &&
g_async_queue_length (renderer->priv->ack_queue) == 0)
{
retval = G_SOURCE_REMOVE;
renderer->priv->idle_id = 0;
g_signal_emit (renderer, signals[RENDERING], 0, FALSE);
}
else if (renderer->priv && renderer->priv->queue)
{
g_signal_emit (renderer, signals[RENDERING], 0, TRUE);
}
return retval;
}
static void
on_proxy_changed (AnimationPlayback *playback,
gdouble ratio,
AnimationRenderer *renderer)
{
Animation *animation = animation_playback_get_animation (playback);
gint current_pos;
gint i;
if (renderer->priv->idle_id)
{
g_source_remove (renderer->priv->idle_id);
renderer->priv->idle_id = 0;
}
/* Stop any rendering. */
for (i = 0; i < animation_get_duration (animation); i++)
{
g_async_queue_remove (renderer->priv->queue, GINT_TO_POINTER (i + 1));
}
/* Delete the cache. */
g_mutex_lock (&renderer->priv->lock);
for (i = 0; i < animation_get_duration (animation); i++)
{
if (renderer->priv->cache[i])
{
g_object_unref (renderer->priv->cache[i]);
renderer->priv->cache[i] = NULL;
}
if (renderer->priv->hashes[i])
{
g_free (renderer->priv->hashes[i]);
renderer->priv->hashes[i] = NULL;
}
}
g_mutex_unlock (&renderer->priv->lock);
/* Queue the whole animation to be updated. */
current_pos = animation_playback_get_position (renderer->priv->playback);
g_async_queue_sort (renderer->priv->queue,
(GCompareDataFunc) compare_int_from,
GINT_TO_POINTER (current_pos + 1));
for (i = 0; i < animation_get_duration (animation); i++)
{
g_async_queue_push_sorted (renderer->priv->queue,
GINT_TO_POINTER (i + 1),
(GCompareDataFunc) compare_int_from,
GINT_TO_POINTER (current_pos + 1));
}
g_signal_emit (renderer, signals[RENDERING], 0, TRUE);
renderer->priv->idle_id = g_idle_add_full (G_PRIORITY_HIGH_IDLE,
(GSourceFunc) animation_renderer_idle_update,
renderer, NULL);
}
static void
on_playback_position (AnimationPlayback *animation,
gint frame_number,
GeglBuffer *buffer,
gboolean must_draw_null,
AnimationRenderer *renderer)
{
gint current_pos;
current_pos = animation_playback_get_position (renderer->priv->playback);
g_async_queue_sort (renderer->priv->queue,
(GCompareDataFunc) compare_int_from,
GINT_TO_POINTER (current_pos + 1));
}
static void
on_size_changed (Animation *animation,
gint width,
gint height,
AnimationRenderer *renderer)
{
on_frames_changed (animation, 0, animation_get_duration (animation),
renderer);
}
static void
on_frames_changed (Animation *animation,
gint position,
gint length,
AnimationRenderer *renderer)
{
gint i;
gint current_pos;
current_pos = animation_playback_get_position (renderer->priv->playback);
g_async_queue_sort (renderer->priv->queue,
(GCompareDataFunc) compare_int_from,
GINT_TO_POINTER (current_pos + 1));
for (i = position; i < position + length; i++)
{
/* Remove if already present: don't process twice the same frames.
* XXX GAsyncQueue does not allow NULL data for no good reason (it relies
* on GQueue which does allow NULL data. As a trick, I just add 1 so that
* we can process the frame 0. */
g_async_queue_remove (renderer->priv->queue, GINT_TO_POINTER (i + 1));
g_async_queue_push_sorted (renderer->priv->queue,
GINT_TO_POINTER (i + 1),
(GCompareDataFunc) compare_int_from,
GINT_TO_POINTER (current_pos + 1));
if (renderer->priv->idle_id == 0)
renderer->priv->idle_id = g_idle_add_full (G_PRIORITY_HIGH_IDLE,
(GSourceFunc) animation_renderer_idle_update,
renderer, NULL);
}
g_signal_emit (renderer, signals[RENDERING], 0, TRUE);
}
static void
on_invalidate_cache (Animation *animation,
gint position,
gint length,
AnimationRenderer *renderer)
{
GList *update = NULL;
GList *iter;
gint current_pos;
gint i;
if (renderer->priv->idle_id)
{
g_source_remove (renderer->priv->idle_id);
renderer->priv->idle_id = 0;
}
/* Stop any rendering temporarily. */
for (i = 0; i < animation_get_duration (animation); i++)
{
if (g_async_queue_remove (renderer->priv->queue, GINT_TO_POINTER (i + 1)))
update = g_list_insert_sorted_with_data (update, GINT_TO_POINTER (i + 1),
(GCompareDataFunc) compare_int_from,
/* TODO: right now I am sorting the render
* queue in common order. I will have to test
* sorting it from the current position.
*/
0);
}
/* Delete the cache. */
g_mutex_lock (&renderer->priv->lock);
for (i = position; i < position + length; i++)
{
gint j;
if (renderer->priv->cache[i])
{
/* Delete this frame and all others which share the same cache. */
GeglBuffer *tmp = g_object_ref (renderer->priv->cache[i]);
for (j = 0; j < animation_get_duration (animation); j++)
{
if (renderer->priv->cache[j] == tmp)
{
g_object_unref (renderer->priv->cache[j]);
renderer->priv->cache[j] = NULL;
g_free (renderer->priv->hashes[j]);
renderer->priv->hashes[j] = NULL;
if (g_list_index (update, GINT_TO_POINTER (j + 1)) == -1)
update = g_list_insert_sorted_with_data (update, GINT_TO_POINTER (j + 1),
(GCompareDataFunc) compare_int_from,
/* TODO: right now I am sorting the render
* queue in common order. I will have to test
* sorting it from the current position.
*/
0);
}
}
g_object_unref (tmp);
}
if (g_list_index (update, GINT_TO_POINTER (i + 1)) == -1)
update = g_list_insert_sorted_with_data (update, GINT_TO_POINTER (i + 1),
(GCompareDataFunc) compare_int_from,
/* TODO: right now I am sorting the render
* queue in common order. I will have to test
* sorting it from the current position.
*/
0);
}
g_mutex_unlock (&renderer->priv->lock);
/* Queue the invalidated part of the animation. */
current_pos = animation_playback_get_position (renderer->priv->playback);
g_async_queue_sort (renderer->priv->queue,
(GCompareDataFunc) compare_int_from,
GINT_TO_POINTER (current_pos + 1));
for (iter = update; iter; iter = iter->next)
{
g_async_queue_push_sorted (renderer->priv->queue,
iter->data,
(GCompareDataFunc) compare_int_from,
GINT_TO_POINTER (current_pos + 1));
}
g_list_free (update);
g_signal_emit (renderer, signals[RENDERING], 0, TRUE);
renderer->priv->idle_id = g_idle_add_full (G_PRIORITY_HIGH_IDLE,
(GSourceFunc) animation_renderer_idle_update,
renderer, NULL);
}
static void
on_duration_changed (Animation *animation,
gint duration,
AnimationRenderer *renderer)
{
gint i;
g_mutex_lock (&renderer->priv->lock);
if (duration < renderer->priv->cache_size)
{
for (i = duration; i < renderer->priv->cache_size; i++)
{
if (renderer->priv->cache[i])
g_object_unref (renderer->priv->cache[i]);
if (renderer->priv->hashes[i])
g_free (renderer->priv->hashes[i]);
}
renderer->priv->cache = g_renew (GeglBuffer*,
renderer->priv->cache,
duration);
renderer->priv->hashes = g_renew (gchar*,
renderer->priv->hashes,
duration);
}
else if (duration > renderer->priv->cache_size)
{
renderer->priv->cache = g_renew (GeglBuffer*,
renderer->priv->cache,
duration);
renderer->priv->hashes = g_renew (gchar*,
renderer->priv->hashes,
duration);
for (i = renderer->priv->cache_size; i < duration; i++)
{
renderer->priv->cache[i] = NULL;
renderer->priv->hashes[i] = NULL;
}
}
renderer->priv->cache_size = duration;
g_mutex_unlock (&renderer->priv->lock);
}
static void
on_animation_loaded (Animation *animation,
AnimationRenderer *renderer)
{
g_signal_handlers_disconnect_by_func (animation,
G_CALLBACK (on_animation_loaded),
renderer);
renderer->priv->queue_thread = g_thread_new ("gimp-animation-process-queue",
(GThreadFunc) animation_renderer_process_queue,
renderer);
renderer->priv->idle_id = 0;
}
/**** Public Functions ****/
/**
* animation_renderer_new:
* @playback: the #AnimationPlayback.
*
* Returns: a new #AnimationRenderer. This renderer as well as all its methods
* should only be visible by the attached @playback.
**/
GObject *
animation_renderer_new (GObject *playback)
{
GObject *object;
AnimationRenderer *renderer;
Animation *animation;
object = g_object_new (ANIMATION_TYPE_RENDERER,
"playback", playback,
NULL);
renderer = ANIMATION_RENDERER (object);
animation = animation_playback_get_animation (renderer->priv->playback);
renderer->priv->cache_size = animation_get_duration (animation);
renderer->priv->cache = g_new0 (GeglBuffer*,
renderer->priv->cache_size);
renderer->priv->hashes = g_new0 (gchar*,
renderer->priv->cache_size);
g_signal_connect (animation, "size-changed",
G_CALLBACK (on_size_changed), renderer);
g_signal_connect (animation, "frames-changed",
G_CALLBACK (on_frames_changed), renderer);
g_signal_connect (animation, "invalidate-cache",
G_CALLBACK (on_invalidate_cache), renderer);
g_signal_connect (animation, "duration-changed",
G_CALLBACK (on_duration_changed), renderer);
g_signal_connect (animation, "loaded",
G_CALLBACK (on_animation_loaded), renderer);
g_signal_connect (renderer->priv->playback, "proxy-changed",
G_CALLBACK (on_proxy_changed), renderer);
g_signal_connect (renderer->priv->playback, "position",
G_CALLBACK (on_playback_position), renderer);
return object;
}
/**
* animation_renderer_get_buffer:
* @renderer: the #AnimationRenderer.
* @position:
*
* Returns: the #GeglBuffer cached for the frame at @position, with an
* additional reference, so that it will stay valid even if the frame is
* updated in-between. Therefore call g_object_unref() after usage.
* As all other renderer function, it should only be visible by the playback,
* or by frame-rendering code in #Animation subclasses themselves, since the
* renderer passes itself as argument when it requests a new frame buffer.
* Other pieces of code, in particular the GUI, should only call
* animation_playback_get_buffer().
**/
GeglBuffer *
animation_renderer_get_buffer (AnimationRenderer *renderer,
gint position)
{
GeglBuffer *frame;
g_mutex_lock (&renderer->priv->lock);
frame = renderer->priv->cache[position];
if (frame)
frame = g_object_ref (frame);
g_mutex_unlock (&renderer->priv->lock);
return frame;
}
gboolean
animation_renderer_identical (AnimationRenderer *renderer,
gint position1,
gint position2)
{
return (g_strcmp0 (renderer->priv->hashes[position1],
renderer->priv->hashes[position2]) == 0);
}

View File

@@ -0,0 +1,63 @@
/* GIMP - The GNU Image Manipulation Program
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
*
* animation-renderer.h
* Copyright (C) 2017 Jehan <jehan@gimp.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __ANIMATION_RENDERER_H__
#define __ANIMATION_RENDERER_H__
#define ANIMATION_TYPE_RENDERER (animation_renderer_get_type ())
#define ANIMATION_RENDERER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), ANIMATION_TYPE_RENDERER, AnimationRenderer))
#define ANIMATION_RENDERER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), ANIMATION_TYPE_RENDERER, AnimationRendererClass))
#define ANIMATION_IS_RENDERER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), ANIMATION_TYPE_RENDERER))
#define ANIMATION_IS_RENDERER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), ANIMATION_TYPE_RENDERER))
#define ANIMATION_RENDERER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), ANIMATION_TYPE_RENDERER, AnimationRendererClass))
typedef struct _AnimationRenderer AnimationRenderer;
typedef struct _AnimationRendererClass AnimationRendererClass;
typedef struct _AnimationRendererPrivate AnimationRendererPrivate;
struct _AnimationRenderer
{
GObject parent_instance;
AnimationRendererPrivate *priv;
};
struct _AnimationRendererClass
{
GObjectClass parent_class;
void (*cache_updated) (AnimationRenderer *renderer,
gint position);
void (*rendering) (AnimationRenderer *renderer,
gboolean has_render_queue);
};
GType animation_renderer_get_type (void);
GObject * animation_renderer_new (GObject *playback);
GeglBuffer * animation_renderer_get_buffer (AnimationRenderer *renderer,
gint position);
gboolean animation_renderer_identical (AnimationRenderer *renderer,
gint position1,
gint position2);
#endif /* __ANIMATION_RENDERER_H__ */

View File

@@ -0,0 +1,585 @@
/* GIMP - The GNU Image Manipulation Program
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
*
* animation.c
* Copyright (C) 2015-2016 Jehan <jehan@gimp.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <string.h>
#include <libgimp/gimp.h>
#include <libgimp/stdplugins-intl.h>
#include "animation-utils.h"
#include "animation.h"
#include "animation-animatic.h"
#include "animation-celanimation.h"
#include "animation-playback.h"
/* Settings we cache assuming they may be the user's
* favorite, like a framerate.
* These will be used only for image without stored animation. */
typedef struct
{
gdouble framerate;
}
CachedSettings;
enum
{
LOADING,
LOADED,
SIZE_CHANGED,
DURATION_CHANGED,
FRAMERATE_CHANGED,
FRAMES_CHANGED,
INVALIDATE_CACHE,
LAST_SIGNAL
};
enum
{
PROP_0,
PROP_IMAGE,
PROP_XML
};
typedef struct _AnimationPrivate AnimationPrivate;
struct _AnimationPrivate
{
gint32 image_id;
/* Animation size may be different from image size. */
gint width;
gint height;
gdouble framerate;
gboolean loaded;
};
#define ANIMATION_GET_PRIVATE(animation) \
G_TYPE_INSTANCE_GET_PRIVATE (animation, \
ANIMATION_TYPE_ANIMATION, \
AnimationPrivate)
static void animation_finalize (GObject *object);
static void animation_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec);
static void animation_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec);
G_DEFINE_TYPE (Animation, animation, G_TYPE_OBJECT)
#define parent_class animation_parent_class
static guint animation_signals[LAST_SIGNAL] = { 0 };
static void
animation_class_init (AnimationClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
/**
* Animation::loading:
* @animation: the animation loading.
* @ratio: fraction loaded [0-1].
* @label: the text to show as progression message.
*
* The ::loading signal must be emitted when a long process is taking
* place and you want the GUI to display a progress bar.
* GUI widgets depending on a consistent state of @animation should
* become unresponsive.
*
* Returns: TRUE is the loading should be stopped, FALSE otherwise.
* This allows long loading to be stopped.
*/
animation_signals[LOADING] =
g_signal_new ("loading",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_ACTION,
G_STRUCT_OFFSET (AnimationClass, loading),
NULL, NULL,
NULL,
G_TYPE_BOOLEAN,
2,
G_TYPE_DOUBLE,
G_TYPE_STRING);
/**
* Animation::loaded:
* @animation: the animation loading.
* @duration: number of frames.
* @width: display width in pixels.
* @height: display height in pixels.
*
* The ::loaded signal is emitted when @animation is fully loaded.
* GUI widgets depending on a consistent state of @animation can
* now become responsive to user interaction.
*/
animation_signals[LOADED] =
g_signal_new ("loaded",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (AnimationClass, loaded),
NULL, NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE,
0);
/**
* Animation::size-changed:
* @animation: the animation.
* @width: @animation width.
* @height: @animation height.
*
* The ::size-changed signal will be emitted when @animation display
* size changes.
*/
animation_signals[SIZE_CHANGED] =
g_signal_new ("size-changed",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (AnimationClass, size_changed),
NULL, NULL,
NULL,
G_TYPE_NONE,
2,
G_TYPE_INT,
G_TYPE_INT);
/**
* Animation::duration:
* @animation: the animation.
* @duration: the new duration of @animation in number of frames.
*
* The ::duration signal must be emitted when the duration of
* @animation changes.
*/
animation_signals[DURATION_CHANGED] =
g_signal_new ("duration-changed",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (AnimationClass, duration_changed),
NULL, NULL,
g_cclosure_marshal_VOID__INT,
G_TYPE_NONE,
1,
G_TYPE_INT);
/**
* Animation::framerate-changed:
* @animation: the animation.
* @framerate: the new framerate of @animation in frames per second.
*
* The ::framerate-changed signal is emitted when the framerate of
* @animation changes.
*/
animation_signals[FRAMERATE_CHANGED] =
g_signal_new ("framerate-changed",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (AnimationClass, framerate_changed),
NULL, NULL,
g_cclosure_marshal_VOID__DOUBLE,
G_TYPE_NONE,
1,
G_TYPE_DOUBLE);
/**
* Animation::frames-changed:
* @animation: the animation.
* @position: the first frame position whose contents changed.
* @length: the number of changed frames from @position.
*
* The ::frames-changed signal must be emitted when the contents
* of one or more successive frames change.
*/
animation_signals[FRAMES_CHANGED] =
g_signal_new ("frames-changed",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (AnimationClass, frames_changed),
NULL, NULL,
NULL,
G_TYPE_NONE,
2,
G_TYPE_INT,
G_TYPE_INT);
/**
* Animation::invalidate-cache:
* @animation: the animation.
* @position: the first frame position whose contents changed.
* @length: the number of changed frames from @position.
*
* The ::invalidate-cache signal is emitted when one or more
* successive frames have to be updated. This is similar to
* ::frames-changed except that it forces the cache update even if the
* contents apparently did not change. It will also invalidate cache
* for similar frames, even when not in the given range.
*/
animation_signals[INVALIDATE_CACHE] =
g_signal_new ("invalidate-cache",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (AnimationClass, invalidate_cache),
NULL, NULL,
NULL,
G_TYPE_NONE,
2,
G_TYPE_INT,
G_TYPE_INT);
object_class->finalize = animation_finalize;
object_class->set_property = animation_set_property;
object_class->get_property = animation_get_property;
/**
* Animation:image:
*
* The associated image id.
*/
g_object_class_install_property (object_class, PROP_IMAGE,
g_param_spec_int ("image", NULL, NULL,
0, G_MAXINT, 0,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY));
/**
* Animation:xml:
*
* The animation serialized as a XML string.
*/
g_object_class_install_property (object_class, PROP_XML,
g_param_spec_string ("xml", NULL,
NULL, NULL,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY));
g_type_class_add_private (klass, sizeof (AnimationPrivate));
}
static void
animation_init (Animation *animation)
{
AnimationPrivate *priv = ANIMATION_GET_PRIVATE (animation);
CachedSettings settings;
/* Acceptable default settings. */
settings.framerate = 24.0;
/* If we saved any settings globally, use the one from the last run. */
gimp_get_data (PLUG_IN_PROC, &settings);
/* Acceptable settings for the default. */
priv->framerate = settings.framerate;
}
/************ Public Functions ****************/
Animation *
animation_new (gint32 image_id,
gboolean animatic,
const gchar *xml)
{
Animation *animation;
animation = g_object_new (animatic? ANIMATION_TYPE_ANIMATIC :
ANIMATION_TYPE_CEL_ANIMATION,
"image", image_id,
"xml", xml,
NULL);
g_signal_emit (animation, animation_signals[FRAMES_CHANGED], 0,
0, animation_get_duration (animation));
return animation;
}
gint32
animation_get_image_id (Animation *animation)
{
AnimationPrivate *priv = ANIMATION_GET_PRIVATE (animation);
return priv->image_id;
}
/**
* animation_load:
* @animation: the #Animation.
*
* Cache the whole animation. This is to be used at the start, or each
* time you want to reload the image contents.
**/
void
animation_load (Animation *animation)
{
AnimationPrivate *priv = ANIMATION_GET_PRIVATE (animation);
priv->loaded = FALSE;
if (ANIMATION_GET_CLASS (animation)->load)
ANIMATION_GET_CLASS (animation)->load (animation);
g_signal_emit (animation, animation_signals[INVALIDATE_CACHE], 0,
0, animation_get_duration (animation));
priv->loaded = TRUE;
g_signal_emit (animation, animation_signals[LOADED], 0);
}
void
animation_save_to_parasite (Animation *animation,
const gchar *playback_xml)
{
AnimationPrivate *priv = ANIMATION_GET_PRIVATE (animation);
GimpParasite *old_parasite;
const gchar *parasite_name;
const gchar *selected;
gchar *xml;
gboolean undo_step_started = FALSE;
CachedSettings settings;
/* First saving in cache as default in the same session. */
settings.framerate = animation_get_framerate (animation);
gimp_set_data (PLUG_IN_PROC, &settings, sizeof (&settings));
/* Then as a parasite for the specific image. */
xml = ANIMATION_GET_CLASS (animation)->serialize (animation,
playback_xml);
if (ANIMATION_IS_ANIMATIC (animation))
{
selected = "animatic";
parasite_name = PLUG_IN_PROC "/animatic";
}
else /* ANIMATION_IS_CEL_ANIMATION */
{
selected = "cel-animation";
parasite_name = PLUG_IN_PROC "/cel-animation";
}
/* If there was already parasites and they were all the same as the
* current state, do not resave them.
* This prevents setting the image in a dirty state while it stayed
* the same. */
old_parasite = gimp_image_get_parasite (priv->image_id, parasite_name);
if (xml && (! old_parasite ||
g_strcmp0 ((gchar *) gimp_parasite_data (old_parasite), xml)))
{
GimpParasite *parasite;
if (! undo_step_started)
{
gimp_image_undo_group_start (priv->image_id);
undo_step_started = TRUE;
}
parasite = gimp_parasite_new (parasite_name,
GIMP_PARASITE_PERSISTENT | GIMP_PARASITE_UNDOABLE,
strlen (xml) + 1, xml);
gimp_image_attach_parasite (priv->image_id, parasite);
gimp_parasite_free (parasite);
}
gimp_parasite_free (old_parasite);
if (xml)
g_free (xml);
old_parasite = gimp_image_get_parasite (priv->image_id,
PLUG_IN_PROC "/selected");
if (! old_parasite ||
g_strcmp0 ((gchar *) gimp_parasite_data (old_parasite), selected))
{
GimpParasite *parasite;
if (! undo_step_started)
{
gimp_image_undo_group_start (priv->image_id);
undo_step_started = TRUE;
}
parasite = gimp_parasite_new (PLUG_IN_PROC "/selected",
GIMP_PARASITE_PERSISTENT | GIMP_PARASITE_UNDOABLE,
strlen (selected) + 1, selected);
gimp_image_attach_parasite (priv->image_id, parasite);
gimp_parasite_free (parasite);
}
gimp_parasite_free (old_parasite);
if (undo_step_started)
{
gimp_image_undo_group_end (priv->image_id);
}
}
gint
animation_get_duration (Animation *animation)
{
return ANIMATION_GET_CLASS (animation)->get_duration (animation);
}
void
animation_set_size (Animation *animation,
gint width,
gint height)
{
AnimationPrivate *priv = ANIMATION_GET_PRIVATE (animation);
if (width != priv->width || height != priv->height)
{
priv->width = width;
priv->height = height;
g_signal_emit (animation, animation_signals[SIZE_CHANGED], 0,
width, height);
}
}
void
animation_get_size (Animation *animation,
gint *width,
gint *height)
{
AnimationPrivate *priv = ANIMATION_GET_PRIVATE (animation);
if (width)
*width = priv->width;
if (height)
*height = priv->height;
}
void
animation_set_framerate (Animation *animation,
gdouble fps)
{
AnimationPrivate *priv = ANIMATION_GET_PRIVATE (animation);
g_return_if_fail (fps > 0.0);
priv->framerate = fps;
g_signal_emit (animation, animation_signals[FRAMERATE_CHANGED], 0,
fps);
}
gdouble
animation_get_framerate (Animation *animation)
{
AnimationPrivate *priv = ANIMATION_GET_PRIVATE (animation);
return priv->framerate;
}
gboolean
animation_loaded (Animation *animation)
{
AnimationPrivate *priv = ANIMATION_GET_PRIVATE (animation);
return priv->loaded;
}
void
animation_update_paint_view (Animation *animation,
gint position)
{
ANIMATION_GET_CLASS (animation)->update_paint_view (animation, position);
gimp_displays_flush ();
}
/************ Private Functions ****************/
static void
animation_finalize (GObject *object)
{
G_OBJECT_CLASS (parent_class)->finalize (object);
}
static void
animation_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
Animation *animation = ANIMATION (object);
AnimationPrivate *priv = ANIMATION_GET_PRIVATE (animation);
switch (property_id)
{
case PROP_IMAGE:
priv->image_id = g_value_get_int (value);
break;
case PROP_XML:
{
const gchar *xml = g_value_get_string (value);
GError *error = NULL;
gint width;
gint height;
if (! xml ||
! ANIMATION_GET_CLASS (animation)->deserialize (animation,
xml,
&error))
{
if (error)
g_warning ("Error parsing XML: %s", error->message);
/* First time or XML parsing failed: reset to defaults. */
ANIMATION_GET_CLASS (animation)->reset_defaults (animation);
}
g_clear_error (&error);
animation_get_size (animation, &width, &height);
if (width <= 0 || height <= 0)
{
/* Default display size is the size of the image. */
animation_set_size (animation,
gimp_image_width (priv->image_id),
gimp_image_height (priv->image_id));
}
}
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
animation_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
Animation *animation = ANIMATION (object);
AnimationPrivate *priv = ANIMATION_GET_PRIVATE (animation);
switch (property_id)
{
case PROP_IMAGE:
g_value_set_int (value, priv->image_id);
break;
case PROP_XML:
{
gchar *xml;
xml = ANIMATION_GET_CLASS (animation)->serialize (animation,
NULL);
g_value_take_string (value, xml);
g_free (xml);
}
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}

View File

@@ -0,0 +1,120 @@
/* GIMP - The GNU Image Manipulation Program
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
*
* animation.h
* Copyright (C) 2015-2016 Jehan <jehan@gimp.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __ANIMATION_H__
#define __ANIMATION_H__
#define ANIMATION_TYPE_ANIMATION (animation_get_type ())
#define ANIMATION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), ANIMATION_TYPE_ANIMATION, Animation))
#define ANIMATION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), ANIMATION_TYPE_ANIMATION, AnimationClass))
#define ANIMATION_IS_ANIMATION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), ANIMATION_TYPE_ANIMATION))
#define ANIMATION_IS_ANIMATION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), ANIMATION_TYPE_ANIMATION))
#define ANIMATION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), ANIMATION_TYPE_ANIMATION, AnimationClass))
typedef struct _Animation Animation;
typedef struct _AnimationClass AnimationClass;
struct _Animation
{
GObject parent_instance;
};
struct _AnimationClass
{
GObjectClass parent_class;
/* Signals */
void (*loading) (Animation *animation,
gdouble ratio,
const gchar *label);
void (*loaded) (Animation *animation);
void (*size_changed) (Animation *animation,
gint width,
gint height);
void (*duration_changed) (Animation *animation,
gint duration);
void (*framerate_changed) (Animation *animation,
gdouble fps);
void (*frames_changed) (Animation *animation,
gint position,
gint length);
void (*invalidate_cache) (Animation *animation,
gint position,
gint length);
/* These virtual methods must be implemented by any subclass. */
gint (*get_duration) (Animation *animation);
void (*reset_defaults) (Animation *animation);
gchar * (*serialize) (Animation *animation,
const gchar *playback_xml);
gboolean (*deserialize) (Animation *animation,
const gchar *xml,
GError **error);
void (*update_paint_view) (Animation *animation,
gint position);
/* Optional: to be implemented if there is anything to do else than
* refreshing the cache upon loading. */
void (*load) (Animation *animation);
/* Used by the renderer only. Must be implemented too. */
gchar * (*get_frame_hash) (Animation *animation,
gint position);
GeglBuffer * (*create_frame) (Animation *animation,
GObject *renderer,
gint position,
gdouble proxy_ratio);
};
GType animation_get_type (void);
Animation * animation_new (gint32 image_id,
gboolean animatic,
const gchar *xml);
gint32 animation_get_image_id (Animation *animation);
void animation_load (Animation *animation);
void animation_save_to_parasite (Animation *animation,
const gchar *playback_xml);
gint animation_get_duration (Animation *animation);
void animation_set_size (Animation *animation,
gint width,
gint height);
void animation_get_size (Animation *animation,
gint *width,
gint *height);
void animation_set_framerate (Animation *animation,
gdouble fps);
gdouble animation_get_framerate (Animation *animation);
gboolean animation_loaded (Animation *animation);
void animation_update_paint_view (Animation *animation,
gint position);
#endif /* __ANIMATION_H__ */

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

View File

@@ -0,0 +1,237 @@
/* GIMP - The GNU Image Manipulation Program
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
*
* animation-dialog-export.c
* Copyright (C) 2017 Jehan <jehan@gimp.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <string.h>
#include <gtk/gtk.h>
#include <libgimp/gimp.h>
#undef GDK_DISABLE_DEPRECATED
#include <libgimp/gimpui.h>
#include "libgimp/stdplugins-intl.h"
#include "core/animation.h"
#include "core/animation-playback.h"
#include "animation-dialog.h"
#include "animation-dialog-export.h"
static void animation_dialog_export_video (AnimationPlayback *playback,
gchar *filename);
static void animation_dialog_export_images (AnimationPlayback *playback,
gchar *filename);
void
animation_dialog_export (GtkWindow *main_dialog,
AnimationPlayback *playback)
{
GtkWidget *dialog;
GtkFileFilter *all;
GtkFileFilter *videos;
GtkFileFilter *images;
gchar *filename = NULL;
dialog = gtk_file_chooser_dialog_new (_("Export animation"),
main_dialog,
GTK_FILE_CHOOSER_ACTION_SAVE,
_("_Cancel"), GTK_RESPONSE_CANCEL,
_("_Export"), GTK_RESPONSE_ACCEPT,
NULL);
gtk_file_chooser_set_do_overwrite_confirmation (GTK_FILE_CHOOSER (dialog), TRUE);
gtk_file_chooser_set_create_folders (GTK_FILE_CHOOSER (dialog), TRUE);
/*gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog),
default_folder_for_saving);*/
/*gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (dialog),
"Untitled document");*/
/* Add filters. */
all = gtk_file_filter_new ();
gtk_file_filter_set_name (all, _("All files"));
gtk_file_filter_add_pattern (all, "*");
gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), all);
videos = gtk_file_filter_new ();
gtk_file_filter_set_name (videos, _("Video Files"));
gtk_file_filter_add_pattern (videos, "*.[oO][gG][vV]");
gtk_file_filter_add_mime_type (videos, "video/x-theora+ogg");
gtk_file_filter_add_mime_type (videos, "video/ogg");
gtk_file_filter_add_pattern (videos, "*.[aA][vV][iI]");
gtk_file_filter_add_pattern (videos, "*.[mM][oO][vV]");
gtk_file_filter_add_pattern (videos, "*.[mM][pP][gG]");
gtk_file_filter_add_pattern (videos, "*.[mM][pP]4");
gtk_file_filter_add_mime_type (videos, "video/x-msvideo");
gtk_file_filter_add_mime_type (videos, "video/quicktime");
gtk_file_filter_add_mime_type (videos, "video/mpeg");
gtk_file_filter_add_mime_type (videos, "video/mp4");
gtk_file_filter_add_mime_type (videos, "video/x-matroska");
gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), videos);
images = gtk_file_filter_new ();
gtk_file_filter_set_name (images, _("Image Sequences"));
gtk_file_filter_add_pattern (images, "*.[pP][nN][gG]");
gtk_file_filter_add_pattern (images, "*.[tT][iI][fF][fF]");
gtk_file_filter_add_pattern (images, "*.[tT][iI][fF]");
gtk_file_filter_add_pattern (images, "*.[jJ][pP][gG]");
gtk_file_filter_add_pattern (images, "*.[jJ][pP][eE][gG]");
gtk_file_filter_add_mime_type (images, "image/tiff");
gtk_file_filter_add_mime_type (images, "image/png");
gtk_file_filter_add_mime_type (images, "image/jpeg");
gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), images);
gtk_file_chooser_set_filter (GTK_FILE_CHOOSER (dialog), videos);
if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT)
{
filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
}
gtk_widget_destroy (dialog);
if (filename)
{
gchar *lower = g_ascii_strdown (filename, -1);
/* check the type of files. */
if (g_str_has_suffix (lower, ".png") ||
g_str_has_suffix (lower, ".jpg") ||
g_str_has_suffix (lower, ".jpeg") ||
g_str_has_suffix (lower, ".tiff") ||
g_str_has_suffix (lower, ".tif"))
{
animation_dialog_export_images (playback, filename);
}
else
{
/* We assume unrecognized type are videos. I doubt that's
* a good assumption, but enough for a first hack. */
animation_dialog_export_video (playback, filename);
}
g_free (lower);
g_free (filename);
}
}
static void
animation_dialog_export_video (AnimationPlayback *playback,
gchar *filename)
{
Animation *animation;
GeglNode *graph;
GeglNode *export;
GeglNode *input;
gchar *label = g_strdup_printf(_("Exporting \"%s\""),
filename);
gint duration;
gint i;
animation = animation_playback_get_animation (playback);
duration = animation_get_duration (animation);
graph = gegl_node_new ();
export = gegl_node_new_child (graph,
"operation", "gegl:ff-save",
"path", filename,
NULL);
input = gegl_node_new_child (graph,
"operation", "gegl:buffer-source",
NULL);
gegl_node_set (export, "frame-rate", 24.0, NULL);
gegl_node_set (export, "video-bufsize", 0, NULL);
gegl_node_set (export, "video-bit-rate", 0, NULL);
gegl_node_link_many (input, export, NULL);
for (i = 0; i < duration; i++)
{
GeglBuffer *buffer;
gboolean stops_loading;
g_signal_emit_by_name (animation, "loading",
(gdouble) i / ((gdouble) duration - 0.999),
label, &stops_loading);
if (stops_loading)
break;
buffer = animation_playback_get_buffer (playback, i);
gegl_node_set (input, "buffer", buffer, NULL);
gegl_node_process (export);
g_object_unref (buffer);
}
g_free (label);
g_object_unref (graph);
g_signal_emit_by_name (animation, "loaded");
}
static void
animation_dialog_export_images (AnimationPlayback *playback,
gchar *filename)
{
Animation *animation;
GeglNode *graph;
GeglNode *export;
GeglNode *input;
gint duration;
gchar *ext;
gint i;
ext = g_strrstr (filename, ".");
g_return_if_fail (ext);
*ext = '\0';
ext++;
animation = animation_playback_get_animation (playback);
duration = animation_get_duration (animation);
graph = gegl_node_new ();
export = gegl_node_new_child (graph,
"operation", "gegl:save",
NULL);
input = gegl_node_new_child (graph,
"operation", "gegl:buffer-source",
NULL);
gegl_node_link_many (input, export, NULL);
for (i = 0; i < duration; i++)
{
GeglBuffer *buffer;
gchar *path;
gboolean stops_loading;
/* Make sure GUI interactions are processed, so that one can
* cancel the export anytime. */
while (g_main_context_pending (NULL))
g_main_context_iteration (NULL, FALSE);
g_signal_emit_by_name (animation, "loading",
(gdouble) i / ((gdouble) duration - 0.999),
_("Exporting frames"), &stops_loading);
if (stops_loading)
break;
path = g_strdup_printf ("%s-%.*d.%s",
filename,
(gint) floor (log10(duration)) + 1,
i + 1, ext);
buffer = animation_playback_get_buffer (playback, i);
gegl_node_set (input, "buffer", buffer, NULL);
gegl_node_set (export, "path", path, NULL);
gegl_node_process (export);
g_object_unref (buffer);
g_free (path);
}
g_object_unref (graph);
g_signal_emit_by_name (animation, "loaded");
}

View File

@@ -0,0 +1,29 @@
/* GIMP - The GNU Image Manipulation Program
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
*
* animation-dialog-export.h
* Copyright (C) 2017 Jehan <jehan@gimp.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __ANIMATION_DIALOG_EXPORT_H__
#define __ANIMATION_DIALOG_EXPORT_H__
#include <gtk/gtk.h>
void animation_dialog_export (GtkWindow *main_dialog,
AnimationPlayback *playback);
#endif /* __ANIMATION_DIALOG_EXPORT_H__ */

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,48 @@
/* GIMP - The GNU Image Manipulation Program
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
*
* animation-dialog.c
* Copyright (C) 2015-2016 Jehan <jehan@gimp.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __ANIMATION_DIALOG_H__
#define __ANIMATION_DIALOG_H__
#define ANIMATION_TYPE_DIALOG (animation_dialog_get_type ())
#define ANIMATION_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), ANIMATION_TYPE_DIALOG, AnimationDialog))
#define ANIMATION_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), ANIMATION_TYPE_DIALOG, AnimationDialogClass))
#define ANIMATION_IS_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), ANIMATION_TYPE_DIALOG))
#define ANIMATION_IS_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), ANIMATION_TYPE_DIALOG))
#define ANIMATION_DIALOG_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), ANIMATION_TYPE_DIALOG, AnimationDialogClass))
typedef struct _AnimationDialog AnimationDialog;
typedef struct _AnimationDialogClass AnimationDialogClass;
struct _AnimationDialog
{
GtkWindow parent_instance;
};
struct _AnimationDialogClass
{
GtkWindowClass parent_class;
};
GType animation_dialog_get_type (void) G_GNUC_CONST;
GtkWidget * animation_dialog_new (gint32 image_id);
#endif /* __ANIMATION_DIALOG_H__ */

View File

@@ -0,0 +1,205 @@
/* GIMP - The GNU Image Manipulation Program
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
*
* Editable label with entry
* Copyright (C) 2015-2017 Jehan <jehan@gimp.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <gdk/gdkkeysyms.h>
#include "animation-editable-label.h"
#include "animation-editable-label-string.h"
struct _AnimationEditableLabelStringPrivate
{
GtkWidget *editing_widget;
};
static void animation_editable_label_constructed (GObject *object);
static void animation_editable_label_icon_clicked (GtkWidget *widget,
GtkEntryIconPosition icon_pos,
GdkEvent *event,
gpointer user_data);
static void animation_editable_label_activate (GtkWidget *widget,
gpointer user_data);
static gboolean animation_editable_label_focus_out (GtkWidget *widget,
GdkEvent *event,
gpointer user_data);
static gboolean animation_editable_label_key_press (GtkWidget *widget,
GdkEvent *event,
gpointer user_data);
static void animation_editable_label_prepare_editing (GtkWidget *widget,
gpointer user_data);
static void animation_editable_label_editing (GtkWidget *widget,
gpointer user_data);
G_DEFINE_TYPE (AnimationEditableLabelString, animation_editable_label_string, ANIMATION_TYPE_EDITABLE_LABEL)
#define parent_class animation_editable_label_string_parent_class
static void
animation_editable_label_string_class_init (AnimationEditableLabelStringClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->constructed = animation_editable_label_constructed;
g_type_class_add_private (klass, sizeof (AnimationEditableLabelStringPrivate));
}
static void
animation_editable_label_string_init (AnimationEditableLabelString *label)
{
label->priv = G_TYPE_INSTANCE_GET_PRIVATE (label,
ANIMATION_TYPE_EDITABLE_LABEL_STRING,
AnimationEditableLabelStringPrivate);
}
static void
animation_editable_label_constructed (GObject *object)
{
AnimationEditableLabel *label = ANIMATION_EDITABLE_LABEL (object);
G_OBJECT_CLASS (parent_class)->constructed (object);
/* The editing widget. */
gtk_widget_set_can_focus (label->editing_widget, TRUE);
gtk_entry_set_icon_from_icon_name (GTK_ENTRY (label->editing_widget),
GTK_ENTRY_ICON_SECONDARY,
"gtk-apply");
if (animation_editable_label_get_text (label))
gtk_entry_set_text (GTK_ENTRY (label->editing_widget),
animation_editable_label_get_text (label));
g_signal_connect (label->editing_widget, "icon-release",
G_CALLBACK (animation_editable_label_icon_clicked),
label);
g_signal_connect (label->editing_widget, "activate",
G_CALLBACK (animation_editable_label_activate),
label);
g_signal_connect (label->editing_widget, "focus-out-event",
G_CALLBACK (animation_editable_label_focus_out),
label);
g_signal_connect (label->editing_widget, "key-press-event",
G_CALLBACK (animation_editable_label_key_press),
label);
gtk_widget_show (label->editing_widget);
g_signal_connect (label, "prepare-editing",
G_CALLBACK (animation_editable_label_prepare_editing),
NULL);
g_signal_connect (label, "editing",
G_CALLBACK (animation_editable_label_editing),
NULL);
}
GtkWidget *
animation_editable_label_string_new (const gchar *default_text)
{
GtkWidget *widget;
GtkWidget *entry;
entry = gtk_entry_new ();
widget = g_object_new (ANIMATION_TYPE_EDITABLE_LABEL_STRING,
"text", default_text,
"editing-widget", entry,
NULL);
return widget;
}
static void
animation_editable_label_icon_clicked (GtkWidget *widget,
GtkEntryIconPosition icon_pos,
GdkEvent *event,
gpointer user_data)
{
animation_editable_label_activate (widget, user_data);
}
static void
animation_editable_label_activate (GtkWidget *widget,
gpointer user_data)
{
AnimationEditableLabel *label = ANIMATION_EDITABLE_LABEL (user_data);
GtkEntry *entry = GTK_ENTRY (widget);
g_object_set (label,
"text", gtk_entry_get_text (entry),
NULL);
g_signal_handlers_block_by_func (label->editing_widget,
G_CALLBACK (animation_editable_label_focus_out),
label);
animation_editable_label_set_editing (label, FALSE);
g_signal_handlers_unblock_by_func (label->editing_widget,
G_CALLBACK (animation_editable_label_focus_out),
label);
}
static gboolean
animation_editable_label_focus_out (GtkWidget *widget,
GdkEvent *event,
gpointer user_data)
{
animation_editable_label_activate (widget, user_data);
/* Return FALSE because GtkEntry is expecting for this signal. */
return FALSE;
}
static gboolean
animation_editable_label_key_press (GtkWidget *widget,
GdkEvent *event,
gpointer user_data)
{
AnimationEditableLabel *label = ANIMATION_EDITABLE_LABEL (user_data);
GdkEventKey *event_key = (GdkEventKey*) event;
if (event_key->keyval == GDK_KEY_Escape)
{
/* Discard entry contents and revert to read-only label. */
g_signal_handlers_block_by_func (label->editing_widget,
G_CALLBACK (animation_editable_label_focus_out),
label);
animation_editable_label_set_editing (label, FALSE);
g_signal_handlers_unblock_by_func (label->editing_widget,
G_CALLBACK (animation_editable_label_focus_out),
label);
}
return FALSE;
}
static void
animation_editable_label_prepare_editing (GtkWidget *widget,
gpointer user_data)
{
AnimationEditableLabel *label = ANIMATION_EDITABLE_LABEL (widget);
gtk_entry_set_text (GTK_ENTRY (label->editing_widget),
animation_editable_label_get_text (label));
}
static void
animation_editable_label_editing (GtkWidget *widget,
gpointer user_data)
{
AnimationEditableLabel *label = ANIMATION_EDITABLE_LABEL (widget);
gtk_widget_grab_focus (GTK_WIDGET (label->editing_widget));
gtk_editable_set_position (GTK_EDITABLE (label->editing_widget), -1);
}

View File

@@ -0,0 +1,54 @@
/* GIMP - The GNU Image Manipulation Program
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
*
* Editable label with entry
* Copyright (C) 2015-2017 Jehan <jehan@gimp.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __ANIMATION_EDITABLE_LABEL_STRING_H__
#define __ANIMATION_EDITABLE_LABEL_STRING_H__
#include <gtk/gtk.h>
#define ANIMATION_TYPE_EDITABLE_LABEL_STRING (animation_editable_label_string_get_type ())
#define ANIMATION_EDITABLE_LABEL_STRING(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), ANIMATION_TYPE_EDITABLE_LABEL_STRING, AnimationEditableLabelString))
#define ANIMATION_IS_EDITABLE_LABEL_STRING(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), ANIMATION_TYPE_EDITABLE_LABEL_STRING))
#define ANIMATION_EDITABLE_LABEL_STRING_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), ANIMATION_TYPE_EDITABLE_LABEL_STRING, AnimationEditableLabelStringClass))
#define ANIMATION_IS_EDITABLE_LABEL_STRING_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), ANIMATION_TYPE_EDITABLE_LABEL_STRING))
#define ANIMATION_EDITABLE_LABEL_STRING_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), ANIMATION_TYPE_EDITABLE_LABEL_STRING, AnimationEditableLabelStringClass))
typedef struct _AnimationEditableLabelString AnimationEditableLabelString;
typedef struct _AnimationEditableLabelStringClass AnimationEditableLabelStringClass;
typedef struct _AnimationEditableLabelStringPrivate AnimationEditableLabelStringPrivate;
struct _AnimationEditableLabelString
{
AnimationEditableLabel parent_instance;
AnimationEditableLabelStringPrivate *priv;
};
struct _AnimationEditableLabelStringClass
{
AnimationEditableLabelClass parent_class;
};
GType animation_editable_label_string_get_type (void);
GtkWidget * animation_editable_label_string_new (const gchar *default_text);
#endif /* __ANIMATION_EDITABLE_LABEL_STRING_H__ */

View File

@@ -0,0 +1,347 @@
/* GIMP - The GNU Image Manipulation Program
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
*
* Editable label base class
* Copyright (C) 2015 Jehan <jehan@gimp.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <libgimp/gimp.h>
#include <libgimp/gimpui.h>
#include "animation-editable-label.h"
enum
{
PROP_0,
PROP_TEXT,
PROP_EDITING_WIDGET
};
enum
{
ALTERNATE_ACTION,
PREPARE_EDITING,
EDITING,
LAST_SIGNAL
};
struct _AnimationEditableLabelPrivate
{
gchar *text;
GtkWidget *viewing_widget;
GtkWidget *label;
gboolean is_editing;
GtkWidget *image;
gboolean show_icon;
};
static void animation_editable_label_constructed (GObject *object);
static void animation_editable_label_finalize (GObject *object);
static void animation_editable_label_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec);
static void animation_editable_label_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec);
static gboolean animation_editable_label_alternate_click (gpointer user_data);
static gboolean animation_editable_label_clicked (GtkWidget *widget,
GdkEvent *event,
gpointer user_data);
G_DEFINE_TYPE (AnimationEditableLabel, animation_editable_label, GTK_TYPE_FRAME)
#define parent_class animation_editable_label_parent_class
static guint animation_editable_label_signals[LAST_SIGNAL] = { 0 };
static void
animation_editable_label_class_init (AnimationEditableLabelClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
animation_editable_label_signals[ALTERNATE_ACTION] =
g_signal_new ("alternate-action",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_FIRST,
0,
NULL, NULL,
NULL,
G_TYPE_NONE,
0);
animation_editable_label_signals[PREPARE_EDITING] =
g_signal_new ("prepare-editing",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_FIRST,
0,
NULL, NULL,
NULL,
G_TYPE_NONE,
0);
animation_editable_label_signals[EDITING] =
g_signal_new ("editing",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_FIRST,
0,
NULL, NULL,
NULL,
G_TYPE_NONE,
0);
object_class->constructed = animation_editable_label_constructed;
object_class->finalize = animation_editable_label_finalize;
object_class->set_property = animation_editable_label_set_property;
object_class->get_property = animation_editable_label_get_property;
g_object_class_install_property (object_class, PROP_TEXT,
g_param_spec_string ("text",
NULL, NULL,
"",
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT));
g_object_class_install_property (object_class, PROP_EDITING_WIDGET,
g_param_spec_object ("editing-widget",
NULL, NULL,
GTK_TYPE_WIDGET,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY));
g_type_class_add_private (klass, sizeof (AnimationEditableLabelPrivate));
}
static void
animation_editable_label_init (AnimationEditableLabel *label)
{
label->priv = G_TYPE_INSTANCE_GET_PRIVATE (label,
ANIMATION_TYPE_EDITABLE_LABEL,
AnimationEditableLabelPrivate);
}
static void
animation_editable_label_constructed (GObject *object)
{
AnimationEditableLabel *label = ANIMATION_EDITABLE_LABEL (object);
GtkWidget *box;
GtkWidget *event_box;
G_OBJECT_CLASS (parent_class)->constructed (object);
/* The viewing widget. */
event_box = gtk_event_box_new ();
gtk_widget_add_events (GTK_WIDGET (event_box),
GDK_BUTTON_PRESS_MASK);
box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 2);
label->priv->label = gtk_label_new (label->priv->text? label->priv->text : "");
gtk_label_set_justify (GTK_LABEL (label->priv->label), GTK_JUSTIFY_LEFT);
gtk_box_pack_start (GTK_BOX (box),
label->priv->label,
TRUE, TRUE,
2.0);
gtk_widget_show (label->priv->label);
label->priv->image = gtk_image_new_from_icon_name ("gtk-edit", GTK_ICON_SIZE_MENU);
gtk_box_pack_start (GTK_BOX (box),
label->priv->image,
FALSE,
FALSE,
2.0);
gtk_widget_show (box);
gtk_container_add (GTK_CONTAINER (event_box), box);
gtk_container_add (GTK_CONTAINER (label),
event_box);
gtk_widget_show (event_box);
label->priv->viewing_widget = event_box;
label->priv->is_editing = FALSE;
label->priv->show_icon = FALSE;
g_signal_connect (event_box, "button-press-event",
G_CALLBACK (animation_editable_label_clicked),
label);
}
static void
animation_editable_label_finalize (GObject *object)
{
AnimationEditableLabel *label = ANIMATION_EDITABLE_LABEL (object);
if (label->priv->text)
g_free (label->priv->text);
if (label->priv->is_editing)
gtk_widget_destroy (label->priv->viewing_widget);
else
gtk_widget_destroy (label->editing_widget);
G_OBJECT_CLASS (parent_class)->finalize (object);
}
static void
animation_editable_label_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
AnimationEditableLabel *label = ANIMATION_EDITABLE_LABEL (object);
switch (property_id)
{
case PROP_TEXT:
if (label->priv->text)
g_free (label->priv->text);
label->priv->text = g_value_dup_string (value);
if (label->priv->label)
gtk_label_set_text (GTK_LABEL (label->priv->label),
label->priv->text);
break;
case PROP_EDITING_WIDGET:
label->editing_widget = g_value_get_object (value);
gtk_widget_show (label->editing_widget);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
animation_editable_label_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
AnimationEditableLabel *label = ANIMATION_EDITABLE_LABEL (object);
switch (property_id)
{
case PROP_TEXT:
g_value_set_string (value, label->priv->text);
break;
case PROP_EDITING_WIDGET:
g_value_set_object (value, label->editing_widget);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static gboolean
animation_editable_label_alternate_click (gpointer user_data)
{
AnimationEditableLabel *label = ANIMATION_EDITABLE_LABEL (user_data);
if (! label->priv->is_editing)
g_signal_emit (label, animation_editable_label_signals[ALTERNATE_ACTION],
0, NULL);
return FALSE;
}
static gboolean
animation_editable_label_clicked (GtkWidget *widget,
GdkEvent *event,
gpointer user_data)
{
AnimationEditableLabel *label = ANIMATION_EDITABLE_LABEL (user_data);
if (event->type == GDK_BUTTON_PRESS)
{
gint time;
g_object_get (gtk_settings_get_for_screen (gdk_screen_get_default ()),
"gtk-double-click-time", &time,
NULL);
/* Do not run the alternate action immediately because we want
* it to be ignored on a double click. */
g_timeout_add (time, animation_editable_label_alternate_click,
label);
}
else if (event->type == GDK_2BUTTON_PRESS)
{
g_signal_emit (label, animation_editable_label_signals[PREPARE_EDITING],
0, NULL);
animation_editable_label_set_editing (label, TRUE);
g_signal_emit (label, animation_editable_label_signals[EDITING],
0, NULL);
}
return TRUE;
}
void
animation_editable_label_set_editing (AnimationEditableLabel *label,
gboolean editing)
{
if (label->priv->is_editing != editing)
{
GtkWidget *current_widget;
GtkWidget *new_widget;
if (editing)
{
current_widget = label->priv->viewing_widget;
new_widget = label->editing_widget;
}
else
{
current_widget = label->editing_widget;
new_widget = label->priv->viewing_widget;
}
g_object_ref (current_widget);
gtk_container_remove (GTK_CONTAINER (label),
current_widget);
gtk_container_add (GTK_CONTAINER (label),
new_widget);
label->priv->is_editing = editing;
}
}
void
animation_editable_label_show_icon (AnimationEditableLabel *label,
gboolean show_icon)
{
if (label->priv->show_icon != show_icon)
{
if (show_icon)
gtk_widget_show (label->priv->image);
else
gtk_widget_hide (label->priv->image);
label->priv->show_icon = show_icon;
}
}
const gchar *
animation_editable_label_get_text (AnimationEditableLabel *label)
{
return label->priv->text;
}
GtkWidget *
animation_editable_label_get_label (AnimationEditableLabel *label)
{
return label->priv->label;
}

View File

@@ -0,0 +1,64 @@
/* GIMP - The GNU Image Manipulation Program
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
*
* Editable label base class
* Copyright (C) 2015-2017 Jehan <jehan@gimp.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __ANIMATION_EDITABLE_LABEL_H__
#define __ANIMATION_EDITABLE_LABEL_H__
#include <gtk/gtk.h>
#define ANIMATION_TYPE_EDITABLE_LABEL (animation_editable_label_get_type ())
#define ANIMATION_EDITABLE_LABEL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), ANIMATION_TYPE_EDITABLE_LABEL, AnimationEditableLabel))
#define ANIMATION_IS_EDITABLE_LABEL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), ANIMATION_TYPE_EDITABLE_LABEL))
#define ANIMATION_EDITABLE_LABEL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), ANIMATION_TYPE_EDITABLE_LABEL, AnimationEditableLabelClass))
#define ANIMATION_IS_EDITABLE_LABEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), ANIMATION_TYPE_EDITABLE_LABEL))
#define ANIMATION_EDITABLE_LABEL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), ANIMATION_TYPE_EDITABLE_LABEL, AnimationEditableLabelClass))
typedef struct _AnimationEditableLabel AnimationEditableLabel;
typedef struct _AnimationEditableLabelClass AnimationEditableLabelClass;
typedef struct _AnimationEditableLabelPrivate AnimationEditableLabelPrivate;
struct _AnimationEditableLabel
{
GtkFrame parent_instance;
AnimationEditableLabelPrivate *priv;
/* Protected variable to be used by the child classes. */
GtkWidget *editing_widget;
};
struct _AnimationEditableLabelClass
{
GtkFrameClass parent_class;
};
GType animation_editable_label_get_type (void);
void animation_editable_label_set_editing (AnimationEditableLabel *label,
gboolean editing);
void animation_editable_label_show_icon (AnimationEditableLabel *label,
gboolean show_icon);
const gchar * animation_editable_label_get_text (AnimationEditableLabel *label);
GtkWidget * animation_editable_label_get_label (AnimationEditableLabel *label);
#endif /* __ANIMATION_EDITABLE_LABEL_H__ */

View File

@@ -0,0 +1,436 @@
/* GIMP - The GNU Image Manipulation Program
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
*
* animation-keyframe_view.c
* Copyright (C) 2016-2017 Jehan <jehan@gimp.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <libgimp/gimp.h>
#include <libgimp/gimpui.h>
#include "libgimp/stdplugins-intl.h"
#include "core/animation.h"
#include "core/animation-camera.h"
#include "core/animation-celanimation.h"
#include "animation-keyframe-view.h"
struct _AnimationKeyFrameViewPrivate
{
AnimationCamera *camera;
gint position;
gboolean local_offset_keyframe;
GtkWidget *offset_entry;
GtkWidget *delete_offset_button;
gboolean local_scale_keyframe;
GtkWidget *scale_entry;
GtkWidget *delete_scale_button;
guint update_source;
gint update_x_offset;
gint update_y_offset;
gdouble update_scale;
gint update_position;
};
/* GObject handlers */
static void animation_keyframe_view_constructed (GObject *object);
static void animation_keyframe_view_dispose (GObject *object);
static gboolean animation_keyframe_update_source (gpointer user_data);
static void on_entry_changed (GimpSizeEntry *entry,
AnimationKeyFrameView *view);
static void on_scale_entry_changed (GtkSpinButton *button,
AnimationKeyFrameView *view);
static void on_offset_entry_changed (GimpSizeEntry *entry,
AnimationKeyFrameView *view);
static void on_camera_changed (AnimationCamera *camera,
gint position,
gint duration,
AnimationKeyFrameView *view);
static void on_delete_offset_clicked (GtkButton *button,
AnimationKeyFrameView *view);
static void on_delete_scale_clicked (GtkButton *button,
AnimationKeyFrameView *view);
G_DEFINE_TYPE (AnimationKeyFrameView, animation_keyframe_view, GTK_TYPE_NOTEBOOK)
#define parent_class animation_keyframe_view_parent_class
static void
animation_keyframe_view_class_init (AnimationKeyFrameViewClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->constructed = animation_keyframe_view_constructed;
object_class->dispose = animation_keyframe_view_dispose;
g_type_class_add_private (klass, sizeof (AnimationKeyFrameViewPrivate));
}
static void
animation_keyframe_view_init (AnimationKeyFrameView *view)
{
view->priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
ANIMATION_TYPE_KEYFRAME_VIEW,
AnimationKeyFrameViewPrivate);
view->priv->position = -1;
view->priv->update_position = -1;
}
/************ Public Functions ****************/
/**
* animation_keyframe_view_new:
*
* Creates a new layer view. You should not show it with
* gtk_widget_show() but with animation_keyframe_view_show() instead.
*/
GtkWidget *
animation_keyframe_view_new ()
{
GtkWidget *view;
view = g_object_new (ANIMATION_TYPE_KEYFRAME_VIEW,
NULL);
return view;
}
/**
* animation_keyframe_view_show:
* @view: the #AnimationKeyFrameView.
* @animation: the #Animation.
* @position:
*
* Show the @view widget, displaying the keyframes set on
* @animation at @position.
*/
void
animation_keyframe_view_show (AnimationKeyFrameView *view,
AnimationCelAnimation *animation,
gint position)
{
AnimationCamera *camera;
gint32 image_id;
gint width;
gint height;
gdouble xres;
gdouble yres;
gint x_offset;
gint y_offset;
gdouble scale;
camera = ANIMATION_CAMERA (animation_cel_animation_get_main_camera (animation));
if (view->priv->position != position ||
view->priv->camera != camera)
{
if (view->priv->camera == camera &&
view->priv->update_position != -1)
{
/* We jumped to another position. Apply the ongoing preview. */
if (view->priv->local_offset_keyframe)
animation_camera_set_offsets (view->priv->camera,
view->priv->update_position,
view->priv->update_x_offset,
view->priv->update_y_offset);
if (view->priv->local_scale_keyframe)
animation_camera_zoom (view->priv->camera,
view->priv->update_position,
view->priv->update_scale);
}
view->priv->camera = camera;
view->priv->position = position;
image_id = animation_get_image_id (ANIMATION (animation));
gimp_image_get_resolution (image_id, &xres, &yres);
animation_get_size (ANIMATION (animation), &width, &height);
gimp_size_entry_set_refval_boundaries (GIMP_SIZE_ENTRY (view->priv->offset_entry),
0, (gdouble) -GIMP_MAX_IMAGE_SIZE,
(gdouble) GIMP_MAX_IMAGE_SIZE);
gimp_size_entry_set_refval_boundaries (GIMP_SIZE_ENTRY (view->priv->offset_entry),
1, (gdouble) -GIMP_MAX_IMAGE_SIZE,
(gdouble) GIMP_MAX_IMAGE_SIZE);
gimp_size_entry_set_size (GIMP_SIZE_ENTRY (view->priv->offset_entry),
0, 0.0, (gdouble) width);
gimp_size_entry_set_size (GIMP_SIZE_ENTRY (view->priv->offset_entry),
1, 0.0, (gdouble) height);
gimp_size_entry_set_resolution (GIMP_SIZE_ENTRY (view->priv->offset_entry),
0, xres, TRUE);
gimp_size_entry_set_resolution (GIMP_SIZE_ENTRY (view->priv->offset_entry),
1, yres, TRUE);
g_signal_handlers_disconnect_by_func (view->priv->scale_entry,
G_CALLBACK (on_scale_entry_changed),
view);
g_signal_handlers_disconnect_by_func (view->priv->offset_entry,
G_CALLBACK (on_offset_entry_changed),
view);
g_signal_handlers_disconnect_by_func (view->priv->camera,
G_CALLBACK (on_camera_changed),
view);
animation_camera_reset_preview (camera);
animation_camera_get (camera, position, &x_offset, &y_offset, &scale);
gimp_size_entry_set_value (GIMP_SIZE_ENTRY (view->priv->offset_entry),
0, (gdouble) x_offset);
gimp_size_entry_set_value (GIMP_SIZE_ENTRY (view->priv->offset_entry),
1, (gdouble) y_offset);
gtk_spin_button_set_value (GTK_SPIN_BUTTON (view->priv->scale_entry),
scale * 100.0);
g_signal_connect (view->priv->scale_entry, "value-changed",
G_CALLBACK (on_scale_entry_changed),
view);
g_signal_connect (view->priv->offset_entry, "value-changed",
G_CALLBACK (on_offset_entry_changed),
view);
g_signal_connect (camera, "camera-changed",
G_CALLBACK (on_camera_changed),
view);
gtk_widget_show (GTK_WIDGET (view));
if (animation_camera_has_offset_keyframe (camera, view->priv->position))
gtk_widget_show (view->priv->delete_offset_button);
else
gtk_widget_hide (view->priv->delete_offset_button);
if (animation_camera_has_zoom_keyframe (camera, view->priv->position))
gtk_widget_show (view->priv->delete_scale_button);
else
gtk_widget_hide (view->priv->delete_scale_button);
}
}
/************ Private Functions ****************/
static void animation_keyframe_view_dispose (GObject *object)
{
AnimationKeyFrameView *view = ANIMATION_KEYFRAME_VIEW (object);
if (view->priv->camera)
{
g_signal_handlers_disconnect_by_func (view->priv->camera,
G_CALLBACK (on_camera_changed),
view);
view->priv->camera = NULL;
}
G_OBJECT_CLASS (parent_class)->dispose (object);
}
static void
animation_keyframe_view_constructed (GObject *object)
{
AnimationKeyFrameView *view = ANIMATION_KEYFRAME_VIEW (object);
GtkWidget *page;
GtkWidget *label;
GtkWidget *widget;
page = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
label = gtk_image_new_from_icon_name ("gimp-tool-move",
GTK_ICON_SIZE_SMALL_TOOLBAR);
gtk_notebook_append_page (GTK_NOTEBOOK (view), page,
label);
view->priv->offset_entry = gimp_size_entry_new (2, GIMP_UNIT_PIXEL, NULL,
TRUE, TRUE, FALSE, 5,
GIMP_SIZE_ENTRY_UPDATE_SIZE);
gimp_size_entry_attach_label (GIMP_SIZE_ENTRY (view->priv->offset_entry),
_("Horizontal offset:"), 0, 1, 0.0);
gimp_size_entry_attach_label (GIMP_SIZE_ENTRY (view->priv->offset_entry),
_("Vertical offset:"), 0, 2, 0.0);
gimp_size_entry_set_pixel_digits (GIMP_SIZE_ENTRY (view->priv->offset_entry), 0);
gimp_size_entry_show_unit_menu (GIMP_SIZE_ENTRY (view->priv->offset_entry), FALSE);
gtk_box_pack_start (GTK_BOX (page), view->priv->offset_entry, FALSE, FALSE, 0);
gtk_widget_show (view->priv->offset_entry);
view->priv->delete_offset_button = gtk_button_new_with_label (_("Reset Offsets"));
g_signal_connect (view->priv->delete_offset_button, "clicked",
G_CALLBACK (on_delete_offset_clicked),
view);
gtk_box_pack_end (GTK_BOX (page), view->priv->delete_offset_button, FALSE, FALSE, 0);
gtk_widget_show (page);
page = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
label = gtk_image_new_from_icon_name ("gimp-scale",
GTK_ICON_SIZE_SMALL_TOOLBAR);
gtk_notebook_append_page (GTK_NOTEBOOK (view), page,
label);
widget = gtk_label_new (_("Zoom: "));
gtk_box_pack_start (GTK_BOX (page), widget, FALSE, FALSE, 0);
gtk_widget_show (widget);
view->priv->scale_entry = gtk_spin_button_new_with_range (0.0, 1000.0, 1.0);
gtk_box_pack_start (GTK_BOX (page), view->priv->scale_entry, FALSE, FALSE, 0);
gtk_widget_show (view->priv->scale_entry);
view->priv->delete_scale_button = gtk_button_new_with_label (_("Reset Zoom"));
g_signal_connect (view->priv->delete_scale_button, "clicked",
G_CALLBACK (on_delete_scale_clicked),
view);
gtk_box_pack_end (GTK_BOX (page), view->priv->delete_scale_button, FALSE, FALSE, 0);
gtk_widget_show (page);
}
static gboolean
animation_keyframe_update_source (gpointer user_data)
{
AnimationKeyFrameView *view = user_data;
view->priv->update_source = 0;
/* Only update the preview if we are currently showing this frame. */
if (view->priv->position == view->priv->update_position)
{
animation_camera_preview_keyframe (view->priv->camera,
view->priv->update_position,
view->priv->update_x_offset,
view->priv->update_y_offset,
view->priv->update_scale);
}
return G_SOURCE_REMOVE;
}
static void
on_entry_changed (GimpSizeEntry *entry G_GNUC_UNUSED,
AnimationKeyFrameView *view)
{
gdouble x_offset;
gdouble y_offset;
gdouble scale;
/* If a timeout is pending, remove before recreating in order to
* postpone the camera update. */
if (view->priv->update_source)
{
g_source_remove (view->priv->update_source);
}
scale = gtk_spin_button_get_value (GTK_SPIN_BUTTON (view->priv->scale_entry)) / 100.0;
x_offset = gimp_size_entry_get_refval (GIMP_SIZE_ENTRY (view->priv->offset_entry), 0);
y_offset = gimp_size_entry_get_refval (GIMP_SIZE_ENTRY (view->priv->offset_entry), 1);
view->priv->update_x_offset = x_offset;
view->priv->update_y_offset = y_offset;
view->priv->update_scale = scale;
view->priv->update_position = view->priv->position;
view->priv->update_source = g_timeout_add (10, animation_keyframe_update_source, view);
}
static void
on_scale_entry_changed (GtkSpinButton *button G_GNUC_UNUSED,
AnimationKeyFrameView *view)
{
view->priv->local_scale_keyframe = TRUE;
gtk_widget_show (view->priv->delete_scale_button);
on_entry_changed (NULL, view);
}
static void
on_offset_entry_changed (GimpSizeEntry *entry G_GNUC_UNUSED,
AnimationKeyFrameView *view)
{
view->priv->local_offset_keyframe = TRUE;
gtk_widget_show (view->priv->delete_offset_button);
on_entry_changed (NULL, view);
}
static void
on_camera_changed (AnimationCamera *camera,
gint position,
gint duration,
AnimationKeyFrameView *view)
{
if (view->priv->position >= position &&
view->priv->position < position + duration)
{
gint x_offset;
gint y_offset;
gdouble scale;
g_signal_handlers_block_by_func (view->priv->offset_entry,
G_CALLBACK (on_offset_entry_changed),
view);
g_signal_handlers_block_by_func (view->priv->offset_entry,
G_CALLBACK (on_scale_entry_changed),
view);
animation_camera_get (camera, view->priv->position,
&x_offset, &y_offset, &scale);
gimp_size_entry_set_value (GIMP_SIZE_ENTRY (view->priv->offset_entry),
0, (gdouble) x_offset);
gimp_size_entry_set_value (GIMP_SIZE_ENTRY (view->priv->offset_entry),
1, (gdouble) y_offset);
gtk_spin_button_set_value (GTK_SPIN_BUTTON (view->priv->scale_entry),
scale * 100.0);
g_signal_handlers_unblock_by_func (view->priv->offset_entry,
G_CALLBACK (on_offset_entry_changed),
view);
g_signal_handlers_unblock_by_func (view->priv->offset_entry,
G_CALLBACK (on_scale_entry_changed),
view);
if (animation_camera_has_offset_keyframe (camera, view->priv->position) ||
view->priv->local_offset_keyframe)
gtk_widget_show (view->priv->delete_offset_button);
else
gtk_widget_hide (view->priv->delete_offset_button);
if (animation_camera_has_zoom_keyframe (camera, view->priv->position) ||
view->priv->local_scale_keyframe)
gtk_widget_show (view->priv->delete_scale_button);
else
gtk_widget_hide (view->priv->delete_scale_button);
}
}
static void
on_delete_offset_clicked (GtkButton *button,
AnimationKeyFrameView *view)
{
g_signal_handlers_block_by_func (view->priv->scale_entry,
G_CALLBACK (on_offset_entry_changed),
view);
view->priv->local_offset_keyframe = FALSE;
animation_camera_delete_offset_keyframe (view->priv->camera,
view->priv->position);
gtk_widget_hide (view->priv->delete_offset_button);
g_signal_handlers_unblock_by_func (view->priv->scale_entry,
G_CALLBACK (on_offset_entry_changed),
view);
}
static void
on_delete_scale_clicked (GtkButton *button,
AnimationKeyFrameView *view)
{
g_signal_handlers_block_by_func (view->priv->scale_entry,
G_CALLBACK (on_scale_entry_changed),
view);
view->priv->local_scale_keyframe = FALSE;
animation_camera_delete_zoom_keyframe (view->priv->camera,
view->priv->position);
gtk_widget_hide (view->priv->delete_scale_button);
g_signal_handlers_unblock_by_func (view->priv->scale_entry,
G_CALLBACK (on_scale_entry_changed),
view);
}

View File

@@ -0,0 +1,55 @@
/* GIMP - The GNU Image Manipulation Program
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
*
* animation-keyframe_view.h
* Copyright (C) 2016-2017 Jehan <jehan@gimp.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __ANIMATION_KEYFRAME_VIEW_H__
#define __ANIMATION_KEYFRAME_VIEW_H__
#define ANIMATION_TYPE_KEYFRAME_VIEW (animation_keyframe_view_get_type ())
#define ANIMATION_KEYFRAME_VIEW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), ANIMATION_TYPE_KEYFRAME_VIEW, AnimationKeyFrameView))
#define ANIMATION_KEYFRAME_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), ANIMATION_TYPE_KEYFRAME_VIEW, AnimationKeyFrameViewClass))
#define ANIMATION_IS_KEYFRAME_VIEW(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), ANIMATION_TYPE_KEYFRAME_VIEW))
#define ANIMATION_IS_KEYFRAME_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), ANIMATION_TYPE_KEYFRAME_VIEW))
#define ANIMATION_KEYFRAME_VIEW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), ANIMATION_TYPE_KEYFRAME_VIEW, AnimationKeyFrameViewClass))
typedef struct _AnimationKeyFrameView AnimationKeyFrameView;
typedef struct _AnimationKeyFrameViewClass AnimationKeyFrameViewClass;
typedef struct _AnimationKeyFrameViewPrivate AnimationKeyFrameViewPrivate;
struct _AnimationKeyFrameView
{
GtkNotebook parent_instance;
AnimationKeyFrameViewPrivate *priv;
};
struct _AnimationKeyFrameViewClass
{
GtkNotebookClass parent_class;
};
GType animation_keyframe_view_get_type (void) G_GNUC_CONST;
GtkWidget * animation_keyframe_view_new (void);
void animation_keyframe_view_show (AnimationKeyFrameView *view,
AnimationCelAnimation *animation,
gint position);
#endif /* __ANIMATION_KEYFRAME_VIEW_H__ */

View File

@@ -0,0 +1,659 @@
/* GIMP - The GNU Image Manipulation Program
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
*
* animation-layer_view.c
* Copyright (C) 2015 Jehan <jehan@gimp.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <libgimp/gimp.h>
#include <libgimp/gimpui.h>
#include "libgimp/stdplugins-intl.h"
#include "animation-layer-view.h"
/* Properties. */
enum
{
PROP_0,
PROP_IMAGE
};
/* Tree model rows. */
enum
{
COLUMN_LAYER_TATTOO,
COLUMN_LAYER_NAME,
COLUMN_SIZE
};
/* Signals. */
enum
{
LAYER_SELECTION,
LAST_SIGNAL
};
struct _AnimationLayerViewPrivate
{
gint32 image_id;
GtkWidget *tree_view;
GtkWidget *filter_button;
gboolean filter_active;
gchar *filter;
};
/* GObject handlers */
static void animation_layer_view_constructed (GObject *object);
static void animation_layer_view_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec);
static void animation_layer_view_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec);
/* GtkWidget handlers */
static gboolean animation_layer_view_button_press (GtkWidget *widget,
GdkEventButton *event);
/* Utils */
static gboolean animation_layer_view_keep_group (AnimationLayerView *view,
gint parent_layer);
static void animation_layer_view_fill (AnimationLayerView *view,
GtkTreeStore *store,
gboolean ignore_filter,
gint parent_layer,
GtkTreeIter *parent);
static GtkTreePath * animation_layer_view_get_row (AnimationLayerView *view,
gint tattoo,
GtkTreeIter *parent);
/* Signal handlers */
static void on_selection_changed (GtkTreeSelection *selection,
AnimationLayerView *view);
static void on_filter_toggled (GtkToggleButton *button,
AnimationLayerView *view);
G_DEFINE_TYPE (AnimationLayerView, animation_layer_view, GTK_TYPE_VBOX)
#define parent_class animation_layer_view_parent_class
static guint animation_layer_view_signals[LAST_SIGNAL] = { 0 };
static void
animation_layer_view_class_init (AnimationLayerViewClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
/**
* AnimationLayerView::layer-selection:
* @layer_view: the widget which received the signal.
* @layers: the #GList of layer tattoos which are currently selected.
*
* The ::layer-selection signal is emitted each time the selection changes
* in @layer_view.
*/
animation_layer_view_signals[LAYER_SELECTION] =
g_signal_new ("layer-selection",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_FIRST,
0,
NULL, NULL,
NULL,
G_TYPE_NONE,
1,
G_TYPE_POINTER);
object_class->constructed = animation_layer_view_constructed;
object_class->get_property = animation_layer_view_get_property;
object_class->set_property = animation_layer_view_set_property;
widget_class->button_press_event = animation_layer_view_button_press;
/**
* AnimationLayerView:animation:
*
* The associated #GimpImage id.
*/
g_object_class_install_property (object_class, PROP_IMAGE,
g_param_spec_int ("image", NULL, NULL,
0, G_MAXINT, 0,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY));
g_type_class_add_private (klass, sizeof (AnimationLayerViewPrivate));
}
static void
animation_layer_view_init (AnimationLayerView *view)
{
GtkTreeStore *store;
view->priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
ANIMATION_TYPE_LAYER_VIEW,
AnimationLayerViewPrivate);
store = gtk_tree_store_new (COLUMN_SIZE, G_TYPE_INT, G_TYPE_STRING);
view->priv->tree_view = gtk_tree_view_new_with_model (GTK_TREE_MODEL (store));
g_object_unref (store);
gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (view->priv->tree_view),
0, _("Layer"),
gtk_cell_renderer_text_new (),
"text", COLUMN_LAYER_NAME,
NULL);
gtk_tree_view_set_rubber_banding (GTK_TREE_VIEW (view->priv->tree_view),
TRUE);
gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (view->priv->tree_view), FALSE);
gtk_box_pack_start (GTK_BOX (view), view->priv->tree_view, TRUE, TRUE, 0);
gtk_widget_show (view->priv->tree_view);
}
/************ Public Functions ****************/
/**
* animation_layer_view_new:
* @image_id: the #GimpImage id.
*
* Creates a new layer view tied to @image_id, ready to be displayed.
*/
GtkWidget *
animation_layer_view_new (gint32 image_id)
{
GtkWidget *layer_view;
layer_view = g_object_new (ANIMATION_TYPE_LAYER_VIEW,
"image", image_id,
NULL);
return layer_view;
}
/**
* animation_layer_view_refresh:
* @view: the #AnimationLayerView.
*
* Refresh the list of layers by reloading the #GimpImage layers.
*/
void
animation_layer_view_refresh (AnimationLayerView *view)
{
GtkTreeModel *model;
GtkTreeSelection *selection;
GList *rows;
GList *iter;
GList *tattoos = NULL;
model = gtk_tree_view_get_model (GTK_TREE_VIEW (view->priv->tree_view));
/* Save current selection. */
selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view->priv->tree_view));
rows = gtk_tree_selection_get_selected_rows (selection, &model);
for (iter = rows; iter; iter = iter->next)
{
GtkTreeIter it;
gint tattoo;
if (gtk_tree_model_get_iter (model, &it, iter->data))
{
gtk_tree_model_get (model, &it,
COLUMN_LAYER_TATTOO, &tattoo, -1);
tattoos = g_list_prepend (tattoos, GINT_TO_POINTER (tattoo));
}
}
g_list_foreach (rows, (GFunc) gtk_tree_path_free, NULL);
g_list_free (rows);
/* Actual refresh. */
gtk_tree_store_clear (GTK_TREE_STORE (model));
animation_layer_view_fill (view, GTK_TREE_STORE (model), FALSE, 0, NULL);
/* Restore the selected rows. */
for (iter = tattoos; iter; iter = iter->next)
{
gint tattoo = GPOINTER_TO_INT (iter->data);
GtkTreePath *path;
path = animation_layer_view_get_row (view, tattoo, NULL);
if (path)
{
gtk_tree_selection_select_path (selection, path);
gtk_tree_path_free (path);
}
}
g_list_free (tattoos);
}
void
animation_layer_view_filter (AnimationLayerView *view,
const gchar *filter)
{
if (g_strcmp0 (view->priv->filter, filter) != 0)
{
if (view->priv->filter)
g_free (view->priv->filter);
view->priv->filter = g_strdup (filter);
if (view->priv->filter_active)
animation_layer_view_refresh (view);
}
}
/**
* animation_layer_view_select:
* @view: the #AnimationLayerView.
* @layers: a #GList of #GimpLayer ids.
* @filter: the viewing filter.
*
* Selects the rows for all @layers in @view.
*/
void
animation_layer_view_select (AnimationLayerView *view,
const GList *layers,
const gchar *filter)
{
GtkTreeSelection *selection;
const GList *layer;
GtkToggleButton *filter_button;
gboolean filter_was_active;
selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view->priv->tree_view));
g_signal_handlers_block_by_func (selection,
G_CALLBACK (on_selection_changed),
view);
filter_button = GTK_TOGGLE_BUTTON (view->priv->filter_button);
filter_was_active = view->priv->filter_active;
/* Deactivate the filtering. */
if (filter_was_active)
gtk_toggle_button_set_active (filter_button, FALSE);
/* Change the filter but do *not* refresh the GUI. */
if (g_strcmp0 (view->priv->filter, filter) != 0)
{
if (view->priv->filter)
g_free (view->priv->filter);
view->priv->filter = g_strdup (filter);
}
gtk_tree_selection_unselect_all (selection);
for (layer = layers; layer; layer = layer->next)
{
GtkTreePath *path;
gint tattoo = GPOINTER_TO_INT (layer->data);
path = animation_layer_view_get_row (view, tattoo, NULL);
g_warn_if_fail (path != NULL);
if (path)
{
gtk_tree_selection_select_path (selection, path);
gtk_tree_path_free (path);
}
}
/* Reactivate the filtering. */
if (filter_was_active)
gtk_toggle_button_set_active (filter_button, TRUE);
g_signal_handlers_unblock_by_func (selection,
G_CALLBACK (on_selection_changed),
view);
}
/************ Private Functions ****************/
static void
animation_layer_view_constructed (GObject *object)
{
AnimationLayerView *view = ANIMATION_LAYER_VIEW (object);
GtkTreeView *tree_view = GTK_TREE_VIEW (view->priv->tree_view);
GtkTreeSelection *selection;
selection = gtk_tree_view_get_selection (tree_view);
gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE);
g_signal_connect (selection, "changed",
G_CALLBACK (on_selection_changed),
view);
view->priv->filter_button = gtk_check_button_new_with_label (_("Filter by level title"));
gtk_box_pack_start (GTK_BOX (view), view->priv->filter_button, FALSE, FALSE, 0);
g_signal_connect (view->priv->filter_button, "toggled",
G_CALLBACK (on_filter_toggled),
view);
gtk_widget_show (view->priv->filter_button);
}
static void
animation_layer_view_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
AnimationLayerView *view = ANIMATION_LAYER_VIEW (object);
switch (property_id)
{
case PROP_IMAGE:
view->priv->image_id = g_value_get_int (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
animation_layer_view_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
AnimationLayerView *view = ANIMATION_LAYER_VIEW (object);
switch (property_id)
{
case PROP_IMAGE:
g_value_set_int (value, view->priv->image_id);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static gboolean
animation_layer_view_button_press (GtkWidget *widget,
GdkEventButton *event)
{
AnimationLayerView *view = ANIMATION_LAYER_VIEW (widget);
GtkTreeView *tree_view = GTK_TREE_VIEW (view->priv->tree_view);
GtkTreeModel *model;
GtkTreeSelection *selection;
GtkTreeRowReference *reference = NULL;
GList *rows;
model = gtk_tree_view_get_model (tree_view);
selection = gtk_tree_view_get_selection (tree_view);
rows = gtk_tree_selection_get_selected_rows (selection, &model);
if (g_list_length (rows) == 1)
{
reference = gtk_tree_row_reference_new (model, rows->data);
}
g_list_foreach (rows, (GFunc) gtk_tree_path_free, NULL);
g_list_free (rows);
g_signal_handlers_block_by_func (selection,
G_CALLBACK (on_selection_changed),
tree_view);
GTK_WIDGET_CLASS (animation_layer_view_parent_class)->button_press_event (widget, event);
g_signal_handlers_unblock_by_func (selection,
G_CALLBACK (on_selection_changed),
tree_view);
rows = gtk_tree_selection_get_selected_rows (selection, &model);
if (g_list_length (rows) == 1 && reference != NULL)
{
GtkTreePath *prev_path = gtk_tree_row_reference_get_path (reference);
GtkTreePath *new_path = rows->data;
/* We keep globally the same behavior as default tree view, except that
* when there is only 1 item selected and you click it, you unselect it.
* You also unselect it by clicking outside any item.
*/
if (gtk_tree_path_compare (new_path, prev_path) == 0)
{
gtk_tree_selection_unselect_path (selection, new_path);
}
else
{
/* Since we blocked the signal callback, call it ourselves. */
on_selection_changed (selection, ANIMATION_LAYER_VIEW (widget));
}
gtk_tree_path_free (prev_path);
gtk_tree_row_reference_free (reference);
}
else
{
/* Since we blocked the signal callback, call it ourselves. */
on_selection_changed (selection, ANIMATION_LAYER_VIEW (widget));
}
g_list_foreach (rows, (GFunc) gtk_tree_path_free, NULL);
g_list_free (rows);
return TRUE;
}
static gboolean
animation_layer_view_keep_group (AnimationLayerView *view,
gint parent_layer)
{
gint *layers;
gint num_layers;
gboolean keep = FALSE;
gint i;
g_return_val_if_fail (gimp_item_is_group (parent_layer), FALSE);
layers = gimp_item_get_children (parent_layer, &num_layers);
for (i = 0; i < num_layers; i++)
{
const gchar *name = gimp_item_get_name (layers[i]);
if (view->priv->filter_active && view->priv->filter &&
g_str_has_prefix (name, view->priv->filter))
{
keep = TRUE;
break;
}
if (gimp_item_is_group (layers[i]) &&
animation_layer_view_keep_group (view, layers[i]))
{
keep = TRUE;
break;
}
}
g_free (layers);
return keep;
}
/* animation_layer_view_fill:
* @view: the #AnimationLayerView.
* @store: the #GtkTreeStore to fill.
* @ignore_filter: insert all layers under @parent_layer.
* @parent_layer: the parent #GimpLayer id. Set 0 for first call.
* @parent: %NULL to search from the first call (used for recursivity).
*
* Recursively fills @store with the #GimpLayers data of the #GimpImage
* tied to @view.
*/
static void
animation_layer_view_fill (AnimationLayerView *view,
GtkTreeStore *store,
gboolean ignore_filter,
gint parent_layer,
GtkTreeIter *parent)
{
gint *layers;
gint num_layers;
GtkTreeIter iter;
gint i;
if (parent_layer == 0)
{
layers = gimp_image_get_layers (view->priv->image_id,
&num_layers);
}
else
{
layers = gimp_item_get_children (parent_layer, &num_layers);
}
for (i = 0; i < num_layers; i++)
{
const gchar *name = gimp_item_get_name (layers[i]);
gboolean keep_group;
if (! ignore_filter &&
view->priv->filter_active &&
view->priv->filter &&
! gimp_item_is_group (layers[i]) &&
! g_str_has_prefix (name, view->priv->filter))
continue;
keep_group = gimp_item_is_group (layers[i]) &&
(ignore_filter ||
! view->priv->filter_active ||
! view->priv->filter ||
g_str_has_prefix (name, view->priv->filter) ||
animation_layer_view_keep_group (view, layers[i]));
if (! gimp_item_is_group (layers[i]) || keep_group)
{
gtk_tree_store_insert (store, &iter, parent, i);
gtk_tree_store_set (store, &iter,
COLUMN_LAYER_TATTOO, gimp_item_get_tattoo (layers[i]),
COLUMN_LAYER_NAME, name,
-1);
}
if (gimp_item_is_group (layers[i]) && keep_group)
{
/* We ignore the filter for children if this group name passes
* the filter. */
animation_layer_view_fill (view, store,
ignore_filter ||
(view->priv->filter_active &&
view->priv->filter &&
g_str_has_prefix (name, view->priv->filter)),
layers[i], &iter);
}
}
g_free (layers);
}
/* animation_layer_view_get_row:
* @view: the #AnimationLayerView.
* @tattoo: the #GimpLayer tattoo.
* @parent: %NULL to search from the first call (used for recursivity).
*
* Returns: the #GtkTreePath for the row of @tattoo, NULL if not found
* in @view.
* The returned path should be freed with gtk_tree_path_free()
*/
static GtkTreePath *
animation_layer_view_get_row (AnimationLayerView *view,
gint tattoo,
GtkTreeIter *parent)
{
GtkTreeModel *model;
GtkTreeIter iter;
model = gtk_tree_view_get_model (GTK_TREE_VIEW (view->priv->tree_view));
if (! gtk_tree_model_iter_children (model, &iter, parent))
return NULL;
do
{
GtkTreePath *path = NULL;
GValue value = { 0, };
gtk_tree_model_get_value (model, &iter,
COLUMN_LAYER_TATTOO,
&value);
if (g_value_get_int (&value) == tattoo)
path = gtk_tree_model_get_path (model, &iter);
g_value_unset (&value);
if (path)
{
return path;
}
else if (gtk_tree_model_iter_has_child (model, &iter))
{
GtkTreePath *found_path;
found_path = animation_layer_view_get_row (view, tattoo, &iter);
if (found_path)
return found_path;
}
}
while (gtk_tree_model_iter_next (model, &iter));
return NULL;
}
static void
on_selection_changed (GtkTreeSelection *selection,
AnimationLayerView *view)
{
GtkTreeView *tree_view = GTK_TREE_VIEW (view->priv->tree_view);
GList *layers = NULL;
GtkTreeModel *model;
GList *rows;
GList *row;
model = gtk_tree_view_get_model (tree_view);
rows = gtk_tree_selection_get_selected_rows (selection, &model);
for (row = rows; row; row = row->next)
{
GtkTreePath *path = row->data;
GtkTreeIter iter;
if (gtk_tree_model_get_iter (model, &iter, path))
{
GValue value = { 0, };
gtk_tree_model_get_value (model, &iter,
COLUMN_LAYER_TATTOO,
&value);
layers = g_list_prepend (layers,
GINT_TO_POINTER (g_value_get_int (&value)));
g_value_unset (&value);
gtk_tree_model_get_value (model, &iter,
COLUMN_LAYER_NAME,
&value);
g_value_unset (&value);
}
}
g_list_foreach (rows, (GFunc) gtk_tree_path_free, NULL);
g_list_free (rows);
g_signal_emit (view, animation_layer_view_signals[LAYER_SELECTION], 0,
layers);
g_list_free (layers);
}
static void
on_filter_toggled (GtkToggleButton *button,
AnimationLayerView *view)
{
if (gtk_toggle_button_get_active (button) != view->priv->filter_active)
{
view->priv->filter_active = gtk_toggle_button_get_active (button);
animation_layer_view_refresh (view);
}
}

View File

@@ -0,0 +1,59 @@
/* GIMP - The GNU Image Manipulation Program
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
*
* animation-layer_view.h
* Copyright (C) 2015 Jehan <jehan@gimp.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __ANIMATION_LAYER_VIEW_H__
#define __ANIMATION_LAYER_VIEW_H__
#define ANIMATION_TYPE_LAYER_VIEW (animation_layer_view_get_type ())
#define ANIMATION_LAYER_VIEW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), ANIMATION_TYPE_LAYER_VIEW, AnimationLayerView))
#define ANIMATION_LAYER_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), ANIMATION_TYPE_LAYER_VIEW, AnimationLayerViewClass))
#define ANIMATION_IS_LAYER_VIEW(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), ANIMATION_TYPE_LAYER_VIEW))
#define ANIMATION_IS_LAYER_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), ANIMATION_TYPE_LAYER_VIEW))
#define ANIMATION_LAYER_VIEW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), ANIMATION_TYPE_LAYER_VIEW, AnimationLayerViewClass))
typedef struct _AnimationLayerView AnimationLayerView;
typedef struct _AnimationLayerViewClass AnimationLayerViewClass;
typedef struct _AnimationLayerViewPrivate AnimationLayerViewPrivate;
struct _AnimationLayerView
{
GtkVBox parent_instance;
AnimationLayerViewPrivate *priv;
};
struct _AnimationLayerViewClass
{
GtkVBoxClass parent_class;
};
GType animation_layer_view_get_type (void) G_GNUC_CONST;
GtkWidget * animation_layer_view_new (gint32 image_id);
void animation_layer_view_refresh (AnimationLayerView *view);
void animation_layer_view_filter (AnimationLayerView *view,
const gchar *filter);
void animation_layer_view_select (AnimationLayerView *view,
const GList *layers,
const gchar *filter);
#endif /* __ANIMATION_LAYER_VIEW_H__ */

View File

@@ -0,0 +1,140 @@
/* GIMP - The GNU Image Manipulation Program
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
*
* animation-menus.c
* Copyright (C) 2016 Jehan <jehan@gimp.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <string.h>
#include <libgimp/gimp.h>
#include <libgimp/gimpui.h>
#include "libgimp/stdplugins-intl.h"
#include "core/animation-celanimation.h"
#include "core/animation-playback.h"
#include "animation-xsheet.h"
#include "animation-menus.h"
typedef struct
{
AnimationCelAnimation *animation;
gint level;
gint position;
gboolean dup_previous;
}
CellData;
static void on_add_cell (GtkMenuItem *menuitem,
gpointer user_data);
static void on_delete_cell (GtkMenuItem *menuitem,
gpointer user_data);
static void
on_add_cell (GtkMenuItem *menuitem,
gpointer user_data)
{
CellData *data = user_data;
animation_cel_animation_cel_add (data->animation,
data->level,
data->position,
data->dup_previous);
}
static void
on_delete_cell (GtkMenuItem *menuitem,
gpointer user_data)
{
CellData *data = user_data;
animation_cel_animation_cel_delete (data->animation,
data->level,
data->position);
}
void
animation_menu_cell (AnimationCelAnimation *animation,
GdkEventButton *event,
gint frame,
gint track)
{
GtkWidget *menu;
GtkWidget *item;
CellData *data;
menu = gtk_menu_new ();
/* Duplicate cell. */
item = gtk_menu_item_new_with_label (_("Duplicate cel"));
data = g_new0 (CellData, 1);
data->animation = animation;
data->position = frame + 1;
data->level = track;
data->dup_previous = TRUE;
g_signal_connect_data (item, "activate",
G_CALLBACK (on_add_cell), data,
(GClosureNotify) g_free, 0);
gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
gtk_widget_show (item);
/* Add empty cell. */
item = gtk_menu_item_new_with_label (_("Push cels down"));
data = g_new0 (CellData, 1);
data->animation = animation;
data->position = frame;
data->level = track;
data->dup_previous = FALSE;
g_signal_connect_data (item, "activate",
G_CALLBACK (on_add_cell), data,
(GClosureNotify) g_free, 0);
gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
gtk_widget_show (item);
/* Add empty cell. */
item = gtk_menu_item_new_with_label (_("Add empty cel after"));
data = g_new0 (CellData, 1);
data->animation = animation;
data->position = frame + 1;
data->level = track;
data->dup_previous = FALSE;
g_signal_connect_data (item, "activate",
G_CALLBACK (on_add_cell), data,
(GClosureNotify) g_free, 0);
gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
gtk_widget_show (item);
/* Delete cell. */
item = gtk_menu_item_new_with_label (_("Delete cell"));
data = g_new0 (CellData, 1);
data->animation = animation;
data->position = frame;
data->level = track;
g_signal_connect_data (item, "activate",
G_CALLBACK (on_delete_cell), data,
(GClosureNotify) g_free, 0);
gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
gtk_widget_show (item);
gtk_widget_show (menu);
gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
event->button, event->time);
}

View File

@@ -0,0 +1,32 @@
/* GIMP - The GNU Image Manipulation Program
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
*
* animation-menus.c
* Copyright (C) 2016 Jehan <jehan@gimp.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __ANIMATION_MENUS_H__
#define __ANIMATION_MENUS_H__
void animation_menu_cell (AnimationCelAnimation *animation,
GdkEventButton *event,
gint frame,
gint track);
#endif /* __ANIMATION_MENUS_H__ */

View File

@@ -0,0 +1,905 @@
/* GIMP - The GNU Image Manipulation Program
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
*
* animation-layer_view.c
* Copyright (C) 2016 Jehan <jehan@gimp.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include "gdk/gdkkeysyms.h"
#include <libgimp/gimp.h>
#include <libgimp/gimpui.h>
#include "libgimp/stdplugins-intl.h"
#include "animation-utils.h"
#include "core/animation-animatic.h"
#include "core/animation-playback.h"
#include "animation-storyboard.h"
/* Properties. */
enum
{
PROP_0,
PROP_ANIMATION
};
struct _AnimationStoryboardPrivate
{
AnimationAnimatic *animation;
AnimationPlayback *playback;
gint current_panel;
GList *panel_buttons;
GList *panel_separators;
GtkWidget *displayed_separator;
GList *disposal_buttons;
GList *comments;
gint dragged_panel;
};
/* GObject handlers */
static void animation_storyboard_constructed (GObject *object);
static void animation_storyboard_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec);
static void animation_storyboard_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec);
static void animation_storyboard_finalize (GObject *object);
/* Callbacks on animation */
static void animation_storyboard_load (Animation *animation,
AnimationStoryboard *view);
static void animation_storyboard_stopped (AnimationPlayback *playback,
AnimationStoryboard *view);
static void animation_storyboard_rendered (AnimationPlayback *playback,
gint frame_number,
GeglBuffer *buffer,
gboolean must_draw_null,
AnimationStoryboard *view);
static void animation_storyboard_duration_spin_changed (GtkSpinButton *spinbutton,
AnimationStoryboard *storyboard);
static gboolean animation_storyboard_comment_keypress (GtkWidget *entry,
GdkEventKey *event,
AnimationStoryboard *view);
static void animation_storyboard_comment_changed (GtkTextBuffer *text_buffer,
AnimationAnimatic *animatic);
static void animation_storyboard_disposal_toggled (GtkToggleButton *button,
AnimationAnimatic *animatic);
static void animation_storyboard_button_clicked (GtkWidget *widget,
AnimationStoryboard *storyboard);
/* Drag and drop */
static void animation_storyboard_panel_drag_begin (GtkWidget *widget,
GdkDragContext *drag_context,
AnimationStoryboard *storyboard);
static void animation_storyboard_panel_drag_end (GtkWidget *widget,
GdkDragContext *drag_context,
AnimationStoryboard *storyboard);
static gboolean animation_storyboard_panel_drag_drop (GtkWidget *widget,
GdkDragContext *drag_context,
gint x,
gint y,
guint time,
AnimationStoryboard *storyboard);
static gboolean animation_storyboard_panel_drag_motion (GtkWidget *widget,
GdkDragContext *drag_context,
gint x,
gint y,
guint time,
AnimationStoryboard *storyboard);
static void animation_storyboard_panel_drag_leave (GtkWidget *widget,
GdkDragContext *context,
guint time,
AnimationStoryboard *storyboard);
/* Utils */
static void animation_storyboard_jump (AnimationStoryboard *view,
gint panel);
static void animation_storyboard_move (AnimationStoryboard *storyboard,
gint from_panel,
gint to_panel);
G_DEFINE_TYPE (AnimationStoryboard, animation_storyboard, GTK_TYPE_SCROLLED_WINDOW)
#define parent_class animation_storyboard_parent_class
static void
animation_storyboard_class_init (AnimationStoryboardClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->constructed = animation_storyboard_constructed;
object_class->finalize = animation_storyboard_finalize;
object_class->get_property = animation_storyboard_get_property;
object_class->set_property = animation_storyboard_set_property;
/**
* AnimationStoryboard:animation:
*
* The associated #AnimationAnimatic.
*/
g_object_class_install_property (object_class, PROP_ANIMATION,
g_param_spec_object ("animation",
NULL, NULL,
ANIMATION_TYPE_ANIMATIC,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY));
g_type_class_add_private (klass, sizeof (AnimationStoryboardPrivate));
}
static void
animation_storyboard_init (AnimationStoryboard *view)
{
view->priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
ANIMATION_TYPE_STORYBOARD,
AnimationStoryboardPrivate);
view->priv->dragged_panel = -1;
}
/**** Public Functions ****/
/**
* animation_storyboard_new:
* @animation: the #AnimationAnimatic for this storyboard.
*
* Creates a new layer view tied to @animation, ready to be displayed.
*/
GtkWidget *
animation_storyboard_new (AnimationAnimatic *animation,
AnimationPlayback *playback)
{
GtkWidget *layer_view;
AnimationStoryboard *storyboard;
layer_view = g_object_new (ANIMATION_TYPE_STORYBOARD,
"animation", animation,
NULL);
storyboard = ANIMATION_STORYBOARD (layer_view);
storyboard->priv->playback = playback;
return layer_view;
}
/**** Private Functions ****/
static void
animation_storyboard_constructed (GObject *object)
{
AnimationStoryboard *view = ANIMATION_STORYBOARD (object);
GtkWidget *layout;
gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (view),
GTK_POLICY_AUTOMATIC,
GTK_POLICY_AUTOMATIC);
layout = gtk_table_new (1, 1, FALSE);
gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (view),
layout);
g_signal_connect (view->priv->animation, "loaded",
(GCallback) animation_storyboard_load,
view);
gtk_widget_show (layout);
}
static void
animation_storyboard_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
AnimationStoryboard *view = ANIMATION_STORYBOARD (object);
switch (property_id)
{
case PROP_ANIMATION:
view->priv->animation = g_value_dup_object (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
animation_storyboard_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
AnimationStoryboard *view = ANIMATION_STORYBOARD (object);
switch (property_id)
{
case PROP_ANIMATION:
g_value_set_object (value, view->priv->animation);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
animation_storyboard_finalize (GObject *object)
{
AnimationStoryboard *view = ANIMATION_STORYBOARD (object);
g_signal_handlers_disconnect_by_func (view->priv->playback,
(GCallback) animation_storyboard_rendered,
view);
g_signal_handlers_disconnect_by_func (view->priv->playback,
(GCallback) animation_storyboard_stopped,
view);
g_object_unref (view->priv->animation);
if (view->priv->panel_buttons)
{
g_list_free (view->priv->panel_buttons);
}
if (view->priv->panel_separators)
{
g_list_free (view->priv->panel_separators);
}
if (view->priv->disposal_buttons)
{
g_list_free (view->priv->disposal_buttons);
}
if (view->priv->comments)
{
g_list_free (view->priv->comments);
}
G_OBJECT_CLASS (parent_class)->finalize (object);
}
static const GtkTargetEntry target_table[] = {
{ "application/x-gimp-animation-panel",
GTK_TARGET_SAME_APP,
ANIMATION_DND_TYPE_PANEL }
};
/* animation_storyboard_load:
* @view: the #AnimationStoryboard.
*
* Recursively fills @store with the #GimpLayers data of the #GimpImage
* tied to @view.
*/
static void
animation_storyboard_load (Animation *animation,
AnimationStoryboard *view)
{
AnimationAnimatic *animatic = ANIMATION_ANIMATIC (animation);
GtkWidget *layout;
GtkWidget *separator;
gint *orig_layers;
gint *layers;
gint32 orig_image_id;
gint32 image_id;
gint n_images;
gint i;
orig_image_id = animation_get_image_id (animation);
image_id = gimp_image_duplicate (orig_image_id);
gimp_image_undo_disable (image_id);
/* The actual layout is the grand-child. */
layout = gtk_bin_get_child (GTK_BIN (view));
layout = gtk_bin_get_child (GTK_BIN (layout));
/* Cleaning previous loads. */
gtk_container_foreach (GTK_CONTAINER (layout),
(GtkCallback) gtk_widget_set_sensitive,
FALSE);
gtk_container_foreach (GTK_CONTAINER (layout),
(GtkCallback) gtk_widget_destroy,
NULL);
if (view->priv->panel_buttons)
{
g_list_free (view->priv->panel_buttons);
view->priv->panel_buttons = NULL;
}
if (view->priv->panel_separators)
{
g_list_free (view->priv->panel_separators);
view->priv->panel_separators = NULL;
}
view->priv->displayed_separator = NULL;
if (view->priv->disposal_buttons)
{
g_list_free (view->priv->disposal_buttons);
view->priv->disposal_buttons = NULL;
}
if (view->priv->comments)
{
g_list_free (view->priv->comments);
view->priv->comments = NULL;
}
/* Setting new values. */
orig_layers = gimp_image_get_layers (orig_image_id,
&n_images);
layers = gimp_image_get_layers (image_id,
&n_images);
gtk_table_resize (GTK_TABLE (layout),
6 * n_images + 1,
9);
separator = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL);
gtk_table_attach (GTK_TABLE (layout),
separator, 0, 9,
6 * n_images,
6 * n_images + 1,
GTK_EXPAND | GTK_FILL,
GTK_FILL,
1, 1);
view->priv->panel_separators = g_list_prepend (view->priv->panel_separators,
separator);
for (i = 0; i < n_images; i++)
{
GdkPixbuf *thumbnail;
GtkWidget *panel_button;
GtkWidget *image;
GtkWidget *comment;
GtkWidget *duration;
GtkWidget *disposal;
gchar *image_name;
gint panel_num = n_images - i - 1;
separator = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL);
gtk_table_attach (GTK_TABLE (layout),
separator, 0, 9,
6 * panel_num,
6 * panel_num + 1,
GTK_EXPAND | GTK_FILL,
GTK_FILL,
1, 1);
view->priv->panel_separators = g_list_prepend (view->priv->panel_separators,
separator);
panel_button = gtk_button_new ();
gtk_button_set_alignment (GTK_BUTTON (panel_button),
0.5, 0.5);
gtk_button_set_relief (GTK_BUTTON (panel_button),
GTK_RELIEF_NONE);
gtk_table_attach (GTK_TABLE (layout),
panel_button, 0, 4,
6 * panel_num + 1,
6 * (panel_num + 1),
GTK_EXPAND | GTK_FILL,
GTK_FILL,
1, 1);
g_object_set_data (G_OBJECT (panel_button), "layer-tattoo",
GINT_TO_POINTER (gimp_item_get_tattoo (orig_layers[i])));
g_object_set_data (G_OBJECT (panel_button), "panel-num",
GINT_TO_POINTER (panel_num));
g_signal_connect (panel_button, "clicked",
G_CALLBACK (animation_storyboard_button_clicked),
view);
view->priv->panel_buttons = g_list_prepend (view->priv->panel_buttons,
panel_button);
gtk_widget_show (panel_button);
gimp_layer_resize_to_image_size (layers[i]);
thumbnail = gimp_drawable_get_thumbnail (layers[i], 250, 250,
GIMP_PIXBUF_SMALL_CHECKS);
image = gtk_image_new_from_pixbuf (thumbnail);
/* Make this button a drag source. */
gtk_drag_source_set (panel_button, GDK_BUTTON1_MASK,
target_table, G_N_ELEMENTS (target_table),
GDK_ACTION_MOVE);
gtk_drag_source_set_icon_pixbuf (panel_button, thumbnail);
g_object_unref (thumbnail);
gtk_drag_dest_set (panel_button, GTK_DEST_DEFAULT_ALL,
target_table, G_N_ELEMENTS (target_table),
GDK_ACTION_MOVE);
g_signal_connect (panel_button, "drag-begin",
G_CALLBACK (animation_storyboard_panel_drag_begin),
view);
g_signal_connect (panel_button, "drag-end",
G_CALLBACK (animation_storyboard_panel_drag_end),
view);
g_signal_connect (panel_button, "drag-drop",
G_CALLBACK (animation_storyboard_panel_drag_drop),
view);
g_signal_connect (panel_button, "drag-motion",
G_CALLBACK (animation_storyboard_panel_drag_motion),
view);
g_signal_connect (panel_button, "drag-leave",
G_CALLBACK (animation_storyboard_panel_drag_leave),
view);
/* Let's align top-right, in case the storyboard gets resized
* and the image grows (the thumbnail right now stays as fixed size). */
gtk_misc_set_alignment (GTK_MISC (image), 1.0, 0.0);
gtk_container_add (GTK_CONTAINER (panel_button), image);
gtk_widget_show (image);
comment = gtk_text_view_new ();
g_object_set_data (G_OBJECT (comment), "panel-num",
GINT_TO_POINTER (panel_num));
gtk_table_attach (GTK_TABLE (layout),
comment, 5, 9,
6 * panel_num + 1,
6 * (panel_num + 1),
GTK_EXPAND | GTK_FILL,
GTK_FILL,
0, 1);
view->priv->comments = g_list_prepend (view->priv->comments,
comment);
gtk_widget_show (comment);
image_name = gimp_item_get_name (layers[i]);
if (image_name)
{
GtkTextBuffer *buffer;
const gchar *comment_contents;
/* Layer name is shown as a tooltip. */
gtk_widget_set_tooltip_text (image, image_name);
buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (comment));
comment_contents = animation_animatic_get_comment (view->priv->animation,
panel_num);
if (comment_contents != NULL)
gtk_text_buffer_insert_at_cursor (buffer, comment_contents, -1);
g_object_set_data (G_OBJECT (buffer), "panel-num",
GINT_TO_POINTER (panel_num));
g_signal_connect (comment, "key-press-event",
G_CALLBACK (animation_storyboard_comment_keypress),
view);
g_signal_connect (buffer, "changed",
(GCallback) animation_storyboard_comment_changed,
animation);
g_free (image_name);
}
duration = gtk_spin_button_new_with_range (0.0, G_MAXINT, 1.0);
gtk_spin_button_set_digits (GTK_SPIN_BUTTON (duration), 0);
gtk_spin_button_set_increments (GTK_SPIN_BUTTON (duration), 1.0, 10.0);
gtk_spin_button_set_snap_to_ticks (GTK_SPIN_BUTTON (duration), TRUE);
gtk_spin_button_set_value (GTK_SPIN_BUTTON (duration),
animation_animatic_get_panel_duration (animatic,
panel_num));
gtk_entry_set_width_chars (GTK_ENTRY (duration), 2);
/* Allowing non-numeric text to type "ms" or "s". */
gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (duration), FALSE);
gtk_table_attach (GTK_TABLE (layout),
duration, 4, 5,
6 * panel_num + 1,
6 * panel_num + 2,
0, /* Do not expand nor fill, nor shrink. */
0, /* Do not expand nor fill, nor shrink. */
0, 1);
g_object_set_data (G_OBJECT (duration), "panel-num",
GINT_TO_POINTER (panel_num));
g_signal_connect (duration, "value-changed",
(GCallback) animation_storyboard_duration_spin_changed,
view);
gtk_widget_show (duration);
disposal = gtk_toggle_button_new ();
gtk_button_set_relief (GTK_BUTTON (disposal), GTK_RELIEF_NONE);
g_object_set_data (G_OBJECT (disposal), "panel-num",
GINT_TO_POINTER (panel_num));
image = gtk_image_new_from_icon_name (GIMP_ICON_TRANSPARENCY,
GTK_ICON_SIZE_MENU);
gtk_container_add (GTK_CONTAINER (disposal), image);
gtk_widget_show (image);
gtk_table_attach (GTK_TABLE (layout),
disposal, 4, 5,
6 * panel_num + 2,
6 * panel_num + 3,
GTK_EXPAND, GTK_EXPAND,
0, 1);
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (disposal),
animation_animatic_get_combine (animatic,
panel_num));
g_signal_connect (disposal, "toggled",
G_CALLBACK (animation_storyboard_disposal_toggled),
animation);
view->priv->disposal_buttons = g_list_prepend (view->priv->disposal_buttons,
disposal);
gtk_widget_show (disposal);
}
g_signal_connect (view->priv->playback, "render",
(GCallback) animation_storyboard_rendered,
view);
g_signal_connect (view->priv->playback, "stop",
(GCallback) animation_storyboard_stopped,
view);
g_free (layers);
g_free (orig_layers);
gimp_image_delete (image_id);
}
static void
animation_storyboard_stopped (AnimationPlayback *playback,
AnimationStoryboard *view)
{
gint position;
gint panel;
position = animation_playback_get_position (playback);
panel = animation_animatic_get_panel (view->priv->animation,
position);
animation_storyboard_jump (view, panel);
}
static void
animation_storyboard_rendered (AnimationPlayback *playback,
gint frame_number,
GeglBuffer *buffer,
gboolean must_draw_null,
AnimationStoryboard *view)
{
gint panel;
panel = animation_animatic_get_panel (view->priv->animation,
frame_number);
animation_storyboard_jump (view, panel);
}
static void
animation_storyboard_duration_spin_changed (GtkSpinButton *spinbutton,
AnimationStoryboard *storyboard)
{
gpointer panel_num;
gint duration;
gint panel_position;
gint position;
panel_num = g_object_get_data (G_OBJECT (spinbutton), "panel-num");
duration = gtk_spin_button_get_value_as_int (spinbutton);
position = animation_playback_get_position (storyboard->priv->playback);
panel_position = animation_animatic_get_position (storyboard->priv->animation,
GPOINTER_TO_INT (panel_num));
if (position >= panel_position)
{
gint cur_duration;
cur_duration = animation_animatic_get_panel_duration (storyboard->priv->animation,
GPOINTER_TO_INT (panel_num));
position += duration - cur_duration;
}
animation_animatic_set_panel_duration (storyboard->priv->animation,
GPOINTER_TO_INT (panel_num),
duration);
animation_playback_jump (storyboard->priv->playback, position);
}
static gboolean
animation_storyboard_comment_keypress (GtkWidget *entry,
GdkEventKey *event,
AnimationStoryboard *view)
{
gpointer panel_num;
panel_num = g_object_get_data (G_OBJECT (entry), "panel-num");
if (event->keyval == GDK_KEY_Tab ||
event->keyval == GDK_KEY_KP_Tab ||
event->keyval == GDK_KEY_ISO_Left_Tab)
{
GtkWidget *comment;
comment = g_list_nth_data (view->priv->comments,
GPOINTER_TO_INT (panel_num) + 1);
if (comment)
{
/* Grab the next comment widget. */
gtk_widget_grab_focus (comment);
}
else
{
/* Loop to the first comment after the last. */
gtk_widget_grab_focus (view->priv->comments->data);
}
return TRUE;
}
return FALSE;
}
static void
animation_storyboard_comment_changed (GtkTextBuffer *text_buffer,
AnimationAnimatic *animatic)
{
gchar *text;
GtkTextIter start;
GtkTextIter end;
gpointer panel_num;
panel_num = g_object_get_data (G_OBJECT (text_buffer), "panel-num");
gtk_text_buffer_get_bounds (text_buffer, &start, &end);
text = gtk_text_buffer_get_text (text_buffer, &start, &end, FALSE);
animation_animatic_set_comment (animatic,
GPOINTER_TO_INT (panel_num),
text);
g_free (text);
}
static void
animation_storyboard_disposal_toggled (GtkToggleButton *button,
AnimationAnimatic *animatic)
{
gpointer panel_num;
panel_num = g_object_get_data (G_OBJECT (button), "panel-num");
animation_animatic_set_combine (animatic,
GPOINTER_TO_INT (panel_num),
gtk_toggle_button_get_active (button));
}
static void
animation_storyboard_button_clicked (GtkWidget *widget,
AnimationStoryboard *storyboard)
{
gpointer panel_num;
gint position;
panel_num = g_object_get_data (G_OBJECT (widget), "panel-num");
position = animation_animatic_get_position (storyboard->priv->animation,
GPOINTER_TO_INT (panel_num));
animation_playback_jump (storyboard->priv->playback, position);
}
/**** Drag and drop ****/
static void
animation_storyboard_panel_drag_begin (GtkWidget *widget,
GdkDragContext *drag_context,
AnimationStoryboard *storyboard)
{
gpointer panel_num;
panel_num = g_object_get_data (G_OBJECT (widget), "panel-num");
storyboard->priv->dragged_panel = GPOINTER_TO_INT (panel_num);
}
static void
animation_storyboard_panel_drag_end (GtkWidget *widget,
GdkDragContext *drag_context,
AnimationStoryboard *storyboard)
{
storyboard->priv->dragged_panel = -1;
}
static gboolean
animation_storyboard_panel_drag_drop (GtkWidget *widget,
GdkDragContext *context,
gint x,
gint y,
guint time,
AnimationStoryboard *storyboard)
{
gpointer panel_num;
GtkAllocation allocation;
gint panel_dest;
g_return_val_if_fail (storyboard->priv->dragged_panel >= 0, FALSE);
panel_num = g_object_get_data (G_OBJECT (widget), "panel-num");
gtk_widget_get_allocation (widget, &allocation);
if (y > allocation.height / 2)
{
panel_dest = GPOINTER_TO_INT (panel_num) + 1;
}
else
{
panel_dest = GPOINTER_TO_INT (panel_num);
}
if (storyboard->priv->dragged_panel < panel_dest)
{
panel_dest--;
}
animation_storyboard_move (storyboard,
storyboard->priv->dragged_panel,
panel_dest);
gtk_drag_finish (context, TRUE,
gdk_drag_context_get_selected_action (context) == GDK_ACTION_MOVE,
time);
return TRUE;
}
static gboolean
animation_storyboard_panel_drag_motion (GtkWidget *widget,
GdkDragContext *context,
gint x,
gint y,
guint time,
AnimationStoryboard *storyboard)
{
GtkWidget *separator;
gpointer panel_num;
GtkAllocation allocation;
gint panel_dest;
g_return_val_if_fail (storyboard->priv->dragged_panel >= 0, FALSE);
panel_num = g_object_get_data (G_OBJECT (widget), "panel-num");
gtk_widget_get_allocation (widget, &allocation);
if (y > allocation.height / 2)
{
panel_dest = GPOINTER_TO_INT (panel_num) + 1;
}
else
{
panel_dest = GPOINTER_TO_INT (panel_num);
}
separator = g_list_nth_data (storyboard->priv->panel_separators,
panel_dest);
if (storyboard->priv->displayed_separator)
gtk_widget_hide (storyboard->priv->displayed_separator);
storyboard->priv->displayed_separator = separator;
gtk_widget_show (separator);
return TRUE;
}
static void
animation_storyboard_panel_drag_leave (GtkWidget *widget,
GdkDragContext *context,
guint time,
AnimationStoryboard *storyboard)
{
if (storyboard->priv->displayed_separator)
gtk_widget_hide (storyboard->priv->displayed_separator);
}
/**** Utils ****/
static void
animation_storyboard_jump (AnimationStoryboard *view,
gint panel)
{
/* Don't jump while playing. This is too disturbing. */
if (! animation_playback_is_playing (view->priv->playback))
{
GtkWidget *button;
if (view->priv->current_panel >= 0)
{
button = g_list_nth_data (view->priv->panel_buttons,
view->priv->current_panel);
gtk_button_set_relief (GTK_BUTTON (button),
GTK_RELIEF_NONE);
}
view->priv->current_panel = panel;
button = g_list_nth_data (view->priv->panel_buttons,
view->priv->current_panel);
gtk_button_set_relief (GTK_BUTTON (button),
GTK_RELIEF_NORMAL);
show_scrolled_child (GTK_SCROLLED_WINDOW (view), button);
}
}
static void
animation_storyboard_move (AnimationStoryboard *storyboard,
gint from_panel,
gint to_panel)
{
Animation *animation;
gint32 image_id;
animation = ANIMATION (storyboard->priv->animation);
image_id = animation_get_image_id (animation);
if (from_panel != to_panel)
{
GtkWidget *layout;
GList *iter;
gpointer tattoo;
gint32 layer;
gint new_position;
gint i;
layout = gtk_bin_get_child (GTK_BIN (storyboard));
layout = gtk_bin_get_child (GTK_BIN (layout));
iter = g_list_nth (storyboard->priv->panel_buttons,
from_panel);
tattoo = g_object_get_data (G_OBJECT (iter->data), "layer-tattoo");
layer = gimp_image_get_layer_by_tattoo (image_id,
GPOINTER_TO_INT (tattoo));
/* Layers are ordered from top to bottom in GIMP. */
new_position = g_list_length (storyboard->priv->panel_buttons) - to_panel - 1;
gimp_image_reorder_item (image_id, layer, 0, new_position);
/* Reorder the internal lists. */
storyboard->priv->panel_buttons = g_list_remove_link (storyboard->priv->panel_buttons,
iter);
storyboard->priv->panel_buttons = g_list_insert (storyboard->priv->panel_buttons,
iter->data, to_panel);
g_list_free (iter);
iter = g_list_nth (storyboard->priv->comments,
from_panel);
storyboard->priv->comments = g_list_remove_link (storyboard->priv->comments,
iter);
storyboard->priv->comments = g_list_insert (storyboard->priv->comments,
iter->data, to_panel);
g_list_free (iter);
/* Refresh the GUI. */
i = MIN (from_panel, to_panel);
iter = g_list_nth (storyboard->priv->panel_buttons, i);
for (; iter; iter = iter->next, i++)
{
g_object_ref (iter->data);
gtk_container_remove (GTK_CONTAINER (layout), iter->data);
gtk_table_attach (GTK_TABLE (layout),
iter->data, 0, 4,
6 * i + 1,
6 * (i + 1),
GTK_EXPAND | GTK_FILL,
GTK_FILL,
1, 1);
g_object_unref (iter->data);
g_object_set_data (G_OBJECT (iter->data), "panel-num",
GINT_TO_POINTER (i));
}
i = MIN (from_panel, to_panel);
iter = g_list_nth (storyboard->priv->comments, i);
for (; iter; iter = iter->next, i++)
{
g_object_ref (iter->data);
gtk_container_remove (GTK_CONTAINER (layout), iter->data);
gtk_table_attach (GTK_TABLE (layout),
iter->data, 5, 9,
6 * i + 1,
6 * (i + 1),
GTK_EXPAND | GTK_FILL,
GTK_FILL,
0, 1);
g_object_unref (iter->data);
g_object_set_data (G_OBJECT (iter->data), "panel-num",
GINT_TO_POINTER (i));
}
animation_animatic_move_panel (storyboard->priv->animation,
from_panel, to_panel);
}
}

View File

@@ -0,0 +1,53 @@
/* GIMP - The GNU Image Manipulation Program
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
*
* animation-layer_view.h
* Copyright (C) 2016 Jehan <jehan@gimp.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __ANIMATION_STORYBOARD_H__
#define __ANIMATION_STORYBOARD_H__
#define ANIMATION_TYPE_STORYBOARD (animation_storyboard_get_type ())
#define ANIMATION_STORYBOARD(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), ANIMATION_TYPE_STORYBOARD, AnimationStoryboard))
#define ANIMATION_STORYBOARD_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), ANIMATION_TYPE_STORYBOARD, AnimationStoryboardClass))
#define ANIMATION_IS_STORYBOARD(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), ANIMATION_TYPE_STORYBOARD))
#define ANIMATION_IS_STORYBOARD_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), ANIMATION_TYPE_STORYBOARD))
#define ANIMATION_STORYBOARD_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), ANIMATION_TYPE_STORYBOARD, AnimationStoryboardClass))
typedef struct _AnimationStoryboard AnimationStoryboard;
typedef struct _AnimationStoryboardClass AnimationStoryboardClass;
typedef struct _AnimationStoryboardPrivate AnimationStoryboardPrivate;
struct _AnimationStoryboard
{
GtkScrolledWindow parent_instance;
AnimationStoryboardPrivate *priv;
};
struct _AnimationStoryboardClass
{
GtkScrolledWindowClass parent_class;
};
GType animation_storyboard_get_type (void) G_GNUC_CONST;
GtkWidget * animation_storyboard_new (AnimationAnimatic *animation,
AnimationPlayback *playback);
#endif /* __ANIMATION_STORYBOARD_H__ */

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,54 @@
/* GIMP - The GNU Image Manipulation Program
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
*
* animation-xsheet.h
* Copyright (C) 2015-2016 Jehan <jehan@gimp.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __ANIMATION_XSHEET_H__
#define __ANIMATION_XSHEET_H__
#define ANIMATION_TYPE_XSHEET (animation_xsheet_get_type ())
#define ANIMATION_XSHEET(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), ANIMATION_TYPE_XSHEET, AnimationXSheet))
#define ANIMATION_XSHEET_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), ANIMATION_TYPE_XSHEET, AnimationXSheetClass))
#define ANIMATION_IS_XSHEET(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), ANIMATION_TYPE_XSHEET))
#define ANIMATION_IS_XSHEET_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), ANIMATION_TYPE_XSHEET))
#define ANIMATION_XSHEET_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), ANIMATION_TYPE_XSHEET, AnimationXSheetClass))
typedef struct _AnimationXSheet AnimationXSheet;
typedef struct _AnimationXSheetClass AnimationXSheetClass;
typedef struct _AnimationXSheetPrivate AnimationXSheetPrivate;
struct _AnimationXSheet
{
GtkScrolledWindow parent_instance;
AnimationXSheetPrivate *priv;
};
struct _AnimationXSheetClass
{
GtkScrolledWindowClass parent_class;
};
GType animation_xsheet_get_type (void) G_GNUC_CONST;
GtkWidget * animation_xsheet_new (AnimationCelAnimation *animation,
AnimationPlayback *playback,
GtkWidget *layer_view,
GtkWidget *keyframe_view);
#endif /* __ANIMATION_XSHEET_H__ */

View File

@@ -51,7 +51,6 @@ AM_CPPFLAGS = \
libexec_PROGRAMS = \
align-layers \
animation-optimize \
animation-play \
blinds \
blur \
border-average \
@@ -200,24 +199,6 @@ animation_optimize_LDADD = \
$(INTLLIBS) \
$(animation_optimize_RC)
animation_play_SOURCES = \
animation-play.c
animation_play_LDADD = \
$(libgimpui) \
$(libgimpwidgets) \
$(libgimpmodule) \
$(libgimp) \
$(libgimpmath) \
$(libgimpconfig) \
$(libgimpcolor) \
$(libgimpbase) \
$(GTK_LIBS) \
$(GEGL_LIBS) \
$(RT_LIBS) \
$(INTLLIBS) \
$(animation_play_RC)
blinds_SOURCES = \
blinds.c

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,6 @@
%plugins = (
'align-layers' => { ui => 1 },
'animation-optimize' => {},
'animation-play' => { ui => 1, gegl => 1 },
'blinds' => { ui => 1 },
'blur' => {},
'border-average' => { ui => 1, gegl => 1 },