agentleFS
gerphgerph/riscos-agent-skills/skills/riscos-graphics-converters/SKILL.md

riscos-graphics-converters

Skill4 starsChanged 7 months agoMITView on GitHub

Create or update RISC OS image converter modules, especially ImageFileConvert converters that register source-to-destination file type pairs, implement Convert and MiscOp entry points, emit Sprite or sprite-file output, handle ImageFileConvert service startup and shutdown, or call ImageFileConvert SWIs directly to convert image buffers and files.

We split this skill into the rules it gives an agent

33 rules. About 0 are common, 17 are distinctive, and 16 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈17

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 16

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    Use this skill when creating, porting, reviewing, or calling RISC OS image conversion code.

    line 11
  2. Distinctive:

    Use the ImageFileConvert include path in the makefile:

    line 48
  3. Very specific:

    Avoid returning the ImageFileConvert_Register SWI error from module initialisation unless the user explicitly wants the module to require …

    line 110
  4. Very specific:

    If the conversion engine streams output and discovers the final size by running once, it is acceptable for the low-level writer to signal …

    line 153
  5. Distinctive:

    When building a caller that offers export choices to the user, enumerate the available converter pairs first rather than hard-coding …

    line 166
  6. Distinctive:

    Treat the packed pair as the durable identity of an export choice.

    line 194
  7. Distinctive:

    If enumeration fails or no pair matches the source type, present no export choices rather than guessing.

    line 200
  8. Distinctive:

    Use the same packed file type pair:

    line 228
  9. Very specific:

    Use OS_File 20 to read the source file type and length.

    line 255
  10. Very specific:

    Call ImageFileConvert_Convert first with R4 = 0 and R5 = 0 to query the required size.

    line 259
  11. Very specific:

    If R5 returns a concrete size, allocate that many bytes and call again.

    line 261
  12. Distinctive:

    use the stored converter pair to recover the destination file type when the user finally confirms the save

    line 266
  13. Distinctive:

    when calling the conversion, pass the current source file type separately as a sanity check and prefer the destination from the enumerated …

    line 271
  14. Very specific:

    Do not assume the size query always returns a usable final length.

    line 278
  15. Distinctive:

    start with a reasonable output buffer, commonly the input length

    line 282
  16. Very specific:

    if the SWI returns public error &81ACCA, treat it as output buffer too small

    line 284
  17. Very specific:

    Use -1 in R6 unless the conversion specifically needs composition against a chosen background colour.

    line 289
  18. Very specific:

    Prefer MiscOp over full conversion when the user only needs dimensions, bit depth, palette presence, mask presence, or interlace state.

    line 366
  19. Distinctive:

    Use these rather than inventing local error blocks for ordinary converter failures.

    line 382
  20. Distinctive:

    If the cache grows dynamically, impose a sensible limit or use a dynamic area with a maximum size.

    line 401
  21. Very specific:

    If the conversion depends on display mode, font mapping, palettes, or other global rendering state, clear the cache on the relevant …

    line 403
  22. Distinctive:

    Always fall back to direct conversion if cache allocation or extension fails.

    line 406
  23. Very specific:

    When emitting DrawFiles, use riscos-output Draw and DrawFile references for object layout, coordinates, paths, text, sprites, and bounding …

    line 410
  24. Very specific:

    Write the draw_fileheader first, then patch it again after all object bounding boxes have been accumulated.

    line 415
  25. Distinctive:

    Keep all object sizes word-aligned, including text strings and font tables.

    line 419
  26. Distinctive:

    If text is emitted, maintain a DrawFile font table and use font mapping where possible.

    line 426
  27. Distinctive:

    Document unsupported source primitives explicitly.

    line 428
  28. Very specific:

    When emitting a Sprite or sprite file, use riscos-output for the sprite layout details.

    line 435
  29. Very specific:

    If there is no mask, set mask == image.

    line 443
  30. Distinctive:

    Before declaring a converter complete:

    line 449
  31. Distinctive:

    Test that the converter module loads successfully without ImageFileConvert present, if it is intended to be quiescent.

    line 453
  32. Very specific:

    Test with ImageFileConvert loaded and call both MiscOp &400 and Convert.

    line 455
  33. Distinctive:

    Check output headers and raw sizes, not only whether a renderer displays an image.

    line 458

≈ “Common” is an estimate: at least 1 in 5 of the 308,232 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

RISC OS Graphics Converters

Use this skill when creating, porting, reviewing, or calling RISC OS image conversion code. Use riscos-output as well when the converter emits sprites, palettes, masks, Draw files, plots, fonts, or any other RISC OS graphics output format.

ImageFileConvert Modules

