about summary refs log tree commit diff
path: root/converter/other
diff options
context:
space:
mode:
authorgiraffedata <giraffedata@9d0c8265-081b-0410-96cb-a4ca84ce46f8>2022-06-24 04:36:14 +0000
committergiraffedata <giraffedata@9d0c8265-081b-0410-96cb-a4ca84ce46f8>2022-06-24 04:36:14 +0000
commit5bcb369386d617631627a4fb086ff6ab80ffa157 (patch)
treeb2f6cae56a948d3897c1ed6452f870ffc8c832de /converter/other
parent5991a720c260e171c145d035014609f18cb2eaf3 (diff)
downloadnetpbm-mirror-5bcb369386d617631627a4fb086ff6ab80ffa157.tar.gz
netpbm-mirror-5bcb369386d617631627a4fb086ff6ab80ffa157.tar.xz
netpbm-mirror-5bcb369386d617631627a4fb086ff6ab80ffa157.zip
Promote Development release to Advanced, version 10.99.00
git-svn-id: http://svn.code.sf.net/p/netpbm/code/advanced@4358 9d0c8265-081b-0410-96cb-a4ca84ce46f8
Diffstat (limited to 'converter/other')
-rw-r--r--converter/other/Makefile7
-rw-r--r--converter/other/exif.c6
-rw-r--r--converter/other/pamtoqoi.c439
-rw-r--r--converter/other/qoi.h101
-rw-r--r--converter/other/qoitopam.c323
-rw-r--r--converter/other/winicontopam.c2
6 files changed, 871 insertions, 7 deletions
diff --git a/converter/other/Makefile b/converter/other/Makefile
index 5dfc27ec..3b3b6aa0 100644
--- a/converter/other/Makefile
+++ b/converter/other/Makefile
@@ -134,14 +134,15 @@ PORTBINARIES =  avstopam bmptopnm fitstopnm \
 		gemtopnm giftopnm hdifftopam infotopam \
 		pamtoavs pamtodjvurle pamtofits pamtogif \
 		pamtohdiff pamtohtmltbl pamtompfont pamtooctaveimg \
-		pamtopam pamtopdbimg pamtopfm pamtopnm pamtosrf pamtouil \
+		pamtopam pamtopdbimg pamtopfm pamtopnm \
+                pamtoqoi pamtosrf pamtouil \
 		pamtowinicon pamtoxvmini \
 		pbmtopgm pdbimgtopam pfmtopam \
 	        pgmtopbm pgmtoppm ppmtopgm pnmtoddif \
 		pnmtopclxl pnmtorast \
-		pnmtosgi pnmtosir pamtotga pnmtoxwd \
+		pnmtosgi pnmtosir pamtotga pnmtoxwd qoitopam \
 		rasttopnm rlatopam sgitopnm sirtopnm srftopam sunicontopnm \
-		winicontopam xwdtopnm yuy2topam zeisstopnm
+		winicontopam xwdtopnm yuy2topam zeisstopnm \
 
 ifneq ($(DONT_HAVE_PROCESS_MGMT),Y)
   PORTBINARIES += pstopnm pnmtops
