changeset 1896:f80b2dc77c30

Work begins on IFF ILBM/PBM image writer. It is pretty broken, some things will not work and some things are hardcoded. The ByteRun1 compression implementation is somewhat inefficient. Interleaved files do not work yet.
author Matti Hamalainen <ccr@tnsp.org>
date Tue, 26 Jun 2018 03:13:38 +0300
parents eb03869a10d3
children 699ee626912b
files tools/gfxconv.c tools/libgfx.c tools/libgfx.h
diffstat 3 files changed, 362 insertions(+), 4 deletions(-) [+]
line wrap: on
line diff
--- a/tools/gfxconv.c	Tue Jun 26 03:09:01 2018 +0300
+++ b/tools/gfxconv.c	Tue Jun 26 03:13:38 2018 +0300
@@ -1228,7 +1228,6 @@
         }
     }
 
-
     return DMERR_OK;
 }
 
@@ -1359,6 +1358,16 @@
             }
             break;
 
+        case DM_IMGFMT_IFF:
+            spec->compression = 1;
+            spec->nplanes = 0;
+            for (int n = 0; n < 8; n++)
+            {
+                if (image->ncolors & (1 << n))
+                    spec->nplanes = n;
+            }
+            break;
+
         default:
             spec->format = spec->paletted ? DM_COLFMT_PALETTE : DM_COLFMT_RGB;
     }
--- a/tools/libgfx.c	Tue Jun 26 03:09:01 2018 +0300
+++ b/tools/libgfx.c	Tue Jun 26 03:13:38 2018 +0300
@@ -1912,6 +1912,354 @@
 }
 
 
