diff options
author | giraffedata <giraffedata@9d0c8265-081b-0410-96cb-a4ca84ce46f8> | 2022-06-08 20:27:23 +0000 |
---|---|---|
committer | giraffedata <giraffedata@9d0c8265-081b-0410-96cb-a4ca84ce46f8> | 2022-06-08 20:27:23 +0000 |
commit | e865b4cd3c9092f8d8adfadd3a9c9a9843d507a2 (patch) | |
tree | 75ce815091c5fe9833807f55dff9c1486b0c11b1 | |
parent | 88891c17336b2f7d6ea3f720eaaac40940a57d1f (diff) | |
download | netpbm-mirror-e865b4cd3c9092f8d8adfadd3a9c9a9843d507a2.tar.gz netpbm-mirror-e865b4cd3c9092f8d8adfadd3a9c9a9843d507a2.tar.xz netpbm-mirror-e865b4cd3c9092f8d8adfadd3a9c9a9843d507a2.zip |
Accept input filename argument; don't use intermediate buffer or generic QOI module
git-svn-id: http://svn.code.sf.net/p/netpbm/code/trunk@4352 9d0c8265-081b-0410-96cb-a4ca84ce46f8
-rw-r--r-- | converter/other/Makefile | 7 | ||||
-rw-r--r-- | converter/other/pamtoqoi.c | 493 | ||||
-rw-r--r-- | converter/other/qoi.c | 365 | ||||
-rw-r--r-- | converter/other/qoi.h | 269 | ||||
-rw-r--r-- | converter/other/qoitopam.c | 309 |
5 files changed, 677 insertions, 766 deletions
diff --git a/converter/other/Makefile b/converter/other/Makefile index b1f8461a..3b3b6aa0 100644 --- a/converter/other/Makefile +++ b/converter/other/Makefile @@ -169,7 +169,7 @@ BINARIES = $(PORTBINARIES) MERGEBINARIES = $(BINARIES) -EXTRA_OBJECTS = exif.o rast.o ipdb.o srf.o qoi.o +EXTRA_OBJECTS = exif.o rast.o ipdb.o srf.o ifeq ($(HAVE_PNGLIB),Y) EXTRA_OBJECTS += pngtxt.o EXTRA_OBJECTS += pngx.o @@ -239,11 +239,6 @@ jpegtopnm: jpegdatasource.o exif.o jpegtopnm: ADDL_OBJECTS = jpegdatasource.o exif.o jpegtopnm: LDFLAGS_TARGET = $(shell $(LIBOPT) $(LIBOPTR) $(JPEGLIB)) -pamtoqoi: pamtoqoi.o qoi.o -pamtoqoi: ADDL_OBJECTS = qoi.o -qoitopam: pamtoqoi.o qoi.o -qoitopam: ADDL_OBJECTS = qoi.o - srftopam pamtosrf: srf.o srftopam pamtosrf: ADDL_OBJECTS = srf.o diff --git a/converter/other/pamtoqoi.c b/converter/other/pamtoqoi.c index 6bad6a90..638efa3a 100644 --- a/converter/other/pamtoqoi.c +++ b/converter/other/pamtoqoi.c @@ -1,184 +1,439 @@ /* - * This file is part of Netpbm (http://netpbm.sourceforge.org). - * Copyright (c) 2022 cancername. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ + 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" -#define QOI_MAXVAL 0xFF -/* tuple row to qoi row */ -typedef void Trqr(const tuplen * const tuplerown, - unsigned int const width, - unsigned char * const qoiRow); -static Trqr trqrRgb; +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 -trqrRgb(const tuplen * const tuplerown, - unsigned int const cols, - unsigned char * const qoiRow) { - size_t qoiRowCursor; - unsigned int col; - for (col = 0, qoiRowCursor = 0; col < cols; ++col) { - qoiRow[qoiRowCursor++] = tuplerown[col][PAM_RED_PLANE] * QOI_MAXVAL; - qoiRow[qoiRowCursor++] = tuplerown[col][PAM_GRN_PLANE] * QOI_MAXVAL; - qoiRow[qoiRowCursor++] = tuplerown[col][PAM_BLU_PLANE] * QOI_MAXVAL; - } +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 Trqr trqrRgba; - static void -trqrRgba(const tuplen * const tuplerown, - unsigned int const cols, - unsigned char * const qoiRow) { - - size_t qoiRowCursor; - unsigned int col; - - for (col = 0, qoiRowCursor = 0; col < cols; ++col) { - qoiRow[qoiRowCursor++] = tuplerown[col][PAM_RED_PLANE] * QOI_MAXVAL; - qoiRow[qoiRowCursor++] = tuplerown[col][PAM_GRN_PLANE] * QOI_MAXVAL; - qoiRow[qoiRowCursor++] = tuplerown[col][PAM_BLU_PLANE] * QOI_MAXVAL; - qoiRow[qoiRowCursor++] = tuplerown[col][PAM_TRN_PLANE] * QOI_MAXVAL; - } +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 Trqr trqrGray; static void -trqrGray(const tuplen * const tuplerown, - unsigned int const cols, - unsigned char * const qoiRow) { +createSampleMap(sample const oldMaxval, + sample ** const sampleMapP) { - size_t qoiRowCursor; - unsigned int col; + unsigned int i; + sample * sampleMap; + sample const newMaxval = 255; - for (col = 0, qoiRowCursor = 0; col < cols; ++col) { - unsigned char const qoiSample = tuplerown[col][0] * QOI_MAXVAL; + MALLOCARRAY_NOFAIL(sampleMap, oldMaxval+1); - qoiRow[qoiRowCursor++] = qoiSample; - qoiRow[qoiRowCursor++] = qoiSample; - qoiRow[qoiRowCursor++] = qoiSample; - } + for (i = 0; i <= oldMaxval; ++i) + sampleMap[i] = ROUNDDIV(i * newMaxval, oldMaxval); + + *sampleMapP = sampleMap; } -static Trqr trqrGrayAlpha; +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); + } -static void -trqrGrayAlpha(const tuplen * const tuplerown, - unsigned int const cols, - unsigned char * const qoiRow) { + return retval; +} - size_t qoiRowCursor; - unsigned int col; - for (col = 0, qoiRowCursor = 0; col < cols; ++col) { - unsigned char const qoiSample = tuplerown[col][0] * QOI_MAXVAL; - qoiRow[qoiRowCursor++] = qoiSample; - qoiRow[qoiRowCursor++] = qoiSample; - qoiRow[qoiRowCursor++] = qoiSample; - qoiRow[qoiRowCursor++] = - tuplerown[col][PAM_GRAY_TRN_PLANE] * QOI_MAXVAL; +#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 Trqr * -trqrForTupleType(const char * const tupleType) { - if (streq(tupleType, PAM_PPM_TUPLETYPE)) - return &trqrRgb; - else if(streq(tupleType, PAM_PPM_ALPHA_TUPLETYPE)) - return &trqrRgba; - else if(streq(tupleType, PAM_PBM_TUPLETYPE) || - streq(tupleType, PAM_PGM_TUPLETYPE)) - return &trqrGray; - else if(streq(tupleType, PAM_PBM_ALPHA_TUPLETYPE) || - streq(tupleType, PAM_PGM_ALPHA_TUPLETYPE)) - return &trqrGrayAlpha; - else { - pm_error("Don't know how to convert tuple type '%s'.", tupleType); - return NULL; /* Suppress compiler warning */ +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); } } -int -main(int argc, char **argv) { +static void +qoiEncode(FILE * const ifP, + struct pam * const inpamP) { - struct pam inpam; - Trqr * trqr; - qoi_Desc qoiDesc; - tuplen * tuplerown; - unsigned char * qoiRaster; - const unsigned char * qoiImage; - size_t qoiSz; + tuple * tuplerow; unsigned int row; - pm_proginit(&argc, (const char **)argv); + qoi_Rgba index[QOI_INDEX_SIZE]; + qoi_Rgba pxPrev; + unsigned int run; + sample * sampleMap; + qoi_Desc qoiDesc; - pnm_readpaminit(stdin, &inpam, PAM_STRUCT_SIZE(tuple_type)); + enum Tupletype const tupleType = + tupleTypeFmPam(inpamP->tuple_type, inpamP->maxval); - tuplerown = pnm_allocpamrown(&inpam); + 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 = inpam.width; - qoiDesc.height = inpam.height; - qoiDesc.channelCt = inpam.depth <= 3 ? 3 : 4; + qoiDesc.width = inpamP->width; + qoiDesc.height = inpamP->height; + qoiDesc.channelCt = channelCtFmTupleType(tupleType); + + encodeQoiHeader(qoiDesc); - qoiRaster = malloc(qoiDesc.width * qoiDesc.height * qoiDesc.channelCt); + tuplerow = pnm_allocpamrow(inpamP); - if (!qoiRaster) - pm_error("Unable to get memory for QOI raster %u x %u x %u", - qoiDesc.width, qoiDesc.height, qoiDesc.channelCt); + if (inpamP->maxval != 255) + createSampleMap(inpamP->maxval, &sampleMap); - trqr = trqrForTupleType(inpam.tuple_type); + 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; row < inpam.height; ++row) { - pnm_readpamrown(&inpam, tuplerown); - trqr(tuplerown, - inpam.width, - &qoiRaster[row * inpam.width * qoiDesc.channelCt]); + 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; + } } - qoi_encode(qoiRaster, &qoiDesc, &qoiImage, &qoiSz); - pm_writefile(stdout, qoiImage, qoiSz); + if (run > 0) + putchar(QOI_OP_RUN | (run - 1)); - free((void*)qoiImage); - free(qoiRaster); - pnm_freepamrown(tuplerown); + fwrite(qoi_padding, sizeof(qoi_padding), 1, stdout); - return 0; + 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.c b/converter/other/qoi.c deleted file mode 100644 index 4fbd7570..00000000 --- a/converter/other/qoi.c +++ /dev/null @@ -1,365 +0,0 @@ -/* - -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. - -*/ - - -#include <assert.h> -#include <stdlib.h> -#include <string.h> - -#include "pm.h" -#include "mallocvar.h" -#include "qoi.h" - -#define ZEROARRAY(a) memset((a),0,sizeof(a)) - -#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_COLOR_HASH(C) (C.rgba.r*3 + C.rgba.g*5 + C.rgba.b*7 + C.rgba.a*11) -#define QOI_MAGIC \ - (((unsigned int)'q') << 24 | ((unsigned int)'o') << 16 | \ - ((unsigned int)'i') << 8 | ((unsigned int)'f')) -#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 ((unsigned int)400000000) - -typedef union { - struct { unsigned char r, g, b, a; } rgba; - unsigned int v; -} Rgba; - -static unsigned char const padding[8] = {0,0,0,0,0,0,0,1}; - -static void -write32(unsigned char * const bytes, - size_t * const cursorP, - unsigned int const v) { - - bytes[(*cursorP)++] = (0xff000000 & v) >> 24; - bytes[(*cursorP)++] = (0x00ff0000 & v) >> 16; - bytes[(*cursorP)++] = (0x0000ff00 & v) >> 8; - bytes[(*cursorP)++] = (0x000000ff & v); -} - - - -static unsigned int -read32(const unsigned char * const bytes, - size_t * const cursorP) { - - unsigned int a = bytes[(*cursorP)++]; - unsigned int b = bytes[(*cursorP)++]; - unsigned int c = bytes[(*cursorP)++]; - unsigned int d = bytes[(*cursorP)++]; - - return a << 24 | b << 16 | c << 8 | d; -} - - - -static void -encodeQoiHeader(unsigned char * const bytes, - qoi_Desc const qoiDesc, - size_t * const cursorP) { - - write32(bytes, cursorP, QOI_MAGIC); - write32(bytes, cursorP, qoiDesc.width); - write32(bytes, cursorP, qoiDesc.height); - bytes[(*cursorP)++] = qoiDesc.channelCt; - bytes[(*cursorP)++] = qoiDesc.colorspace; -} - - - -static void -encodeNewPixel(Rgba const px, - Rgba const pxPrev, - unsigned char * const bytes, - size_t * const cursorP) { - - 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 - ) { - bytes[(*cursorP)++] = 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 - ) { - bytes[(*cursorP)++] = QOI_OP_LUMA | (vg + 32); - bytes[(*cursorP)++] = (vgR + 8) << 4 | (vgB + 8); - } else { - bytes[(*cursorP)++] = QOI_OP_RGB; - bytes[(*cursorP)++] = px.rgba.r; - bytes[(*cursorP)++] = px.rgba.g; - bytes[(*cursorP)++] = px.rgba.b; - } - } else { - bytes[(*cursorP)++] = QOI_OP_RGBA; - bytes[(*cursorP)++] = px.rgba.r; - bytes[(*cursorP)++] = px.rgba.g; - bytes[(*cursorP)++] = px.rgba.b; - bytes[(*cursorP)++] = px.rgba.a; - } -} - - - -void -qoi_encode(const unsigned char * const pixels, - const qoi_Desc * const descP, - const unsigned char ** const qoiImageP, - size_t * const outLenP) { - - size_t cursor; - unsigned int i, maxSize, run; - unsigned int pxLen, pxEnd, pxPos; - unsigned char * bytes; - Rgba index[64]; - Rgba px, pxPrev; - - assert(pixels); - assert(descP); - assert(outLenP); - assert(descP->width > 0); - assert(descP->height > 0); - assert(descP->channelCt >= 3 && descP->channelCt <= 4); - assert(descP->colorspace == QOI_SRGB || descP->colorspace == QOI_LINEAR); - - if (descP->height >= QOI_PIXELS_MAX / descP->width) - pm_error("Too many pixles for OQI: %u x %u (max is %u", - descP->height, descP->width, QOI_PIXELS_MAX); - - maxSize = - descP->width * descP->height * (descP->channelCt + 1) + - QOI_HEADER_SIZE + sizeof(padding); - - MALLOCARRAY(bytes, maxSize); - if (!bytes) - pm_error("Cannot allocate %u bytes", maxSize); - - cursor = 0; - - encodeQoiHeader(bytes, *descP, &cursor); - - ZEROARRAY(index); - - run = 0; - pxPrev.rgba.r = 0; - pxPrev.rgba.g = 0; - pxPrev.rgba.b = 0; - pxPrev.rgba.a = 255; - px = pxPrev; - - pxLen = descP->width * descP->height * descP->channelCt; - pxEnd = pxLen - descP->channelCt; - - for (pxPos = 0; pxPos < pxLen; pxPos += descP->channelCt) { - px.rgba.r = pixels[pxPos + 0]; - px.rgba.g = pixels[pxPos + 1]; - px.rgba.b = pixels[pxPos + 2]; - - if (descP->channelCt == 4) { - px.rgba.a = pixels[pxPos + 3]; - } - - if (px.v == pxPrev.v) { - ++run; - if (run == 62 || pxPos == pxEnd) { - bytes[cursor++] = QOI_OP_RUN | (run - 1); - run = 0; - } - } else { - unsigned int const indexPos = QOI_COLOR_HASH(px) % 64; - - if (run > 0) { - bytes[cursor++] = QOI_OP_RUN | (run - 1); - run = 0; - } - - if (index[indexPos].v == px.v) { - bytes[cursor++] = QOI_OP_INDEX | indexPos; - } else { - index[indexPos] = px; - - encodeNewPixel(px, pxPrev, bytes, &cursor); - } - } - pxPrev = px; - } - - for (i = 0; i < sizeof(padding); ++i) - bytes[cursor++] = padding[i]; - - *qoiImageP = bytes; - *outLenP = cursor; -} - - - -static void -decodeQoiHeader(const unsigned char * const qoiImage, - qoi_Desc * const qoiDescP, - size_t * const cursorP) { - - unsigned int headerMagic; - - headerMagic = read32(qoiImage, cursorP); - qoiDescP->width = read32(qoiImage, cursorP); - qoiDescP->height = read32(qoiImage, cursorP); - qoiDescP->channelCt = qoiImage[(*cursorP)++]; - qoiDescP->colorspace = qoiImage[(*cursorP)++]; - - if (qoiDescP->width == 0) - pm_error("Invalid QOI image: width is zero"); - if (qoiDescP->height == 0) - pm_error("Invalid QOI image: height is zero"); - if (qoiDescP->channelCt != 3 && qoiDescP->channelCt != 4) - pm_error("Invalid QOI image: channel count is %u. " - "Only 3 and 4 are valid", qoiDescP->channelCt); - 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); - if (headerMagic != QOI_MAGIC) - pm_error("Invalid QOI image: Where the magic number 0x%04x " - "should be, there is 0x%04x", - QOI_MAGIC, headerMagic); - if (qoiDescP->height >= QOI_PIXELS_MAX / qoiDescP->width) - pm_error ("Invalid QOI image: %u x %u is More than %u pixels", - qoiDescP->width, qoiDescP->height, QOI_PIXELS_MAX); -} - - - -void -qoi_decode(const unsigned char * const qoiImage, - size_t const size, - qoi_Desc * const qoiDescP, - const unsigned char ** const qoiRasterP) { - - unsigned int const chunksLen = size - sizeof(padding); - - unsigned char * pixels; - Rgba index[64]; - Rgba px; - unsigned int pxLen, pxPos; - size_t cursor; - unsigned int run; - - assert(qoiImage); - assert(qoiDescP); - assert(size >= QOI_HEADER_SIZE + sizeof(padding)); - - cursor = 0; - - decodeQoiHeader(qoiImage, qoiDescP, &cursor); - - pxLen = qoiDescP->width * qoiDescP->height * qoiDescP->channelCt; - MALLOCARRAY(pixels, pxLen); - if (!pixels) - pm_error("Failed to allocate %u bytes for %u x %u x %u QOI raster", - pxLen, - qoiDescP->width, qoiDescP->height, qoiDescP->channelCt); - - ZEROARRAY(index); - px.rgba.r = 0; - px.rgba.g = 0; - px.rgba.b = 0; - px.rgba.a = 255; - - for (pxPos = 0, run = 0; pxPos < pxLen; pxPos += qoiDescP->channelCt) { - if (run > 0) { - --run; - } else if (cursor < chunksLen) { - unsigned char const b1 = qoiImage[cursor++]; - - if (b1 == QOI_OP_RGB) { - px.rgba.r = qoiImage[cursor++]; - px.rgba.g = qoiImage[cursor++]; - px.rgba.b = qoiImage[cursor++]; - } else if (b1 == QOI_OP_RGBA) { - px.rgba.r = qoiImage[cursor++]; - px.rgba.g = qoiImage[cursor++]; - px.rgba.b = qoiImage[cursor++]; - px.rgba.a = qoiImage[cursor++]; - } else if ((b1 & QOI_MASK_2) == QOI_OP_INDEX) { - 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 = qoiImage[cursor++]; - 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); - } - - index[QOI_COLOR_HASH(px) % 64] = px; - } - - pixels[pxPos + 0] = px.rgba.r; - pixels[pxPos + 1] = px.rgba.g; - pixels[pxPos + 2] = px.rgba.b; - - if (qoiDescP->channelCt == 4) - pixels[pxPos + 3] = px.rgba.a; - } - - *qoiRasterP = pixels; -} - - - diff --git a/converter/other/qoi.h b/converter/other/qoi.h index 2886a777..52ee95e2 100644 --- a/converter/other/qoi.h +++ b/converter/other/qoi.h @@ -27,254 +27,75 @@ 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. +*/ --- About - -QOI encodes and decodes images in a lossless format. Compared to stb_image and -stb_image_write QOI offers 20x-50x faster encoding, 3x-4x faster decoding and -20% better compression. - - --- Synopsis - -// Encode and store an RGBA buffer to the file system. The qoi_desc describes -// the input pixel data. -qoi_write("image_new.qoi", rgba_pixels, &(qoi_desc){ - .width = 1920, - .height = 1080, - .channels = 4, - .colorspace = QOI_SRGB -}); - -// Load and decode a QOI image from the file system into a 32bbp RGBA buffer. -// The qoi_desc struct will be filled with the width, height, number of channels -// and colorspace read from the file header. -qoi_desc desc; -void *rgba_pixels = qoi_read("image.qoi", &desc, 4); - - - --- Documentation - -This library provides the following functions; -- qoi_read -- read and decode a QOI file -- qoi_decode -- decode the raw bytes of a QOI image from memory -- qoi_write -- encode and write a QOI file -- qoi_encode -- encode an rgba buffer into a QOI image in memory - -See the function declaration below for the signature and more information. - -This library uses malloc() and free(). To supply your own malloc implementation -you can define QOI_MALLOC and QOI_FREE before including this library. - -This library uses memset() to zero-initialize the index. To supply your own -implementation you can define QOI_ZEROARR before including this library. - - --- Data Format - -A QOI file has a 14 byte header, followed by any number of data "chunks" and an -8-byte end marker. - -struct qoi_header_t { - char magic[4]; // magic bytes "qoif" - uint32_t width; // image width in pixels (BE) - uint32_t height; // image height in pixels (BE) - uint8_t channels; // 3 = RGB, 4 = RGBA - uint8_t colorspace; // 0 = sRGB with linear alpha, 1 = all channels linear -}; - -Images are encoded row by row, left to right, top to bottom. The decoder and -encoder start with {r: 0, g: 0, b: 0, a: 255} as the previous pixel value. An -image is complete when all pixels specified by width * height have been covered. - -Pixels are encoded as - - a run of the previous pixel - - an index into an array of previously seen pixels - - a difference to the previous pixel value in r,g,b - - full r,g,b or r,g,b,a values - -The color channels are assumed to not be premultiplied with the alpha channel -("un-premultiplied alpha"). - -A running array[64] (zero-initialized) of previously seen pixel values is -maintained by the encoder and decoder. Each pixel that is seen by the encoder -and decoder is put into this array at the position formed by a hash function of -the color value. In the encoder, if the pixel value at the index matches the -current pixel, this index position is written to the stream as QOI_OP_INDEX. -The hash function for the index is: - - index_position = (r * 3 + g * 5 + b * 7 + a * 11) % 64 - -Each chunk starts with a 2- or 8-bit tag, followed by a number of data bits. The -bit length of chunks is divisible by 8 - i.e. all chunks are byte aligned. All -values encoded in these data bits have the most significant bit on the left. - -The 8-bit tags have precedence over the 2-bit tags. A decoder must check for the -presence of an 8-bit tag first. - -The byte stream's end is marked with 7 0x00 bytes followed a single 0x01 byte. - - -The possible chunks are: - - -.- QOI_OP_INDEX ----------. -| Byte[0] | -| 7 6 5 4 3 2 1 0 | -|-------+-----------------| -| 0 0 | index | -`-------------------------` -2-bit tag b00 -6-bit index into the color index array: 0..63 - -A valid encoder must not issue 2 or more consecutive QOI_OP_INDEX chunks to the -same index. QOI_OP_RUN should be used instead. +typedef enum { + QOI_SRGB = 0, + QOI_LINEAR = 1 +} qoi_Colorspace; -.- QOI_OP_DIFF -----------. -| Byte[0] | -| 7 6 5 4 3 2 1 0 | -|-------+-----+-----+-----| -| 0 1 | dr | dg | db | -`-------------------------` -2-bit tag b01 -2-bit red channel difference from the previous pixel between -2..1 -2-bit green channel difference from the previous pixel between -2..1 -2-bit blue channel difference from the previous pixel between -2..1 -The difference to the current channel values are using a wraparound operation, -so "1 - 2" will result in 255, while "255 + 1" will result in 0. +typedef struct { + unsigned int width; + unsigned int height; + unsigned int channelCt; + qoi_Colorspace colorspace; +} qoi_Desc; -Values are stored as unsigned integers with a bias of 2. E.g. -2 is stored as -0 (b00). 1 is stored as 3 (b11). -The alpha value remains unchanged from the previous pixel. +#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 */ -.- QOI_OP_LUMA -------------------------------------. -| Byte[0] | Byte[1] | -| 7 6 5 4 3 2 1 0 | 7 6 5 4 3 2 1 0 | -|-------+-----------------+-------------+-----------| -| 1 0 | green diff | dr - dg | db - dg | -`---------------------------------------------------` -2-bit tag b10 -6-bit green channel difference from the previous pixel -32..31 -4-bit red channel difference minus green channel difference -8..7 -4-bit blue channel difference minus green channel difference -8..7 +#define QOI_MASK_2 0xc0 /* 11000000 */ -The green channel is used to indicate the general direction of change and is -encoded in 6 bits. The red and blue channels (dr and db) base their diffs off -of the green channel difference and are encoded in 4 bits. I.e.: - dr_dg = (cur_px.r - prev_px.r) - (cur_px.g - prev_px.g) - db_dg = (cur_px.b - prev_px.b) - (cur_px.g - prev_px.g) - -The difference to the current channel values are using a wraparound operation, -so "10 - 13" will result in 253, while "250 + 7" will result in 1. - -Values are stored as unsigned integers with a bias of 32 for the green channel -and a bias of 8 for the red and blue channel. - -The alpha value remains unchanged from the previous pixel. - - -.- QOI_OP_RUN ------------. -| Byte[0] | -| 7 6 5 4 3 2 1 0 | -|-------+-----------------| -| 1 1 | run | -`-------------------------` -2-bit tag b11 -6-bit run-length repeating the previous pixel: 1..62 - -The run-length is stored with a bias of -1. Note that the run-lengths 63 and 64 -(b111110 and b111111) are illegal as they are occupied by the QOI_OP_RGB and -QOI_OP_RGBA tags. +#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 -.- QOI_OP_RGB ------------------------------------------. -| Byte[0] | Byte[1] | Byte[2] | Byte[3] | -| 7 6 5 4 3 2 1 0 | 7 .. 0 | 7 .. 0 | 7 .. 0 | -|-------------------------+---------+---------+---------| -| 1 1 1 1 1 1 1 0 | red | green | blue | -`-------------------------------------------------------` -8-bit tag b11111110 -8-bit red channel value -8-bit green channel value -8-bit blue channel value +static unsigned int const qoi_pixels_max = (unsigned int) QOI_PIXELS_MAX; -The alpha value remains unchanged from the previous pixel. +#define QOI_MAXVAL 255 +#define QOI_INDEX_SIZE 64 -.- QOI_OP_RGBA ---------------------------------------------------. -| Byte[0] | Byte[1] | Byte[2] | Byte[3] | Byte[4] | -| 7 6 5 4 3 2 1 0 | 7 .. 0 | 7 .. 0 | 7 .. 0 | 7 .. 0 | -|-------------------------+---------+---------+---------+---------| -| 1 1 1 1 1 1 1 1 | red | green | blue | alpha | -`-----------------------------------------------------------------` -8-bit tag b11111111 -8-bit red channel value -8-bit green channel value -8-bit blue channel value -8-bit alpha channel value -*/ +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) { -/* A pointer to a qoi_desc struct has to be supplied to all of qoi's - functions. It describes either the input format (for qoi_write and - qoi_encode), or is filled with the description read from the file header - (for qoi_read and qoi_decode). + return + (x.rgba.r*3 + x.rgba.g*5 + x.rgba.b*7 + x.rgba.a*11) % QOI_INDEX_SIZE; +} - The colorspace in this qoi_desc is an enum where - 0 = sRGB, i.e. gamma scaled RGB channels and a linear alpha channel - 1 = all channels are linear +static __inline__ void +qoi_clearQoiIndex(qoi_Rgba * index) { - You may use the constants QOI_SRGB or QOI_LINEAR. The colorspace is purely - informative. It will be saved to the file header, but does not affect how - chunks are en-/decoded. -*/ + memset(index, 0, QOI_INDEX_SIZE * sizeof(qoi_Rgba)); -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; +} -/* Encode raw RGB or RGBA pixels into a QOI image in memory. +#define QOI_MAGIC_SIZE 4 - The function either returns NULL on failure (invalid parameters or malloc - failed) or a pointer to the encoded data on success. On success the out_len - is set to the size in bytes of the encoded data. - - The returned qoi data should be free()d after use. -*/ +static char const qoi_magic[QOI_MAGIC_SIZE + 1] = {'q','o','i','f','\0'}; -void -qoi_encode(const unsigned char * const data, - const qoi_Desc * const descP, - const unsigned char ** const qoiImageP, - size_t * const outLenP); +#define QOI_PADDING_SIZE 8 -/* Decode a QOI image from memory. - - The function either returns NULL on failure (invalid parameters or malloc - failed) or a pointer to the decoded pixels. On success, the qoi_Desc struct - is filled with the description from the file header. - - The returned pixel data should be free()d after use. -*/ +static unsigned char const qoi_padding[QOI_PADDING_SIZE] = {0,0,0,0,0,0,0,1}; -void -qoi_decode(const unsigned char * const qoiImage, - size_t const size, - qoi_Desc * const descP, - const unsigned char ** const qoiRasterP); #endif diff --git a/converter/other/qoitopam.c b/converter/other/qoitopam.c index cb9fd925..af6817b7 100644 --- a/converter/other/qoitopam.c +++ b/converter/other/qoitopam.c @@ -1,101 +1,305 @@ /* - * This file is part of Netpbm (http://netpbm.sourceforge.org). - * Copyright (c) 2022 cancername. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ -#include <stdbool.h> + 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 <stdlib.h> -#include <malloc.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" -#define QOI_MAXVAL 0xFF + + +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 -readQoi(FILE * const ifP, - qoi_Desc * const qoiDescP, - const unsigned char ** const qoiRasterP) { +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. - size_t qoiSz; - const unsigned char * qoiImg; + If command line is internally inconsistent (invalid options, etc.), + issue error message to stderr and abort program. - /* Unfortunately, qoi.h does not implement a streaming decoder, - we need to read the whole stream into memory -- expensive. - We might be able to cheat here with mmap sometimes, - but it's not worth the effort. - */ - pm_readfile(stdin, &qoiImg, &qoiSz); + 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 */ - qoi_decode(qoiImg, qoiSz, qoiDescP, qoiRasterP); +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); - free((void*)qoiImg); + return (unsigned char) c; } static void -writeRaster(struct pam const outpam, - const unsigned char * const qoiRaster) { +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); +} - unsigned int qoiRasterCursor; + + +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; - tuplerow = pnm_allocpamrow(&outpam); + assert(qoiDescP); + tuplerow = pnm_allocpamrow(outpamP); - qoiRasterCursor = 0; /* initial value */ + qoi_clearQoiIndex(index); + px.rgba.r = px.rgba.g = px.rgba.b = 0; + px.rgba.a = 255; - for (row = 0; row < outpam.height; ++row) { + for (row = 0, run = 0; row < outpamP->height; ++row) { unsigned int col; - for (col = 0; col < outpam.width; ++col) { - tuplerow[col][PAM_RED_PLANE] = qoiRaster[qoiRasterCursor++]; - tuplerow[col][PAM_GRN_PLANE] = qoiRaster[qoiRasterCursor++]; - tuplerow[col][PAM_BLU_PLANE] = qoiRaster[qoiRasterCursor++]; - if (outpam.depth > 3) - tuplerow[col][PAM_TRN_PLANE] = qoiRaster[qoiRasterCursor++]; + 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; } - pnm_writepamrow(&outpam, tuplerow); + 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, char **argv) { +main(int argc, const char **argv) { - const unsigned char * qoiRaster; + struct CmdlineInfo cmdline; qoi_Desc qoiDesc; struct pam outpam; + FILE * ifP; + + pm_proginit(&argc, argv); - pm_proginit(&argc, (const char **)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; - readQoi(stdin, &qoiDesc, &qoiRaster); + decodeQoiHeader(ifP, &qoiDesc); outpam.depth = qoiDesc.channelCt == 3 ? 3 : 4; outpam.width = qoiDesc.width; @@ -109,10 +313,11 @@ main(int argc, char **argv) { strcpy(outpam.tuple_type, PAM_PPM_ALPHA_TUPLETYPE); pnm_writepaminit(&outpam); + qoiDecode(ifP, &qoiDesc, &outpam); - writeRaster(outpam, qoiRaster); - - free((void*)qoiRaster); + readAndValidatePadding(ifP); return 0; } + + |