Hi again,
Iâm hailing from the other thread. Your script is cool and well done, even more so for being new to Aseprite and to scripting in its API. Thanks for being open to feedback.
I hope some of these ideas will help you make it better. I realize how sucky it is when youâre leaned on for this and that feature, however, after youâve put your work on the Internet for free. The .iff
extension seems so expansive that you could spend a lot of time and effort and still not explore all the possibilities or quash every bug.
As mentioned, I donât have an Amiga, am not part of that community. Excuse incorrect assumptions, terminology, etc.
Suggestions
-
Putting an ampersand before a character in the text field allows the user to hold down
Alt
and press the letter to activate a button. For example,Alt+E
fordlg:button { id = "export", text = "&Export", focus = true }
. However, beware of conflicts with the menus in the menu bar.Alt+E
is also the Edit menu, so maybe an alternative like"E&xport"
. -
A cel image may be smaller or larger than the sprite canvas, or it may fall partially outside the sprite canvas due to an offset position. The
BMHD
chunk seems to support both of these possibilities (maybe SPRT is also needed, idk?) . I donât understand why page width and height are signed words, though, unless my analogy between a page and a sprite canvas is wrong. You can simplify the matter by creating a flattened copy at a given frame with Image:drawSprite. The spriteâs spec can be passed to the image constructor. If you prefer to handle the complexity, try a rectangle intersection between a spriteâs and celâs bounds. -
If you want to handle cel images, beware of tile map color mode images from tile map layers in Aseprite 1.3 onward. How to handle these varies with which version of 1.3 you use, and may be simplified in the future / from the last time I focused on them.
-
Aseprite supports pixel aspect ratios. The ratios supported explicitly by Asepriteâs UI are 2:1 and 1:2. Internally, the scripting API allows whatever. By âwhateverâ, I mean including invalid ratios like -5:0. Aspect ratios like 11:13 will have visual glitches, but may be worth it. To support this would require reducing aspect ratios â e.g., from 6:3 to 2:1 â to keep the sprite from being so big that Aseprite canât zoom out enough.
-
For palettes, itâd be smoother UX imo to find the nearest larger power of 2 from the paletteâs length, then write the palette (or up to 256 in case the palette is longer than that), then fill any remainder with a default color. Maybe black, or maybe loop through the palette repeatedly using the floor modulo operator,
%
. This is arguable, I suppose, since you may prefer to fail loudly if users do not discipline themselves re: the mediumâs constraints. But you know how users are. -
Same with color mode, although you could say this is even more hand-holdy. For RGB color mode, you could access Asepriteâs nearest palette match functionality â without converting the sprite â via a
Color
objectâs index field. A dictionary using a 32-bit integer ABGR color as a key is an option if youâre concerned about performance costs. For grayscale, you canât assume that the sprite palette is 256 entries from black to white, but you can create such a palette. After the alpha is isolated from the 16-bit pixel, the gray value can be demoted to an index. Or you can promote a gray image to 32-bit color by repeating a pixel gray three times as BGR. Or you could try the spriteâs copy constructor to create a duplicate, convert to indexed (the command allows for dithering options), then close the duplicate after writing the file. -
For
.bmp
files, Aseprite uses whether or not a sprite has a background as a surrogate for determining 24-bit RGB color vs. 32-bit. I guess for.iff
this is referred to as âdeepâ or âtrue colorâ. Alpha with background layers in indexed color mode is messy because you can, for example, set an opaque swatch at index 6 to be your transparent color and paint a background with a palette swatch that has zero alpha at index 7. -
It could be nice to support specific extensions, say,
.ilbm
in addition to.iff
. However, that can introduce extra problems, for example, if the user types.ilbm
but then the sprite has something critical thatâs unsupported by that format. It also may change when you can report a problem to the user. -
I like to not close the dialog on export, but to just announce success and return to the ready state. I usually use app.alert when the UI is available, but I also like how youâve set it up.
-
I tried and failed to write a compression algorithm from scratch. I gather that it falls under run-length encoding. One of the tricks being that it prefixes both runs of same and different bytes with a length signal, if I understand the spec correctly.
-
Precede function definitions with
local
unless thereâs a reason for them to be global, e.g.,local function showError(err)
. Same for variableslocal spr = app.sprite
. -
In newer versions of Lua, the
<const>
keyword allows for immutability, e.g.,local dlg <const> = Dialog("Export Complete")
. I find it makes debugging easier, and is worth the extra syntax. Theconst
is less of a safeguard to reference types than value types, though. -
The line
title=string,
in the dialog file widget is probably included by mistake. -
If you plan on more Lua coding, look for extensions for your editor of choice. If the ext. supports custom type definitions, look for one. I use one for Aseprite version 1.3 (forked from the original) with the Lua LLS extension in VS Code. When all else fails, check the source code that handles inputs from Lua. It helps to reference the return type of certain methods.
-
I do not specialize in optimization or performance. And I realize the first rule of optimization is donât. But a general Lua guide on performance I know of is âLua Performance Tipsâ by Roberto Ierusalimschy. I use Luaâs os.clock to measure elapsed time.
Images to illustrate some things from above:
Above, the King Tut display aspect is 11:13 on the left vs. 1:1 on the right. The left is at 8.3% zoom. Notice the visual artifacts from moving the dialog window around.
Above is Seven Seas by Fairfax in Aseprite. The blue rectangle is from View > Show > Layer Edges
(the UI terminology differs from that of the scripting API). The image is larger than the sprite canvas. An importing bug on my end is a likelihood. For images created in Aseprite from the start, arguably not a big deal, given how regularly cels are trimmed.
Above, IrfanView seems to ignore any difference between image width and sprite width, or maybe takes the larger of the two?
Above is IrfanViewâs display of the re-exported image from the script.
Cheers,
Jeremy