Big changes without short-term benefits May 2, 2014

Last year we were making a lot of refactoring tasks. As you know, improving code doesn’t affect users in a direct way (and that sucks) but anyway these changes are good for our future (or should be).

There are changes like these ones, where old C structs (jrect and jregion) are replaced with classes (gfx::Rect and gfx::Region). The main issue here was that we were using jrect_new()/jrect_free() and jregion_new()/jregion_free() functions to allocate these structures everywhere. They come from C code and it is a pretty common C pattern: You allocate and free everything manually. Now we use gfx::Rect/Region with value semantics and all those free() are gone. Recommendation: Always prefer value semantics to automatically avoid memory leaks. If your struct is huge, use RAII idiom.

Other big change. This commit is a shame, it contains several changes in one patch, never do that (and sometimes we don’t follow the rules). The main change here is the addition of lock/unlock semantic to raster::Image access. You should never access to image data directly, i.e. using a char* pointer. At least, you should not be able to access to that char* before you lock the image. The right way to access image data is:

  1. lock the region you want to read/write (the lock operation should specify if you are going to write or it’s just read-only),
  2. get your pretty pointer to pixels (or an iterator in our case),
  3. read/modify the data,
  4. unlock the data.

These steps are necessary if you want to change the underlying implementation of the image pixels (e.g. splitting the image in several tiles). In this way, the lock operation can convert your fancy pixels representation to something simpler to iterate, then, the unlock operation can copy those pixels back in the complex representation (only if the lock was a write operation).

Recommendation: Always separate the internal representation of an image from the way you access to that image. Use lock/unlock semantic as it is good for parallelizing work and simplifying image access.

These changes were part of the evolution of Aseprite. They were needed 1) to remove old code and improve productivity-time working with new structures (that avoid memory leaks automatically), and 2) to start adding layers of abstractions (e.g. abstract image pixels access) so we can think in future/better internal data structures for representations of images.