ImageFileConvert converter modules register one or more file type pairs. The pair word has destination file type in bits 0-11 and source file type in bits 16-27. For example Clear (&690) to Sprite (&FF9) is:

#define CONVERT_FILETYPE ((0x690 << 16) | 0xFF9)

Register with ImageFileConvert_Register using a converterdef_t:

converterdef_t def;

def.api = CONVERTER_API_VERSION;     /* 100 */
def.flags = CONVERTER_VALID;         /* 0 */
def.type = CONVERT_FILETYPE;
def.name = "Module\t0.01\tAuthor";
def.private = pw;
def.convert = sprite_convert_Entry;
def.miscop = sprite_miscop_Entry;

Include these headers when available:

#include "ImageFileConvert/ImageFileConvert.h"
#include "ImageFileConvert/ImageFileConvertSWIs.h"
#include "ImageFileConvert/converters.h"

Use the ImageFileConvert include path in the makefile:

INCLUDES = <Lib$Dir>.ImageFileConvert.

Converters do not have to target Sprite. A vector converter can register a pair such as WMF (&B2F) to DrawFile (&AFF). If the destination format is renderable through ImageFileRender, ImageFileConvert may expose useful intermediate routes through that destination. For example, a WMF to DrawFile converter can allow WMF to Sprite, SVG, PNG, BMP, or JPEG conversions through existing DrawFile and Sprite converters.

CMHG And Veneers

Register converter entry points as generic veneers so ImageFileConvert can call C handlers with a register block:

#include "ImageFileConvert.h"

service-call-handler: Mod_Service Service_ImageFileConvert_Started,
                                  Service_ImageFileConvert_ConverterChanged,
                                  Service_ImageFileConvert_Dying,
                                  Service_ModeChange

generic-veneers: sprite_convert_Entry/sprite_convert_Handler,
                 sprite_miscop_Entry/sprite_miscop_Handler

Declare the veneer entry symbols in C:

extern void sprite_convert_Entry(void);
extern void sprite_miscop_Entry(void);

The C handlers usually have this shape:

_kernel_oserror *sprite_convert_Handler(_kernel_swi_regs *r, void *pw);
_kernel_oserror *sprite_miscop_Handler(_kernel_swi_regs *r, void *pw);

Registration Lifecycle

Initialisation should try to register if ImageFileConvert is already present, but failure to register must not prevent the module from loading. Leave the module quiescent and try again when Service_ImageFileConvert_Started is received.

Recommended behaviour:

  • Mod_Init: call a helper which attempts registration and returns NULL even if ImageFileConvert is absent.
  • Service_ImageFileConvert_Started: attempt registration.
  • Service_ImageFileConvert_Dying: mark cached registration state as false; do not call back into the dying module.
  • Mod_Final: deregister only if this module believes it is registered.
  • Display- or font-dependent converters may also need to listen for Service_ModeChange and discard cached converted output.

Avoid returning the ImageFileConvert_Register SWI error from module initialisation unless the user explicitly wants the module to require ImageFileConvert at load time.

Convert Entry Point

ImageFileConvert calls the converter with registers based on ImageFileConvert_Convert:

RegisterMeaning
R0conversion flags
R1file type pair
R2input buffer
R3input buffer length
R4output buffer, or 0 to query required size
R5output buffer length, or returned used/required size
R6background colour, often ignored
R7-R9passed through for converter-specific use
R12private word from the converter definition

For Sprite destinations, bit 0 of R0 is Converter_Convert_Sprite_CreateSprite. If set, return a bare sprite. If clear, return a sprite file with the 12-byte sprite-area header before the sprite.

For non-Sprite destinations, do not apply Sprite destination flags. If the destination defines no flags, reject any set bits:

#define CONVERT_RESERVED (~0u)

Reject unsupported flags with err_ConverterBadFlags. Use a narrow mask such as:

#define CONVERT_RESERVED (~Converter_Convert_Sprite_CreateSprite)

On successful size query (R4 == 0), set r->r[5] to the required byte count. On successful conversion, set r->r[5] to the byte count used.

If the conversion engine streams output and discovers the final size by running once, it is acceptable for the low-level writer to signal err_ConverterOutputBufferTooSmall during a size query, provided the public converter handler converts that into success once R5 contains the required size.

Calling ImageFileConvert

Callers that use ImageFileConvert_Convert directly should use the same pair word layout as converter registration: source file type in bits 16-27 and destination file type in bits 0-11.

When building a caller that offers export choices to the user, enumerate the available converter pairs first rather than hard-coding destination types. ImageFileConvert_EnumerateConverters returns packed type pairs in the same layout used by registration and conversion calls, so the caller can filter for the current source type by comparing the high 16 bits:

