...and here are the questions I have:
- Would it be worth it to allow the user to specify a pitch (in bytes) between pixel rows in the API? It would be trivial for me to implement, and could be useful for natively supporting more use cases or allowith the user to encode a subrect of a buffer without having to memcpy things around, but it would also be yet another parameter for library users to fill out, which maybe very few would actually use. It would also make the `upsideDown` parameter technically redundant (since the user could just pass in a pointer to the last pixel row and a negative pitch), in which case should I remove that flag, or keep it for convenience?
- Right now, I have the user specify frame timing as an integer number of centiseconds, because that's what the GIF format supports. However, I've considered allowing them to pass in an integer number of milliseconds or a floating point number of seconds instead, and accumulating the timing error in the library - e.g. if they pass in 16 milliseconds, the library spits out a frame that lasts 1 centisecond, and saves the 6 leftover milliseconds to add to the next frame's timing, etc.
This could make things a bit easier for library users, but I'm worried it might push them toward naively passing in measured frame timings directly, which would result in more jittery gifs - especially if the framerate is inconsistent, since passing in the time it took to render a frame is actually a subtle bug - the library expects to recieve the amount of time that frame should be displayed for, which is actually roughly equal to the time the next frame takes to render.
The best solution, in my opinion, is to target a constant framerate that evenly divides into centiseconds, and save off frames whenever you surpass a new multiple of that target frame time. I think exposing the timing as centiseconds will make people more likely to implement this better option, especially if I include example code that uses this strategy, but of course it's also asking more work of the library user. Then again, if they can just copy the logic of the example code, maybe it's not asking much after all?
So, is it a good idea to hide this quirk of the gif format, or should I force users to reckon with it?
- The non-incremental API currently doesn't allow specifying per-frame timings. Given the aforementioned tradeoff between giving people maximum flexibility vs. encouraging use of constant frame times, should I change the API to have the user fill out an array of frame timings alongside the array of pixel buffers?
Those are all the things I need to decide on soon in order to finalize a first version of the API. However, there are some other things I've been thinking about that could come up later down the road:
- Should I support custom memory allocators (to replace the library's use of malloc, realloc, and free)? If so, how? There are a couple that seem to severly limit the usefulness of custom allocators in this situations: First, the total amount of memory allocated over the course of encoding an entire gif is quite high (a bit more than 4*width*height*frames bytes), and there is at least one allocation (the quantized version of the previous frame, used for delta compression) that needs to persist between calls to msf_gif_frame(), so simple allocators (e.g. lifetime-based arena allocators) are unlikely to be useful. Second, the non-incremental API is multithreaded, so any allocator it uses would need to be thread-safe as well. At that point, whatever you're replacing malloc with is basically just another malloc implementation... so I'm not sure how useful that is.
- I've also considered adding an asynchronous API that defers the work to a background thread. The current (synchronous) incremental API is fast, but could still be slow enough to cause problems. Of course library users can do their own multithreading to take care of this, but it's a common enough concern I figured it would be convenient to have the library handle it if possible. However, this would even further complicate allocator usage patterns. Because you'd no longer know when the framebuffer data you pass into the library is done being used, the library would either need to somehow communicate back to the main thread which buffers are ready to be freed, or just assume the buffers were allocated by malloc (or whatever custom allocator you're replacing it with) and free them for you.
- Finally, would it be useful to provide a version of the API that writes to a block of memory instead of a file? I can think of a couple use cases where this might be nice, but in general I worry about adding too much Stuff™ to the API of a library that should ideally be dead-simple and really easy to use...
- Am I massively overthinking everything?
Of course, any other comments or critiques urelated to the things I mentioned are welcome as well.