The old implementation did a linear scan over the values. This was
slow with slowly changing keyframes. This new implementation skips
over constant (when rounded) segments and performs binary search
in (possibly long) interpolated segments to find the X coordinates
where a change occurs quickly.
Bezier interpolation code is now in a dedicated function and can be
used to either find a Y from a known X or a X from a known Y, with a
given allowed error.
As suggested in the code review:
- More traditional placment of the const specifier, e.g. const unsigned char * instead of unsigned char const *
- Matching casts to also cast to const unsigned char * instead of of unsigned char *
Co-Authored-By: Frank Dana <ferdnyc@gmail.com>
As mentioned in issue #202 QImage::bits() and QImage::scanLine() make
a deep copy of the source image. This is completely unnecessary when
read-only access to the pixel data is required. Changing to
QImage::constBits() and QImage::constScanLine() solves this. Both
functions were introduced in Qt 4.7.
https://doc.qt.io/qt-5/qimage.html#constBits
Using switch allows (some) compilers to emit a warning if a possible
enum value for the interpolation type has been forgotten. This is not
the case now, but might guard against future errors (e.g. adding an
interpolation type)
- GetValue() calculates the value at the given position now ondemand,
without caching values as previously. First, it uses binary search
to find the segment (start and end point) containing the given
position. Constant and linear interpolation are straightforward,
bezier curves are calculating by binary search between the end
points until a point on the curve is found with a X coordinate close
enough to the given position. That points Y coordinate is returned.
- IsIncreasing(), GetRepeatFraction(), GetDelta() use GetValue()
instead of accessing the (removed) Values vector.
- Removed now unused private functions, and the unused public
Process().
First test results show good performance improvements for the
"rendering case" (setting up the keyframe points at once, then getting
all values) and drastic performance improvements for the "preview
case" (changing keyframe points, mixed with getting values).
Returning a non constant reference is not possible; this allows
outside code to invalidate internal invariants (sorted order of
points) by modifying the returned point. To make updates the current
best approach is to remove the point by index, and then add it again.
The Values vector should only be accessed from the outside through the
GetValue() function. The Points vector should only be accessed using
the AddPoint(), RemovePoint(), .. functions.
This helps maintain internal invariants (e.g. keeping Points sorted)
and allows for future removal / lazy evaluation of Values.
The size() of the vectors had been accessed from various parts of the
code; the GetLength() (for Values) and GetCount() (for Points) member
functions provide access to this information and are already part of
the public API.
AddPoint() now searches (binary search) for the best place to insert a
new point, thus always maintaining the order of Points. Therefore,
ReorderPoints() is no longer needed.
CAVEAT: This breaks if some outside code changes (the public member)
Points!