Decide on animation frame Python scripting API
Open, WishlistPublic

Description

So, right now, we avoid touching paintdevices directly, but instead hand the user a node(layer) object that has pixel data.

The node object can tell the user if it is animatable, but there's no frame api yet.

Should we just give the user the pixel data for a given frame, or shall we make a frame object?

I have the feeling we would need the latter so that users can access which color a frame is, or whether it is interpolatable? And I suppose that some users might want to set up frames via python?

So anyway, a suggestion what is the best idea to tackle this?

I would suggest having an object to represent a keyframe, as there are a fair number of properties they have. Specifically, all keyframes have time and color label. Opacity frames have their value, interpolation modes and tangents. Raster frames of course have pixel data.

Time and pixel data seem to me the most important, but I wouldn't be surprised if people find need for the rest as well.

Keyframe channels might not need a specific wrapper class; a generic container (QMap?) would probably do fine to represent the sequence of keyframes contained. Although, we might still want to access each channel individually. E.g. node.keyframes("opacity") could return a collection of opacity keyframes.

Hm... wouldn't it be necessary as well to make sure that people can access the resultant pixeldata of a given frame? especially when dealing with opacity keyframes. Like say, if someone wishes to make an exporter that generates a spritesheet, they would want to take a node and itterate over the frames, but not touch the actual keyframes.

hm...

Okay, let's consider the following example:

We want to make a ghost sprite from a single image, and we'll use python to generate the ghost sprite. We then need to do the following:

  1. We take a sprite, make the layer animated and make the animation go from 0-7(8 frames)
  2. We apply an opacity keyframe to frames 0, 3 and 7. The values of the opacity will go from 25% to 75% to 25% again.
  3. We apply basic easing to these frames.
  4. We also add a transformation mask(yes, this doesn't work yet for now, but lets roll with it). The transformation mask will translate the sprite vertically on frames 0, 3 and 7 again. This time with a difference of -3 to 3 to -3.
  5. We add a transparency mask, and fill the pixel data of each of the 8 frames with plain white noise.
  6. We set the transparency mask to 50% opacity overal.
  7. Finally, we take our image and try to generate a spritesheet from the total. We can use createDocument and setPixelData in node for this, but how do we get the proper data from the opacity keyframes?

I think this is a good sample usecase?

rempt added a comment.May 18 2017, 8:39 AM

Yes, I think that the Frame object should have a pixeldata method just like the Node object.

KisBaseNode has a keyframes() which does not give access to a QMap, but rather the QList of of a QMap.values(). This is not too bad, as the QMap.keys() contain standard IDs noted in KisKeyFrameChannel.

These can be used by KisBaseNode::getKeyframeChannel. That the IDs are standard IDs notes in KisKeyFrameChannel(or gained by keyframe.id()) should be a bit better documented I think...

A Keyframe channel has keyframes. You can get them with keyframeat()

Keyframes have a time, interpolation mode/tangents and a colorlabel.
There's currently 2 types of keyframe channel: Raster and Scalar.

Only Raster Keyframes are able to give the pixel-data of a given frame with fetchFrame().

Right now, it seems that to give the exact pixeldata of any given point in time, it seems we first need to get the pixeldata from the content channel and then apply the scalar channels(like opacity) on it afterwards(?)

Right now, I am thinking that we should have a frame object and stuff, but to have the pixel-data method be handled by node.

Something like Node.getPixelDataAtTime(int time) and then in there something like(pseudocode)

{
    rasterkeyframechannel = node->getkeyframechannle("content")
    keyframeSP = rasterkeyframechannel.keyframeAt(time)
    rasterkeyframechannel.fetchFrame(keyframeSP, paintdevice)
    opacitykeyframechannel = node->getkeyframechannle("opacity")
    qreal opacity = opacitykeyframechannel.interpolatedValue(time)
    //whatever it is we can do to apply opacity to paintdevice
    //do whatever we do to get the paintdevice's pixeldata in a bytearray.
    return bytearray
}
rempt added a comment.May 27 2017, 3:55 PM

Outch... Generating a list of the values in a map is _expensive_. That really needs to be refactored in KisBVaseNode.

KisBaseNode has a keyframes() which does not give access to a QMap, but rather the QList of of a QMap.values(). This is not too bad, as the QMap.keys() contain standard IDs noted in KisKeyFrameChannel.

If you mean QList<KisKeyframeChannel *> KisBaseNode::keyframeChannels() const then yes, it returns only a list of values. It might be more useful to return a list of ids or a complete map.