+static int dmWriteIFFChunkHdr(DMResource *fp, DMIFFChunk *chunk, const Uint32 id)
+{
+    chunk->offs = dmftell(fp);
+    chunk->id = id;
+    dmMakeIFFChunkIDStr(chunk);
+
+    if (!dmf_write_be32(fp, chunk->id) ||
+        !dmf_write_be32(fp, chunk->size))
+    {
+        return dmError(DMERR_FREAD,
+            "IFF: Could not write IFF '%s' chunk header.\n",
+            chunk->idStr);
+    }
+    else
+        return DMERR_OK;
+}
+
+
+static int dmWriteIFFChunkFinish(DMResource *fp, DMIFFChunk *chunk)
+{
+    off_t curr = dmftell(fp);
+    if (curr < 0)
+        return dmferror(fp);
+
+    chunk->size = curr - chunk->offs - (sizeof(Uint32) * 2);
+    if ((chunk->size & 1) && !dmf_write_byte(fp, 0))
+        return dmferror(fp);
+
+    if (dmfseek(fp, chunk->offs, SEEK_SET) < 0)
+        return dmferror(fp);
+
+    if (!dmf_write_be32(fp, chunk->id) ||
+        !dmf_write_be32(fp, chunk->size))
+    {
+        return dmError(DMERR_FREAD,
+            "IFF: Could not write IFF '%s' chunk header.\n",
+            chunk->idStr);
+    }
+
+    if (dmfseek(fp, curr, SEEK_SET) < 0)
+        return dmferror(fp);
+
+    return DMERR_OK;
+}
+
+
+enum
+{
+    DMODE_LIT,
+    DMODE_RLE,
+};
+
+
+BOOL dmIFFEncodeByteRun1Flush(
+    DMResource *fp, const int mode, const BOOL flush,
+    size_t *l_offs, const size_t offs, const Uint8 *buf,
+    const Uint8 data, unsigned int *r_count)
+{
+    if (mode == DMODE_LIT)
+    {
+        if (offs - *l_offs > *r_count || flush)
+        {
+            size_t count = (offs - *l_offs) - *r_count;
+            Sint8 tmp = count - 1;
+
+            if (!dmf_write_byte(fp, tmp) ||
+                !dmf_write_str(fp, buf + *l_offs, count))
+                return FALSE;
+        }
+        (*r_count)++;
+    }
+    else
+    {
+        unsigned int count = *r_count;
+        Sint8 tmp = -(count - 1);
+
+        if (!dmf_write_byte(fp, tmp) ||
+            !dmf_write_byte(fp, data))
+            return FALSE;
+
+        *r_count = 0;
+        *l_offs = offs;
+    }
+
+    return TRUE;
+}
+
+
+BOOL dmIFFEncodeByteRun1Row(DMResource *fp, const Uint8 *buf, const size_t bufLen)
+{
+    size_t l_offs = 0, offs = 0;
+    unsigned int r_count = 0;
+    int prev = -1, mode = DMODE_LIT;
+
+    for (offs = 0; offs < bufLen; offs++)
+    {
+        Uint8 data = buf[offs];
+        int next_mode;
+
+        if (data == prev)
+        {
+            r_count++;
+            next_mode = DMODE_RLE;
+        }
+        else
+        {
+            next_mode = DMODE_LIT;
+        }
+
+        BOOL flush = offs - l_offs >= 126 || r_count >= 126;
+        if ((next_mode != mode || flush) &&
+            !dmIFFEncodeByteRun1Flush(fp, mode, flush, &l_offs, offs, buf, prev, &r_count))
+            return FALSE;
+
+        mode = next_mode;
+        prev = data;
+    }
+
+    if (!dmIFFEncodeByteRun1Flush(fp, mode, TRUE, &l_offs, offs, buf, prev, &r_count))
+        return FALSE;
+
+    return TRUE;
+}
+
+
+static BOOL dmIFFWriteOneRow(DMResource *fp, DMIFF *iff, const Uint8 *buf, const size_t bufLen)
+{
+    if (iff->bmhd.compression == IFF_COMP_BYTERUN1)
+        return dmIFFEncodeByteRun1Row(fp, buf, bufLen);
+    else
+        return dmf_write_str(fp, buf, bufLen);
+}
+
+
+static inline Uint8 dmEncodeBit(const Uint8 *buf, const int xc)
+{
+    return (buf[xc] >> (7 - (xc & 7))) & 1;
+}
+
+
+void dmEncodeBitPlane(Uint8 *dp, const Uint8 *src, const int width, const int nplane)
+{
+    for (int xc = 0; xc < width; xc++)
+        dp[xc / 8] |= dmEncodeBit(src, xc) << nplane;
+}
+
+
+int dmEncodeILBMBody(DMResource *fp, DMIFF *iff, const DMImage *img)
+{
+    Uint8 *buf;
+    size_t bufLen;
+    int res = DMERR_OK;
+    const int nplanes = iff->bmhd.nplanes;
+
+    // Allocate planar encoding buffer
+    bufLen = ((img->width + 15) / 16) * 2;
+    if ((buf = dmMalloc(bufLen)) == NULL)
+        return DMERR_MALLOC;
+
+    dmMsg(2, "IFF: plane row size %d bytes.\n", bufLen);
+
+    // Encode the chunk
+    for (int yc = 0; yc < img->height; yc++)
+    {
+        const Uint8 *sp = img->data + (yc * img->pitch);
+
+        dmMemset(buf, 0, bufLen);
+
+        for (int plane = 0; plane < nplanes; plane++)
+        {
+            // Encode bitplane
+            dmEncodeBitPlane(buf, sp, img->width, plane);
+
+            // Compress / write data
+            if (!dmIFFWriteOneRow(fp, iff, buf, bufLen))
+            {
+                res = dmError(DMERR_FWRITE,
+                    "IFF: Error in writing image plane #%d @ %d.\n",
+                    plane, yc);
+                goto error;
+            }
+        }
+    }
+
+error:
+    dmFree(buf);
+    return res;
+}
+
+
+int dmEncodePBMBody(DMResource *fp, DMIFF *iff, const DMImage *img)
+{
+    for (int yc = 0; yc < img->height; yc++)
+    {
+        if (!dmIFFWriteOneRow(fp, iff, img->data + (yc * img->pitch), img->width))
+        {
+            return dmError(DMERR_FWRITE,
+                "IFF: Error writing PBM image row #%d.\n", yc);
+        }
+    }
+
+    return DMERR_OK;
+}
+
+
+int dmWriteIFFImage(DMResource *fp, const DMImage *img, const DMImageConvSpec *spec)
+{
+    Uint32 idsig;
+    DMIFF iff;
+    int res = DMERR_OK;
+
+    // XXX: Non-paletted ILBM not supported!
+    if (!spec->paletted)
+    {
+        return dmError(DMERR_NOT_SUPPORTED,
+            "Non-paletted IFF is not supported.\n");
+    }
+
+    // Setup headers
+    iff.bmhd.x           = 0;
+    iff.bmhd.y           = 0;
+    iff.bmhd.w           = img->width;
+    iff.bmhd.h           = img->height;
+    iff.bmhd.pagew       = img->width;
+    iff.bmhd.pageh       = img->height;
+    iff.bmhd.pad1        = 0;
+    iff.bmhd.xasp        = 1; // XXX TODO: compute the xasp/yasp from the img->aspect
+    iff.bmhd.yasp        = 1;
+
+    iff.camg             = 0; // XXX TODO: when/if HAM support
+    iff.bmhd.masking     = (img->ctransp < 0) ? IFF_MASK_NONE : spec->mask;
+    iff.bmhd.compression = spec->compression ? IFF_COMP_BYTERUN1 : IFF_COMP_NONE;
+    iff.bmhd.transp      = (img->ctransp >= 0 && spec->mask == IFF_MASK_TRANSP) ? img->ctransp : 0xffff;
+    iff.bmhd.nplanes     = spec->nplanes;
+    idsig                = spec->planar ? IFF_ID_ILBM : IFF_ID_PBM;
+
+    dmMsg(2, "IFF: nplanes=%d, comp=%d, mask=%d\n",
+        iff.bmhd.nplanes, iff.bmhd.compression, iff.bmhd.masking);
+
+    // Write IFF FORM header
+    if ((res = dmWriteIFFChunkHdr(fp, &iff.chFORM, IFF_ID_FORM)) != DMERR_OK)
+        goto out;
+
+    // Write IFF ILBM/PBM signature
+    if (!dmf_write_be32(fp, idsig))
+    {
+        res = dmError(DMERR_FWRITE,
+            "IFF: Error writing %s signature.\n",
+            spec->planar ? "ILBM" : "PBM");
+        goto out;
+    }
+
+    // Write BMHD chunk and data
+    if ((res = dmWriteIFFChunkHdr(fp, &iff.chBMHD, IFF_ID_BMHD)) != DMERR_OK ||
+        !dmf_write_be16(fp, iff.bmhd.w) ||
+        !dmf_write_be16(fp, iff.bmhd.h) ||
+        !dmf_write_be16(fp, iff.bmhd.x) ||
+        !dmf_write_be16(fp, iff.bmhd.y) ||
+        !dmf_write_byte(fp, iff.bmhd.nplanes) ||
+        !dmf_write_byte(fp, iff.bmhd.masking) ||
+        !dmf_write_byte(fp, iff.bmhd.compression) ||
+        !dmf_write_byte(fp, iff.bmhd.pad1) ||
+        !dmf_write_be16(fp, iff.bmhd.transp) ||
+        !dmf_write_byte(fp, iff.bmhd.xasp) ||
+        !dmf_write_byte(fp, iff.bmhd.yasp) ||
+        !dmf_write_be16(fp, iff.bmhd.pagew) ||
+        !dmf_write_be16(fp, iff.bmhd.pageh))
+    {
+        res = dmError(DMERR_FWRITE,
+            "IFF: Error writing BMHD chunk.\n");
+        goto out;
+    }
+
+    if ((res = dmWriteIFFChunkFinish(fp, &iff.chBMHD)) != DMERR_OK)
+        goto out;
+
+    //
+    // CMAP
+    //
+    if (img->ncolors > 0 && spec->paletted)
+    {
+        if ((res = dmWriteIFFChunkHdr(fp, &iff.chCMAP, IFF_ID_CMAP)) != DMERR_OK)
+            goto out;
+
+        for (int i = 0; i < img->ncolors; i++)
+        {
+            DMColor *col = &img->pal[i];
+            if (!dmf_write_byte(fp, col->r) ||
+                !dmf_write_byte(fp, col->g) ||
+                !dmf_write_byte(fp, col->b))
+            {
+                res = dmError(DMERR_FWRITE,
+                    "IFF: Could not write CMAP palette entry #%d.\n", i);
+                goto out;
+            }
+        }
+
+        if ((res = dmWriteIFFChunkFinish(fp, &iff.chCMAP)) != DMERR_OK)
+            goto out;
+
+        dmMsg(2, "IFF: CMAP %d entries (%d bytes)\n",
+            img->ncolors, iff.chCMAP.size);
+    }
+
+    //
+    // CAMG
+    //
+    if ((res = dmWriteIFFChunkHdr(fp, &iff.chCMAP, IFF_ID_CAMG)) != DMERR_OK)
+        goto out;
+
+    if (!dmf_write_be32(fp, iff.camg))
+    {
+        return dmError(DMERR_FREAD,
+            "IFF: Error writing CAMG chunk.\n");
+    }
+
+    if ((res = dmWriteIFFChunkFinish(fp, &iff.chCMAP)) != DMERR_OK)
+        goto out;
+
+    //
+    // Encode the body
+    //
+    if ((res = dmWriteIFFChunkHdr(fp, &iff.chBODY, IFF_ID_BODY)) != DMERR_OK)
+        goto out;
+
+    if (spec->planar)
+    {
+        if ((res = dmEncodeILBMBody(fp, &iff, img)) != DMERR_OK)
+            goto out;
+    }
+    else
+    {
+        if ((res = dmEncodePBMBody(fp, &iff, img)) != DMERR_OK)
+            goto out;
+    }
+
+    if ((res = dmWriteIFFChunkFinish(fp, &iff.chBODY)) != DMERR_OK)
+        goto out;
+
+    // Finish the FORM chunk
+    if ((res = dmWriteIFFChunkFinish(fp, &iff.chFORM)) != DMERR_OK)
+        goto out;
+
+out:
+    return res;
+}
+
+
 //
 // List of formats
 //
@@ -1937,8 +2285,7 @@
     {
         "iff", "IFF ILBM / PBM",
         DM_IMGFMT_IFF, DM_FMT_RDWR,
-        fmtProbeIFF, dmReadIFFImage,
-        NULL,
+        fmtProbeIFF, dmReadIFFImage, dmWriteIFFImage,
     },
     {
         "raw", "Plain bitplaned (planar or non-planar) RAW",
--- a/tools/libgfx.h	Tue Jun 26 03:09:01 2018 +0300
+++ b/tools/libgfx.h	Tue Jun 26 03:13:38 2018 +0300
@@ -90,8 +90,9 @@
 {
     int format;
     int scaleX, scaleY;
-    int nplanes, bpp;
+    int nplanes, bpp, mask;
     BOOL planar, paletted;
+    int compression;
 } DMImageConvSpec;
 
 
@@ -140,6 +141,7 @@
 int dmReadPCXImage(DMResource *fp, DMImage **pimg);
 
 int dmReadIFFImage(DMResource *fp, DMImage **pimg);
+int dmWriteIFFImage(DMResource *fp, const DMImage *img, const DMImageConvSpec *spec);
 
 
 typedef struct _DMBitStreamContext