unsigned long pair = -1;

do
{
    _kernel_oserror *err;
    err = _swix(ImageFileConvert_EnumerateConverters,
                _INR(0,1) | _OUT(1),
                0, pair, &pair);
    if (err)
        pair = -1;
    else if (pair != (unsigned long)-1 &&
             (pair & 0xFFFF0000u) == (source_type << 16))
    {
        unsigned int destination_type = pair & 0xFFF;
        /* Offer this destination to the user. */
    }
} while (pair != (unsigned long)-1);

Practical points:

  • Treat the packed pair as the durable identity of an export choice. If the UI needs to remember which menu entry or button the user picked, store the full pair and derive the destination type from it later.
  • Derive the displayed name of the destination from the destination file type at UI-update time, for example through OS_FSControl 18, rather than keeping a private list of names.
  • If enumeration fails or no pair matches the source type, present no export choices rather than guessing. An empty export list is safer than offering a conversion route that is not actually registered.

Recommended caller sequence:

  • First call ImageFileConvert_Convert with R4 == 0 and R5 == 0 to query the required output size.
  • Allocate an output buffer of the returned size and call again with R4 pointing at the buffer and R5 set to the buffer length.
  • On success, use the returned R5 as the actual output byte count; it may be smaller than the allocated size.
  • Pass -1 as the background colour when the conversion should use its natural background.

Some converters cannot know the output size cheaply. A successful size query may return R5 == -1; in that case, retry conversion with a guessed buffer size and grow it if ImageFileConvert returns the public "output buffer too small" error (&81ACCA). A common fallback is to start with the input size and increase geometrically, freeing failed buffers because their contents are not useful.

To obtain a dynamic sprite area from a Sprite destination conversion, request destination file type Sprite (&FF9) with Sprite flags clear so the converter returns a sprite file. Allocate four extra bytes before the returned data and store the total area size in the first word; the sprite-file header then follows at word 1.

Use the same packed file type pair:

unsigned int pair = (source_type << 16) | destination_type;

Caller-side register use is:

RegisterMeaning
R0conversion flags
R1file type pair
R2input buffer
R3input buffer length
R4output buffer, or 0 to query size
R5output buffer length, or returned output size
R6background colour, often -1 for natural background

Typical call:

_swix(ImageFileConvert_Convert, _INR(0,6) | _OUT(5),
      flags, pair, src, srclen, dest, destlen, background, &used);

For file-to-file conversion:

  1. Use OS_File 20 to read the source file type and length.
  2. Load the source file into memory.
  3. Resolve a textual destination type with OS_FSControl 31 if needed.
  4. Pack the source and destination file types into R1.
  5. Call ImageFileConvert_Convert first with R4 = 0 and R5 = 0 to query the required size.
  6. If R5 returns a concrete size, allocate that many bytes and call again.
  7. Save the converted data with OS_File 10 using the destination file type.

For a save or export dialogue driven by a shared UI object:

  • use the stored converter pair to recover the destination file type when the user finally confirms the save
  • set the save dialogue's file type from pair & 0xFFF
  • derive the suggested leafname from the destination file type name, not from the current source filename alone
  • when calling the conversion, pass the current source file type separately as a sanity check and prefer the destination from the enumerated pair rather than a free-form user-entered type

This keeps the eventual conversion consistent with the specific registered route that was offered to the user.

Do not assume the size query always returns a usable final length. In practice, some converters return R5 == -1 to mean that the final size is not yet known. In that case:

  • start with a reasonable output buffer, commonly the input length
  • call ImageFileConvert_Convert
  • if the SWI returns public error &81ACCA, treat it as output buffer too small
  • enlarge the buffer, commonly by doubling it
  • retry until the conversion succeeds or a different error is returned

Use -1 in R6 unless the conversion specifically needs composition against a chosen background colour.

For Sprite destinations, bit 0 of R0 is still Converter_Convert_Sprite_CreateSprite:

  • set it to request a bare sprite
  • clear it to request a sprite file, including the 12-byte sprite-area header

For non-Sprite destinations, do not set Sprite-specific flags.

MiscOp Entry Point

For Sprite output converters, implement operation Converter_MiscOp_Sprite_ReadInfo (&400) where possible. It allows callers and render proxies to get image details without converting the full image.

For non-Sprite destinations, there may be no useful generic MiscOp. Returning err_ConverterUnknownMiscOp is valid when no operation is supported.

Entry:

RegisterMeaning
R0flags
R1file type pair
R2input buffer
R3input buffer length
R4operation number

Exit for ReadInfo:

RegisterMeaning
R0width in pixels
R1height in pixels
R2bits per pixel
R3image flags
R4X DPI
R5Y DPI