diff --git a/converter/other/exif.c b/converter/other/exif.c
index 1bfe4b2b..87236dc7 100644
--- a/converter/other/exif.c
+++ b/converter/other/exif.c
@@ -533,7 +533,7 @@ processDirEntry(const unsigned char *  const dirEntry,
         for (end = byteCount; end > 0 && value[end] == ' '; --end);
 
         /* Skip "ASCII" if it is there */
-        if (end >= 5 && MEMEQ(value, "ASCII", 5))
+        if (end >= 5 && memeq(value, "ASCII", 5))
             cursor = 5;
         else
             cursor = 0;
@@ -860,12 +860,12 @@ exif_parse(const unsigned char * const exifData,
     if (wantTagTrace)
         fprintf(stderr, "Exif header %d bytes long\n",length);
 
-    if (MEMEQ(exifData + 0, "II" , 2)) {
+    if (memeq(exifData + 0, "II" , 2)) {
         if (wantTagTrace) 
             fprintf(stderr, "Exif header in Intel order\n");
         byteOrder = NORMAL;
     } else {
-        if (MEMEQ(exifData + 0, "MM", 2)) {
+        if (memeq(exifData + 0, "MM", 2)) {
             if (wantTagTrace) 
                 fprintf(stderr, "Exif header in Motorola order\n");
             byteOrder = MOTOROLA;
diff --git a/converter/other/pamtoqoi.c b/converter/other/pamtoqoi.c
new file mode 100644
index 00000000..638efa3a
--- /dev/null
+++ b/converter/other/pamtoqoi.c
@@ -0,0 +1,439 @@
+/*
+  pamtoqoi -  Converts PAM to a QOI - The "Quite OK Image" format file
+
+  This program is part of Netpbm.
+
+  ---------------------------------------------------------------------
+
+  QOI - The "Quite OK Image" format for fast, lossless image compression
+
+  Encoder by Dominic Szablewski - https://phoboslab.org
+
+  -- LICENSE: The MIT License(MIT)
+
+  Copyright(c) 2021 Dominic Szablewski
+
+  Permission is hereby granted, free of charge, to any person obtaining a copy
+  of this software and associated documentation files(the "Software"), to deal
+  in the Software without restriction, including without limitation the rights
+  to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
+  copies of the Software, and to permit persons to whom the Software is
+  furnished to do so, subject to the following conditions :
+
+  The above copyright notice and this permission notice shall be included in
+  all copies or substantial portions of the Software.
+
+  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
+  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+  SOFTWARE.
+
+  For more information on the format visit: https//qoiformat.org/ .
+
+  Modifications for Netpbm read routines by Akira F. Urushibata.
+
+*/
+
+#include <string.h>
+#include <assert.h>
+#include "pam.h"
+#include "nstring.h"
+#include "mallocvar.h"
+#include "shhopt.h"
+
+#include "qoi.h"
+
+
+
+struct CmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char * inputFileName;  /* '-' if stdin */
+};
+
+
+
+static void
+parseCommandLine(int                  argc,
+                 const char **        argv,
+                 struct CmdlineInfo * cmdlineP ) {
+/*----------------------------------------------------------------------------
+   Parse program command line described in Unix standard form by argc
+   and argv.  Return the information in the options as *cmdlineP.
+
+   If command line is internally inconsistent (invalid options, etc.),
+   issue error message to stderr and abort program.
+
+   Note that the strings we return are stored in the storage that
+   was passed to us as the argv array.  We also trash *argv.
+-----------------------------------------------------------------------------*/
+    optEntry * option_def;
+        /* Instructions to pm_optParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+
+    MALLOCARRAY(option_def, 100);
+
+    OPTENTINIT;
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
+
+    pm_optParseOptions3(&argc, (char **)argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    if (argc-1 < 1)
+        cmdlineP->inputFileName = "-";
+    else if (argc-1 == 1)
+        cmdlineP->inputFileName = argv[1];
+    else
+        pm_error("Program takes at most one argument: input file name.  "
+            "you specified %d", argc-1);
+}
+
+
+
+static void
+encodeQoiHeader(qoi_Desc const qoiDesc) {
+
+    assert (QOI_MAGIC_SIZE + 4 + 4 + 1 + 1 == QOI_HEADER_SIZE);
+
+    fwrite (qoi_magic, QOI_MAGIC_SIZE, 1, stdout);
+    pm_writebiglongu(stdout, qoiDesc.width);
+    pm_writebiglongu(stdout, qoiDesc.height);
+    putchar(qoiDesc.channelCt);
+    putchar(qoiDesc.colorspace);
+
+}
+
+enum Tupletype {BW, BWAlpha, GRAY, GRAYAlpha, RGB, RGBAlpha,
+                GRAY255, GRAY255Alpha, RGB255, RGB255Alpha};
+
+
+
+static void
+createSampleMap(sample    const oldMaxval,
+                sample ** const sampleMapP) {
+
+    unsigned int i;
+    sample * sampleMap;
+    sample   const newMaxval = 255;
+
+    MALLOCARRAY_NOFAIL(sampleMap, oldMaxval+1);
+
+    for (i = 0; i <= oldMaxval; ++i)
+        sampleMap[i] = ROUNDDIV(i * newMaxval, oldMaxval);
+
+    *sampleMapP = sampleMap;
+}
+
+
+
+static enum Tupletype
+tupleTypeFmPam(const char * const pamTupleType,
+               sample       const maxval) {
+
+    enum Tupletype retval;
+
+    if (streq(pamTupleType, PAM_PBM_TUPLETYPE)) {
+        if (maxval !=1)
+            pm_error("Invalid maxval (%lu) for tuple type '%s'.",
+                     maxval, pamTupleType);
+        else
+            retval =  BW;
+    } else if (streq(pamTupleType, PAM_PBM_ALPHA_TUPLETYPE)) {
+      if (maxval !=1)
+          pm_error("Invalid maxval (%lu) for tuple type '%s'.",
+                    maxval, pamTupleType);
+      else
+          retval =  BWAlpha;
+    } else if (maxval == 255) {
+        if (streq(pamTupleType, PAM_PPM_TUPLETYPE))
+            retval =  RGB255;
+        else if(streq(pamTupleType, PAM_PPM_ALPHA_TUPLETYPE))
+            retval =  RGB255Alpha;
+        else if(streq(pamTupleType, PAM_PGM_TUPLETYPE))
+            retval =  GRAY255;
+        else if(streq(pamTupleType, PAM_PGM_ALPHA_TUPLETYPE))
+            retval =  GRAY255Alpha;
+        else
+            pm_error("Don't know how to convert tuple type '%s'.",
+                     pamTupleType);
+    } else {
+        if (streq(pamTupleType, PAM_PPM_TUPLETYPE))
+            retval =  RGB;
+        else if(streq(pamTupleType, PAM_PPM_ALPHA_TUPLETYPE))
+            retval =  RGBAlpha;
+        else if(streq(pamTupleType, PAM_PGM_TUPLETYPE))
+            retval =  GRAY;
+        else if(streq(pamTupleType, PAM_PGM_ALPHA_TUPLETYPE))
+            retval =  GRAYAlpha;
+        else
+            pm_error("Don't know how to convert tuple type '%s'.",
+                     pamTupleType);
+    }
+
+    return retval;
+}
+
+
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wreturn-type"
+
+static unsigned int
+channelCtFmTupleType(enum Tupletype const tupleType) {
+
+    switch (tupleType) {
+        case RGB:
+          return 3;
+        case RGB255:
+          return 3;
+        case RGBAlpha:
+          return 4;
+        case RGB255Alpha:
+          return 4;
+        case BW:
+        case GRAY:
+          return 3;
+        case GRAY255:
+          return 3;
+        case BWAlpha:
+        case GRAYAlpha:
+          return 4;
+        case GRAY255Alpha:
+          return 4;
+    }
+}
+#pragma GCC diagnostic pop
+
+
+
+static qoi_Rgba
+pxFmTuple(tuple          const tuple0,
+          const sample * const sampleMap,
+          enum Tupletype const tupleType) {
+/*----------------------------------------------------------------------------
+   Convert PAM tuple to qoi rgba pixel struct
+-----------------------------------------------------------------------------*/
+    qoi_Rgba px;
+
+    switch (tupleType) {
+    case RGB:
+        px.rgba.r = sampleMap[tuple0[PAM_RED_PLANE]];
+        px.rgba.g = sampleMap[tuple0[PAM_GRN_PLANE]];
+        px.rgba.b = sampleMap[tuple0[PAM_BLU_PLANE]];
+        px.rgba.a = 255;
+        break;
+    case RGB255:
+        px.rgba.r = tuple0[PAM_RED_PLANE];
+        px.rgba.g = tuple0[PAM_GRN_PLANE];
+        px.rgba.b = tuple0[PAM_BLU_PLANE];
+        px.rgba.a = 255;
+        break;
+    case RGBAlpha:
+        px.rgba.r = sampleMap[tuple0[PAM_RED_PLANE]];
+        px.rgba.g = sampleMap[tuple0[PAM_GRN_PLANE]];
+        px.rgba.b = sampleMap[tuple0[PAM_BLU_PLANE]];
+        px.rgba.a = sampleMap[tuple0[PAM_TRN_PLANE]];
+        break;
+    case RGB255Alpha:
+        px.rgba.r = tuple0[PAM_RED_PLANE];
+        px.rgba.g = tuple0[PAM_GRN_PLANE];
+        px.rgba.b = tuple0[PAM_BLU_PLANE];
+        px.rgba.a = tuple0[PAM_TRN_PLANE];
+        break;
+    case BW:
+    case GRAY : {
+        unsigned char const qoiSample = sampleMap[tuple0[0]];
+        px.rgba.r = qoiSample;
+        px.rgba.g = qoiSample;
+        px.rgba.b = qoiSample;
+        px.rgba.a = 255;
+    } break;
+    case GRAY255: {
+        unsigned char const qoiSample = tuple0[0];
+        px.rgba.r = qoiSample;
+        px.rgba.g = qoiSample;
+        px.rgba.b = qoiSample;
+        px.rgba.a = 255;
+    } break;
+    case BWAlpha:
+    case GRAYAlpha: {
+        unsigned char const qoiSample = sampleMap[tuple0[0]];
+        px.rgba.r = qoiSample;
+        px.rgba.g = qoiSample;
+        px.rgba.b = qoiSample;
+        px.rgba.a = sampleMap[tuple0[PAM_GRAY_TRN_PLANE]];
+    } break;
+    case GRAY255Alpha: {
+        unsigned char const qoiSample = tuple0[0];
+        px.rgba.r = qoiSample;
+        px.rgba.g = qoiSample;
+        px.rgba.b = qoiSample;
+        px.rgba.a = tuple0[PAM_GRAY_TRN_PLANE];
+    } break;
+    }
+
+    return px;
+}
+
+
+
+static void
+encodeNewPixel(qoi_Rgba const px,
+               qoi_Rgba const pxPrev) {
+
+    if (px.rgba.a == pxPrev.rgba.a) {
+        signed char const vr = px.rgba.r - pxPrev.rgba.r;
+        signed char const vg = px.rgba.g - pxPrev.rgba.g;
+        signed char const vb = px.rgba.b - pxPrev.rgba.b;
+
+        signed char const vgR = vr - vg;
+        signed char const vgB = vb - vg;
+
+        if (
+            vr > -3 && vr < 2 &&
+            vg > -3 && vg < 2 &&
+            vb > -3 && vb < 2
+            ) {
+            putchar(QOI_OP_DIFF |
+                    (vr + 2) << 4 | (vg + 2) << 2 | (vb + 2));
+        } else if (
+            vgR >  -9 && vgR <  8 &&
+            vg  > -33 && vg  < 32 &&
+            vgB >  -9 && vgB <  8
+            ) {
+            putchar(QOI_OP_LUMA    | (vg   + 32));
+            putchar((vgR + 8) << 4 | (vgB +  8));
+        } else {
+            putchar(QOI_OP_RGB);
+            putchar(px.rgba.r);
+            putchar(px.rgba.g);
+            putchar(px.rgba.b);
+        }
+    } else {
+        putchar(QOI_OP_RGBA);
+        putchar(px.rgba.r);
+        putchar(px.rgba.g);
+        putchar(px.rgba.b);
+        putchar(px.rgba.a);
+    }
+}
+
+
+
+static void
+qoiEncode(FILE           * const ifP,
+          struct pam     * const inpamP) {
+
+    tuple * tuplerow;
+    unsigned int row;
+
+    qoi_Rgba index[QOI_INDEX_SIZE];
+    qoi_Rgba pxPrev;
+    unsigned int run;
+    sample * sampleMap;
+    qoi_Desc qoiDesc;
+
+    enum Tupletype const tupleType =
+      tupleTypeFmPam(inpamP->tuple_type, inpamP->maxval);
+
+    if (inpamP->height > QOI_PIXELS_MAX / inpamP->width)
+        pm_error("Too many pixels for QOI: %u x %u (max is %u)",
+                 inpamP->height, inpamP->width, QOI_PIXELS_MAX);
+
+    qoiDesc.colorspace = QOI_SRGB;
+    qoiDesc.width      = inpamP->width;
+    qoiDesc.height     = inpamP->height;
+    qoiDesc.channelCt  = channelCtFmTupleType(tupleType);
+
+    encodeQoiHeader(qoiDesc);
+
+    tuplerow = pnm_allocpamrow(inpamP);
+
+    if (inpamP->maxval != 255)
+        createSampleMap(inpamP->maxval, &sampleMap);
+
+    qoi_clearQoiIndex(index);
+
+    pxPrev.rgba.r = 0;
+    pxPrev.rgba.g = 0;
+    pxPrev.rgba.b = 0;
+    pxPrev.rgba.a = 255;
+
+    /* Read and convert rows. */
+    for (row = 0, run = 0; row < inpamP->height; ++row) {
+        unsigned int col;
+
+        pnm_readpamrow(inpamP, tuplerow);
+
+        for (col = 0; col < inpamP->width; ++col) {
+            qoi_Rgba const px = pxFmTuple(tuplerow[col], sampleMap, tupleType);
+
+            if (px.v == pxPrev.v) {
+                ++run;
+                if (run == 62) {
+                    putchar(QOI_OP_RUN | (run - 1));
+                    run = 0;
+                }
+            } else {
+                unsigned int const indexPos = qoi_colorHash(px);
+
+                if (run > 0) {
+                    putchar(QOI_OP_RUN | (run - 1));
+                    run = 0;
+                }
+
+                if (index[indexPos].v == px.v) {
+                    putchar(QOI_OP_INDEX | indexPos);
+
+                } else {
+                    index[indexPos] = px;
+                    encodeNewPixel(px, pxPrev);
+                }
+            }
+            pxPrev = px;
+        }
+    }
+
+    if (run > 0)
+        putchar(QOI_OP_RUN | (run - 1));
+
+    fwrite(qoi_padding, sizeof(qoi_padding), 1, stdout);
+
+    if (inpamP->maxval != 255)
+        free(sampleMap);
+    pnm_freepamrow(tuplerow);
+}
+
+
+
+int
+main(int argc, const char **argv) {
+
+    struct CmdlineInfo cmdline;
+    struct pam inpam;
+    FILE * ifP;
+
+    pm_proginit(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    ifP = pm_openr(cmdline.inputFileName);
+
+    pnm_readpaminit(ifP, &inpam, PAM_STRUCT_SIZE(tuple_type));
+
+    qoiEncode(ifP, &inpam);
+
+    return 0;
+}
+
+
diff --git a/converter/other/qoi.h b/converter/other/qoi.h
new file mode 100644
index 00000000..52ee95e2
--- /dev/null
+++ b/converter/other/qoi.h
@@ -0,0 +1,101 @@
+#ifndef QOI_H_INCLUDED
+#define QOI_H_INCLUDED
+/*
+
+QOI - The "Quite OK Image" format for fast, lossless image compression
+
+Dominic Szablewski - https://phoboslab.org
+
+
+-- LICENSE: The MIT License(MIT)
+
+Copyright(c) 2021 Dominic Szablewski
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files(the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and / or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions :
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+*/
+
+
+typedef enum {
+    QOI_SRGB = 0,
+    QOI_LINEAR = 1
+} qoi_Colorspace;
+
+
+typedef struct {
+    unsigned int   width;
+    unsigned int   height;
+    unsigned int   channelCt;
+    qoi_Colorspace colorspace;
+} qoi_Desc;
+
+
+
+#define QOI_OP_INDEX  0x00 /* 00xxxxxx */
+#define QOI_OP_DIFF   0x40 /* 01xxxxxx */
+#define QOI_OP_LUMA   0x80 /* 10xxxxxx */
+#define QOI_OP_RUN    0xc0 /* 11xxxxxx */
+#define QOI_OP_RGB    0xfe /* 11111110 */
+#define QOI_OP_RGBA   0xff /* 11111111 */
+
+#define QOI_MASK_2    0xc0 /* 11000000 */
+
+#define QOI_HEADER_SIZE 14
+
+/* 2GB is the max file size that this implementation can safely handle. We
+   guard against anything larger than that, assuming the worst case with 5
+   bytes per pixel, rounded down to a nice clean value. 400 million pixels
+   ought to be enough for anybody.
+ */
+#define QOI_PIXELS_MAX 400000000
+
+static unsigned int const qoi_pixels_max = (unsigned int) QOI_PIXELS_MAX;
+
+#define QOI_MAXVAL 255
+
+#define QOI_INDEX_SIZE 64
+
+
+typedef union {
+    struct { unsigned char r, g, b, a; } rgba;
+    unsigned int v;
+} qoi_Rgba;
+
+static __inline__ unsigned int
+qoi_colorHash(qoi_Rgba const x) {
+
+    return
+        (x.rgba.r*3 + x.rgba.g*5 + x.rgba.b*7 + x.rgba.a*11) % QOI_INDEX_SIZE;
+}
+
+static __inline__ void
+qoi_clearQoiIndex(qoi_Rgba * index) {
+
+    memset(index, 0, QOI_INDEX_SIZE * sizeof(qoi_Rgba));
+
+}
+
+#define QOI_MAGIC_SIZE 4
+
+static char const qoi_magic[QOI_MAGIC_SIZE + 1] = {'q','o','i','f','\0'};
+
+#define QOI_PADDING_SIZE 8
+
+static unsigned char const qoi_padding[QOI_PADDING_SIZE] = {0,0,0,0,0,0,0,1};
+
+
+#endif
diff --git a/converter/other/qoitopam.c b/converter/other/qoitopam.c
new file mode 100644
index 00000000..af6817b7
--- /dev/null
+++ b/converter/other/qoitopam.c
@@ -0,0 +1,323 @@
+/*
+  qoitopam -  Converts from a QOI - The "Quite OK Image" format file to PAM
+
+  This program is part of Netpbm.
+
+  ---------------------------------------------------------------------
+
+
+  QOI - The "Quite OK Image" format for fast, lossless image compression
+
+  Decoder by Dominic Szablewski - https://phoboslab.org
+
+  -- LICENSE: The MIT License(MIT)
+
+  Copyright(c) 2021 Dominic Szablewski
+
+  Permission is hereby granted, free of charge, to any person obtaining a copy
+  of this software and associated documentation files(the "Software"), to deal
+  in the Software without restriction, including without limitation the rights
+  to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
+  copies of the Software, and to permit persons to whom the Software is
+  furnished to do so, subject to the following conditions :
+
+  The above copyright notice and this permission notice shall be included in
+  all copies or substantial portions of the Software.
+
+  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
+  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+  SOFTWARE.
+
+  For more information on the format visit: https//qoiformat.org/ .
+
+  Modifications for Netpbm & PAM write routines by Akira F. Urushibata.
+*/
+
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+
+#include "pm.h"
+#include "pam.h"
+#include "mallocvar.h"
+#include "nstring.h"
+#include "shhopt.h"
+
+#include "qoi.h"
+
+
+
+struct CmdlineInfo {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char * inputFileName;  /* '-' if stdin */
+};
+
+
+
+static void
+parseCommandLine(int                  argc,
+                 const char **        argv,
+                 struct CmdlineInfo * cmdlineP ) {
+/*----------------------------------------------------------------------------
+   Parse program command line described in Unix standard form by argc
+   and argv.  Return the information in the options as *cmdlineP.
+
+   If command line is internally inconsistent (invalid options, etc.),
+   issue error message to stderr and abort program.
+
+   Note that the strings we return are stored in the storage that
+   was passed to us as the argv array.  We also trash *argv.
+-----------------------------------------------------------------------------*/
+    optEntry * option_def;
+        /* Instructions to pm_optParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+
+    MALLOCARRAY(option_def, 100);
+
+    OPTENTINIT;
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
+
+    pm_optParseOptions3(&argc, (char **)argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
+
+    if (argc-1 < 1)
+        cmdlineP->inputFileName = "-";
+    else if (argc-1 == 1)
+        cmdlineP->inputFileName = argv[1];
+    else
+        pm_error("Program takes at most one argument: input file name.  "
+            "you specified %d", argc-1);
+}
+
+
+
+static void
+readAndValidateMagic(FILE * const ifP){
+
+    char magicBuff[QOI_MAGIC_SIZE];
+    size_t charsReadCt;
+
+    charsReadCt = fread(magicBuff, 1, QOI_MAGIC_SIZE, ifP);
+
+    if (charsReadCt == 0)
+        pm_error("Input file is empty.");
+    else if (charsReadCt < QOI_MAGIC_SIZE || !MEMSEQ(&magicBuff, &qoi_magic)) {
+        assert(QOI_MAGIC_SIZE == 4);
+        pm_error("Invalid QOI image: does not start with magic number "
+                 "'%c%c%c%c'",
+                 qoi_magic[0], qoi_magic[1], qoi_magic[2], qoi_magic[3]);
+    }
+}
+
+
+/* The following two functions are from lib/pmfileio.c */
+
+static void
+abortWithReadError(FILE * const ifP) {
+
+    if (feof(ifP))
+        pm_error("Unexpected end of input file");
+    else
+        pm_error("Error (not EOF) reading file.");
+}
+
+
+
+static unsigned char
+getcNofail(FILE * const ifP) {
+
+    int c;
+
+    c = getc(ifP);
+
+    if (c == EOF)
+        abortWithReadError(ifP);
+
+    return (unsigned char) c;
+}
+
+
+
+static void
+decodeQoiHeader(FILE *     const ifP,
+                qoi_Desc * const qoiDescP) {
+
+    unsigned long int width, height;
+
+    readAndValidateMagic(ifP);
+
+    pm_readbiglongu(ifP, &width);
+    if (width == 0)
+        pm_error("Invalid QOI image: width is zero");
+    else
+        qoiDescP->width = width;
+
+    pm_readbiglongu(ifP, &height);
+    if (height == 0)
+        pm_error("Invalid QOI image: height is zero");
+    else if (height > QOI_PIXELS_MAX / width)
+        pm_error ("Invalid QOI image: %u x %u is more than %u pixels",
+                  (unsigned int) width, (unsigned int) height, QOI_PIXELS_MAX);
+    else
+        qoiDescP->height = height;
+
+    qoiDescP->channelCt = getcNofail(ifP);
+    if (qoiDescP->channelCt != 3 && qoiDescP->channelCt != 4)
+        pm_error("Invalid QOI image: channel count is %u.  "
+                 "Only 3 and 4 are valid", qoiDescP->channelCt);
+
+    qoiDescP->colorspace = getcNofail(ifP);
+    if (qoiDescP->colorspace != QOI_SRGB && qoiDescP->colorspace != QOI_LINEAR)
+        pm_error("Invalid QOI image: colorspace code is %u.  "
+                 "Only %u (SRGB) and %u (LINEAR) are valid",
+                 qoiDescP->colorspace, QOI_SRGB, QOI_LINEAR);
+}
+
+
+
+static void
+qoiDecode(FILE *       const ifP,
+          qoi_Desc *   const qoiDescP,
+          struct pam * const outpamP) {
+
+    qoi_Rgba index[QOI_INDEX_SIZE];
+    unsigned int row;
+    qoi_Rgba px;
+    unsigned int run;
+    tuple * tuplerow;
+
+    assert(qoiDescP);
+    tuplerow = pnm_allocpamrow(outpamP);
+
+    qoi_clearQoiIndex(index);
+    px.rgba.r = px.rgba.g = px.rgba.b = 0;
+    px.rgba.a = 255;
+
+    for (row = 0, run = 0; row < outpamP->height; ++row) {
+        unsigned int col;
+
+        for (col = 0; col < outpamP->width; ++col) {
+            if (run > 0) {
+                 --run;
+            } else {
+                unsigned char const b1 = getcNofail(ifP);
+
+                if (b1 == QOI_OP_RGB) {
+                    px.rgba.r = getcNofail(ifP);
+                    px.rgba.g = getcNofail(ifP);
+                    px.rgba.b = getcNofail(ifP);
+                } else if (b1 == QOI_OP_RGBA) {
+                    px.rgba.r = getcNofail(ifP);
+                    px.rgba.g = getcNofail(ifP);
+                    px.rgba.b = getcNofail(ifP);
+                    px.rgba.a = getcNofail(ifP);
+                } else if ((b1 & QOI_MASK_2) == QOI_OP_INDEX) {
+                  /* Official spec says 2 or more consecutive instances of
+                     QOI_OP_INDEX are not allowed, but we don't check */
+                    px = index[b1];
+                } else if ((b1 & QOI_MASK_2) == QOI_OP_DIFF) {
+                    px.rgba.r += ((b1 >> 4) & 0x03) - 2;
+                    px.rgba.g += ((b1 >> 2) & 0x03) - 2;
+                    px.rgba.b += ( b1       & 0x03) - 2;
+                } else if ((b1 & QOI_MASK_2) == QOI_OP_LUMA) {
+                    unsigned char const b2 = getcNofail(ifP);
+                    unsigned char const vg = (b1 & 0x3f) - 32;
+                    px.rgba.r += vg - 8 + ((b2 >> 4) & 0x0f);
+                    px.rgba.g += vg;
+                    px.rgba.b += vg - 8 +  (b2       & 0x0f);
+                } else if ((b1 & QOI_MASK_2) == QOI_OP_RUN) {
+                    run = (b1 & 0x3f);
+                }
+                /* register pixel in hash lookup array */
+                index[qoi_colorHash(px)] = px;
+            }
+            tuplerow[col][PAM_RED_PLANE] = px.rgba.r;
+            tuplerow[col][PAM_GRN_PLANE] = px.rgba.g;
+            tuplerow[col][PAM_BLU_PLANE] = px.rgba.b;
+            if (qoiDescP->channelCt == 4)
+                tuplerow[col][PAM_TRN_PLANE] = px.rgba.a;
+        }
+        pnm_writepamrow(outpamP, tuplerow);
+    }
+    if (run > 0)
+        pm_error("Invalid QOI image: %u (or more) extra pixels "
+                 "beyond end of image.", run);
+
+    pnm_freepamrow(tuplerow);
+}
+
+
+
+static void
+readAndValidatePadding(FILE * const ifP){
+
+    unsigned char padBuff[QOI_PADDING_SIZE];
+    size_t charsReadCt;
+
+    charsReadCt = fread(padBuff, 1, QOI_PADDING_SIZE, ifP);
+
+    if(charsReadCt < QOI_PADDING_SIZE) {
+        pm_error("Invalid QOI image.  Error reading final 8-byte padding.  "
+                 "Premature end of file.");
+    } else if (!MEMSEQ(&padBuff, &qoi_padding))
+        pm_error("Invalid QOI image.  Final 8-byte padding incorrect.");
+    else if (fgetc(ifP) != EOF)
+        pm_error("Invalid QOI image.  "
+                 "Extraneous bytes after final 8-byte padding.");
+}
+
+
+
+int
+main(int argc, const char **argv) {
+
+    struct CmdlineInfo cmdline;
+    qoi_Desc qoiDesc;
+    struct pam outpam;
+    FILE * ifP;
+
+    pm_proginit(&argc, argv);
+
+    parseCommandLine(argc, argv, &cmdline);
+
+    ifP = pm_openr(cmdline.inputFileName);
+
+    outpam.size        = sizeof(struct pam);
+    outpam.len         = PAM_STRUCT_SIZE(tuple_type);
+    outpam.maxval      = QOI_MAXVAL;
+    outpam.plainformat = 0;
+
+    decodeQoiHeader(ifP, &qoiDesc);
+
+    outpam.depth  = qoiDesc.channelCt == 3 ? 3 : 4;
+    outpam.width  = qoiDesc.width;
+    outpam.height = qoiDesc.height;
+    outpam.format = PAM_FORMAT;
+    outpam.file   = stdout;
+
+    if (qoiDesc.channelCt == 3)
+        strcpy(outpam.tuple_type, PAM_PPM_TUPLETYPE);
+    else
+        strcpy(outpam.tuple_type, PAM_PPM_ALPHA_TUPLETYPE);
+
+    pnm_writepaminit(&outpam);
+    qoiDecode(ifP, &qoiDesc, &outpam);
+
+    readAndValidatePadding(ifP);
+
+    return 0;
+}
+
+
diff --git a/converter/other/winicontopam.c b/converter/other/winicontopam.c
index f6f89e11..bb39bf60 100644
--- a/converter/other/winicontopam.c
+++ b/converter/other/winicontopam.c
@@ -1273,7 +1273,7 @@ convertImage(struct File *         const icoP,
 
     image = readImage(icoP, dirEntryP);
 
-    if (MEMEQ(image, pngSignature, sizeof (pngSignature)))
+    if (memeq(image, pngSignature, sizeof (pngSignature)))
         convertPng(image, ofP, dirEntryP);
     else
         convertBmp(image, ofP, dirEntryP, needHeaderDump, wantAndMaskPlane);