Image flags include:

  • Converter_MiscOp_Sprite_ReadInfo_Palette if the image has a palette.
  • Converter_MiscOp_Sprite_ReadInfo_Colour for colour images.
  • Converter_MiscOp_Sprite_ReadInfo_Mask if there is a mask or alpha channel.
  • Converter_MiscOp_Sprite_ReadInfo_Interlace_None for normal non-interlaced images.

Return err_ConverterUnknownMiscOp for unrecognised operation numbers.

Calling ImageFileConvert_MiscOp

Client code can call ImageFileConvert_MiscOp when only lightweight image metadata is needed.

Use entry registers:

RegisterMeaning
R0flags
R1file type pair
R2input buffer
R3input buffer length
R4miscellaneous operation number

For Converter_MiscOp_Sprite_ReadInfo (&400), expect:

RegisterMeaning
R0width in pixels
R1height in pixels
R2bits per pixel
R3image flags
R4X DPI
R5Y DPI

Prefer MiscOp over full conversion when the user only needs dimensions, bit depth, palette presence, mask presence, or interlace state.

Converter Pseudo-Errors

Converter handlers may return ImageFileConvert pseudo-error pointers from converters.h. ImageFileConvert maps them to its own public errors:

Pseudo-errorMeaning
err_ConverterOutputBufferTooSmalloutput buffer too small
err_ConverterBadFlagsunsupported flags
err_ConverterUnknownMiscOpunrecognised miscellaneous operation
err_ConverterOutOfMemoryconversion workspace allocation failed
err_ConverterCorruptDatasource image is invalid or truncated

Use these rather than inventing local error blocks for ordinary converter failures.

For SWI callers, treat ordinary _kernel_oserror returns as the main failure path. Special-case &81ACCA only inside the resize-and-retry path for output buffers.

Caching And Repeated Conversion

ImageFileRender proxying and intermediate conversions may cause the same source data to be converted repeatedly. A common sequence is a size or bounding-box request followed by a real conversion. Expensive converters, especially vector or metafile converters, can cache the most recent converted output.

Practical cache guidance:

  • Key the cache by input pointer, input length, and a checksum or content signature; pointer and length alone are not enough if buffers are reused.
  • Clear the cache on failed conversion so stale output is never returned.
  • If the cache grows dynamically, impose a sensible limit or use a dynamic area with a maximum size.
  • If the conversion depends on display mode, font mapping, palettes, or other global rendering state, clear the cache on the relevant services such as Service_ModeChange.
  • Always fall back to direct conversion if cache allocation or extension fails.

DrawFile Destination Reminders

When emitting DrawFiles, use riscos-output Draw and DrawFile references for object layout, coordinates, paths, text, sprites, and bounding boxes.

Useful patterns from WMF-style converters:

  • Write the draw_fileheader first, then patch it again after all object bounding boxes have been accumulated.
  • Initialise document and object bounding boxes to extreme values and merge each emitted object into the document bbox.
  • Keep all object sizes word-aligned, including text strings and font tables.
  • For path objects, emit MOVE, LINE, CURVE, CLOSE, and TERM elements exactly as required by DrawFile, and patch the path header after the element count and bbox are known.
  • Convert embedded bitmap records into sprites and wrap them in draw_OBJSPRITE objects when a suitable bitmap-to-sprite converter is available.
  • If text is emitted, maintain a DrawFile font table and use font mapping where possible; rotated text needs a transform and a transformed bbox.
  • Document unsupported source primitives explicitly. Vector formats often contain operations that cannot be represented directly or are not worth supporting initially, such as clipping, raster operations, patterned fills, opacity, or complex brush modes.

Sprite Output Reminders

When emitting a Sprite or sprite file, use riscos-output for the sprite layout details. Important points:

  • The sprite-file header is three words: sprite count, first sprite offset usually 16, and free offset.
  • A bare sprite starts at the sprite header, without those 12 bytes.
  • Paletted sprite palette entries are double words in &BBGGRR00 style.
  • Set image and mask offsets relative to the start of the sprite header.
  • If there is no mask, set mask == image.
  • Preserve exact row stride and lbit/rbit semantics; do not assume byte-width rows for packed low-depth sprites.

Validation

Before declaring a converter complete:

  • Build 32-bit with riscos-amu.
  • Build 64-bit with riscos-amu BUILD64=1 when the project supports it.
  • Test that the converter module loads successfully without ImageFileConvert present, if it is intended to be quiescent.
  • Test with ImageFileConvert loaded and call both MiscOp &400 and Convert.
  • Include at least one size-query call and one real output-buffer conversion.
  • Check output headers and raw sizes, not only whether a renderer displays an image.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.